From 8553e23f3fcf083249a418683c43f2fa04c4470a Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Aug 2025 23:24:18 -0700 Subject: [PATCH 001/133] chore: increment crate versions to v0.12.0 --- CHANGELOG.md | 2 + Cargo.lock | 144 ++++++++++++------------ Cargo.toml | 12 +- README.md | 2 +- crates/miden-block-prover/Cargo.toml | 2 +- crates/miden-lib/Cargo.toml | 2 +- crates/miden-objects/Cargo.toml | 2 +- crates/miden-testing/Cargo.toml | 2 +- crates/miden-tx-batch-prover/Cargo.toml | 2 +- crates/miden-tx/Cargo.toml | 2 +- 10 files changed, 87 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e29bb2ff72..a630be299a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.12.0 (TBD) + ## 0.11.0 (2025-08-26) ### Features diff --git a/Cargo.lock b/Cargo.lock index ec56449486..751e9bd807 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -250,9 +250,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.2" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "blake3" @@ -302,9 +302,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.33" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "jobserver", "libc", @@ -313,9 +313,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "ciborium" @@ -346,18 +346,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.45" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstyle", "clap_lex", @@ -716,9 +716,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" dependencies = [ "cc", "cfg-if", @@ -794,9 +794,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown", @@ -822,11 +822,11 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cfg-if", "libc", ] @@ -913,9 +913,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom", "libc", @@ -953,7 +953,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", "sha3", "string_cache", "term", @@ -1046,9 +1046,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] @@ -1060,7 +1060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3e7ecca25db4e82c5d47141b723fb781f7dbb2e0c410ad86d4a038fa92fb646" dependencies = [ "miden-core", - "thiserror 2.0.15", + "thiserror 2.0.16", "winter-air", "winter-prover", ] @@ -1076,7 +1076,7 @@ dependencies = [ "miden-core", "miden-mast-package", "smallvec", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -1097,7 +1097,7 @@ dependencies = [ "rustc_version 0.4.1", "semver 1.0.26", "smallvec", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -1117,11 +1117,11 @@ dependencies = [ [[package]] name = "miden-block-prover" -version = "0.11.0" +version = "0.12.0" dependencies = [ "miden-lib", "miden-objects", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -1135,7 +1135,7 @@ dependencies = [ "miden-formatting", "num-derive", "num-traits", - "thiserror 2.0.15", + "thiserror 2.0.16", "winter-math", "winter-utils", ] @@ -1154,7 +1154,7 @@ dependencies = [ "rand", "rand_core", "sha3", - "thiserror 2.0.15", + "thiserror 2.0.16", "winter-crypto", "winter-math", "winter-utils", @@ -1174,7 +1174,7 @@ dependencies = [ "paste", "serde", "serde_spanned 1.0.0", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -1188,7 +1188,7 @@ dependencies = [ [[package]] name = "miden-lib" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "assert_matches", @@ -1198,7 +1198,7 @@ dependencies = [ "miden-stdlib", "rand", "regex", - "thiserror 2.0.15", + "thiserror 2.0.16", "walkdir", ] @@ -1239,7 +1239,7 @@ dependencies = [ "syn", "terminal_size", "textwrap", - "thiserror 2.0.15", + "thiserror 2.0.16", "trybuild", "unicode-width 0.1.14", ] @@ -1257,7 +1257,7 @@ dependencies = [ [[package]] name = "miden-objects" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "assert_matches", @@ -1279,7 +1279,7 @@ dependencies = [ "semver 1.0.26", "serde", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.16", "toml 0.8.23", "winter-air", "winter-rand-utils", @@ -1295,7 +1295,7 @@ dependencies = [ "miden-core", "miden-debug-types", "miden-utils-diagnostics", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "tracing", "winter-prover", @@ -1326,12 +1326,12 @@ dependencies = [ "miden-core", "miden-processor", "miden-utils-sync", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] name = "miden-testing" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "assert_matches", @@ -1344,7 +1344,7 @@ dependencies = [ "rand", "rand_chacha", "rstest", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", "winter-rand-utils", "winterfell", @@ -1352,7 +1352,7 @@ dependencies = [ [[package]] name = "miden-tx" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "assert_matches", @@ -1365,13 +1365,13 @@ dependencies = [ "miden-verifier", "rand", "rstest", - "thiserror 2.0.15", + "thiserror 2.0.16", "tokio", ] [[package]] name = "miden-tx-batch-prover" -version = "0.11.0" +version = "0.12.0" dependencies = [ "miden-objects", "miden-tx", @@ -1409,7 +1409,7 @@ checksum = "9997989e3f883b70c56ebad49d430fa3b996cd0d50a852c0bca2746d652f2390" dependencies = [ "miden-air", "miden-core", - "thiserror 2.0.15", + "thiserror 2.0.16", "tracing", "winter-verifier", ] @@ -1422,7 +1422,7 @@ checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" dependencies = [ "miden-formatting", "smallvec", - "thiserror 2.0.15", + "thiserror 2.0.16", ] [[package]] @@ -1853,19 +1853,19 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.10", + "regex-syntax 0.8.6", ] [[package]] @@ -1879,13 +1879,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", ] [[package]] @@ -1896,9 +1896,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "relative-path" @@ -1974,7 +1974,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1987,7 +1987,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys 0.9.4", @@ -2073,9 +2073,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "indexmap", "itoa", @@ -2261,15 +2261,15 @@ checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" [[package]] name = "tempfile" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix 1.0.8", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2322,11 +2322,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.15", + "thiserror-impl 2.0.16", ] [[package]] @@ -2342,9 +2342,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -2761,11 +2761,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3100,9 +3100,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -3226,7 +3226,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 79943d84d1..2ddc86d644 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,12 +40,12 @@ lto = true [workspace.dependencies] # Workspace crates -miden-block-prover = { default-features = false, path = "crates/miden-block-prover", version = "0.11" } -miden-lib = { default-features = false, path = "crates/miden-lib", version = "0.11" } -miden-objects = { default-features = false, path = "crates/miden-objects", version = "0.11" } -miden-testing = { default-features = false, path = "crates/miden-testing", version = "0.11" } -miden-tx = { default-features = false, path = "crates/miden-tx", version = "0.11" } -miden-tx-batch-prover = { default-features = false, path = "crates/miden-tx-batch-prover", version = "0.11" } +miden-block-prover = { default-features = false, path = "crates/miden-block-prover", version = "0.12" } +miden-lib = { default-features = false, path = "crates/miden-lib", version = "0.12" } +miden-objects = { default-features = false, path = "crates/miden-objects", version = "0.12" } +miden-testing = { default-features = false, path = "crates/miden-testing", version = "0.12" } +miden-tx = { default-features = false, path = "crates/miden-tx", version = "0.12" } +miden-tx-batch-prover = { default-features = false, path = "crates/miden-tx-batch-prover", version = "0.12" } # Miden dependencies miden-assembly = { default-features = false, version = "0.17" } diff --git a/README.md b/README.md index bcd2db18fc..50e92a8fc8 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ If you want to join the technical discussion or learn more about the project, pl ## Status and features -Miden is currently on release v0.11. This is an early version of the protocol and its components. We expect to keep making changes (including breaking changes) to all components. +Miden is currently on release v0.12. This is an early version of the protocol and its components. We expect to keep making changes (including breaking changes) to all components. ### Feature highlights diff --git a/crates/miden-block-prover/Cargo.toml b/crates/miden-block-prover/Cargo.toml index 793a9a70bd..cc3ff8d396 100644 --- a/crates/miden-block-prover/Cargo.toml +++ b/crates/miden-block-prover/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-block-prover" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.11.0" +version = "0.12.0" [lib] bench = false diff --git a/crates/miden-lib/Cargo.toml b/crates/miden-lib/Cargo.toml index 6b6ee5a837..24e2dfe608 100644 --- a/crates/miden-lib/Cargo.toml +++ b/crates/miden-lib/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-lib" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.11.0" +version = "0.12.0" [lib] diff --git a/crates/miden-objects/Cargo.toml b/crates/miden-objects/Cargo.toml index 60cd7355de..4f583a73ec 100644 --- a/crates/miden-objects/Cargo.toml +++ b/crates/miden-objects/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-objects" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.11.0" +version = "0.12.0" [[bench]] harness = false diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index 8c2ef24781..0b239f506a 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-testing" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.11.0" +version = "0.12.0" [features] std = ["miden-lib/std"] diff --git a/crates/miden-tx-batch-prover/Cargo.toml b/crates/miden-tx-batch-prover/Cargo.toml index f3f50e88b8..f7074cd34d 100644 --- a/crates/miden-tx-batch-prover/Cargo.toml +++ b/crates/miden-tx-batch-prover/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-tx-batch-prover" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.11.0" +version = "0.12.0" [lib] bench = false diff --git a/crates/miden-tx/Cargo.toml b/crates/miden-tx/Cargo.toml index a885161ae0..6857f8ed8b 100644 --- a/crates/miden-tx/Cargo.toml +++ b/crates/miden-tx/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-tx" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.11.0" +version = "0.12.0" [features] concurrent = ["miden-prover/concurrent", "std"] From 580c140125fa261d72166d4f8c5be07df158ff1a Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Aug 2025 23:26:36 -0700 Subject: [PATCH 002/133] chore: update MSRV to 1.89 --- CHANGELOG.md | 4 ++++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a630be299a..44ac4219a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.12.0 (TBD) +### Changes + +- [BREAKING] Incremented MSRV to 1.89. + ## 0.11.0 (2025-08-26) ### Features diff --git a/Cargo.toml b/Cargo.toml index 2ddc86d644..ff90b7f987 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ exclude = [".github/"] homepage = "https://miden.xyz" license = "MIT" repository = "https://github.com/0xMiden/miden-base" -rust-version = "1.88" +rust-version = "1.89" [profile.release] codegen-units = 1 diff --git a/README.md b/README.md index 50e92a8fc8..32734f3135 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/0xMiden/miden-base/blob/main/LICENSE) [![test](https://github.com/0xMiden/miden-base/actions/workflows/test.yml/badge.svg)](https://github.com/0xMiden/miden-base/actions/workflows/test.yml) [![build](https://github.com/0xMiden/miden-base/actions/workflows/build.yml/badge.svg)](https://github.com/0xMiden/miden-base/actions/workflows/build.yml) -[![RUST_VERSION](https://img.shields.io/badge/rustc-1.88+-lightgray.svg)](https://www.rust-lang.org/tools/install) +[![RUST_VERSION](https://img.shields.io/badge/rustc-1.89+-lightgray.svg)](https://www.rust-lang.org/tools/install) [![GitHub Release](https://img.shields.io/github/release/0xMiden/miden-base)](https://github.com/0xMiden/miden-base/releases/) Description and core structures for the Miden Rollup protocol. From ccfb5be28d8e8d2994cb1631911f84e0cdb9b48c Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Aug 2025 23:39:57 -0700 Subject: [PATCH 003/133] chore: minor Cargo.toml updates --- Cargo.toml | 1 + crates/miden-objects/Cargo.toml | 2 +- crates/miden-testing/Cargo.toml | 4 ++-- crates/miden-tx/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ff90b7f987..9c519b6e23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ miden-utils-sync = { default-features = false, version = "0.17" } miden-verifier = { default-features = false, version = "0.17" } # External dependencies +anyhow = { default-features = false, version = "1.0" } assert_matches = { default-features = false, version = "1.5" } rand = { default-features = false, version = "0.9" } rstest = { version = "0.26" } diff --git a/crates/miden-objects/Cargo.toml b/crates/miden-objects/Cargo.toml index 4f583a73ec..01b2ea006a 100644 --- a/crates/miden-objects/Cargo.toml +++ b/crates/miden-objects/Cargo.toml @@ -56,7 +56,7 @@ toml = { optional = true, version = "0.8" } getrandom = { features = ["wasm_js"], version = "0.3" } [dev-dependencies] -anyhow = { default-features = false, features = ["backtrace", "std"], version = "1.0" } +anyhow = { features = ["backtrace", "std"], workspace = true } assert_matches = { workspace = true } criterion = { default-features = false, features = ["html_reports"], version = "0.5" } miden-objects = { features = ["testing"], path = "." } diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index 0b239f506a..a50b2c3ba5 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -26,7 +26,7 @@ miden-tx = { features = ["testing"], workspace = true } miden-processor = { workspace = true } # External dependencies -anyhow = { default-features = false, version = "1.0" } +anyhow = { workspace = true } itertools = { default-features = false, features = ["use_alloc"], version = "0.14" } rand = { features = ["os_rng", "small_rng"], workspace = true } rand_chacha = { default-features = false, version = "0.9" } @@ -36,7 +36,7 @@ tokio = { features = ["macros", "rt"], workspace = true } winterfell = { version = "0.13" } [dev-dependencies] -anyhow = { features = ["backtrace", "std"], version = "1.0" } +anyhow = { features = ["backtrace", "std"], workspace = true } assert_matches = { workspace = true } miden-objects = { features = ["std"], workspace = true } rstest = { workspace = true } diff --git a/crates/miden-tx/Cargo.toml b/crates/miden-tx/Cargo.toml index 6857f8ed8b..460d69b844 100644 --- a/crates/miden-tx/Cargo.toml +++ b/crates/miden-tx/Cargo.toml @@ -34,7 +34,7 @@ thiserror = { workspace = true } tokio = { features = ["rt"], workspace = true } [dev-dependencies] -anyhow = { default-features = false, features = ["backtrace", "std"], version = "1.0" } +anyhow = { features = ["backtrace", "std"], workspace = true } assert_matches = { workspace = true } miden-assembly = { workspace = true } miden-tx = { features = ["testing"], path = "." } From 162faeb40879b99ef0c58fd69f79cb815d472b10 Mon Sep 17 00:00:00 2001 From: Marti Date: Wed, 27 Aug 2025 16:16:53 +0200 Subject: [PATCH 004/133] chore: skip publishing benchmark binary crates (#1804) --- .release-plz.toml | 6 ------ bin/bench-note-checker/Cargo.toml | 1 + bin/bench-prover/Cargo.toml | 1 + bin/bench-tx/Cargo.toml | 1 + 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.release-plz.toml b/.release-plz.toml index 9b17cba6d4..c3dfed33db 100644 --- a/.release-plz.toml +++ b/.release-plz.toml @@ -4,9 +4,3 @@ release_always = true # Without the tracking PR, it would never trigger unles git_release_enable = false git_tag_enable = false - -[[package]] -# `cargo-semver` breaks because of the `async` feature (by default it runs with `--all-features`). -# We will either remove the feature (as discussed) or need to add an ability to release-plz to pass flags to `cargo-semver`. -name = "miden-testing" -semver_check = false diff --git a/bin/bench-note-checker/Cargo.toml b/bin/bench-note-checker/Cargo.toml index e40c221eb0..9c81202e8c 100644 --- a/bin/bench-note-checker/Cargo.toml +++ b/bin/bench-note-checker/Cargo.toml @@ -5,6 +5,7 @@ exclude.workspace = true homepage.workspace = true license.workspace = true name = "bench-note-checker" +publish = false repository.workspace = true rust-version.workspace = true version = "0.1.0" diff --git a/bin/bench-prover/Cargo.toml b/bin/bench-prover/Cargo.toml index dbded907ee..f8a705fffb 100644 --- a/bin/bench-prover/Cargo.toml +++ b/bin/bench-prover/Cargo.toml @@ -5,6 +5,7 @@ exclude.workspace = true homepage.workspace = true license.workspace = true name = "bench-prover" +publish = false repository.workspace = true rust-version.workspace = true version = "0.1.0" diff --git a/bin/bench-tx/Cargo.toml b/bin/bench-tx/Cargo.toml index ef7a823b9c..12b29e2e9e 100644 --- a/bin/bench-tx/Cargo.toml +++ b/bin/bench-tx/Cargo.toml @@ -5,6 +5,7 @@ exclude.workspace = true homepage.workspace = true license.workspace = true name = "miden-bench-tx" +publish = false repository.workspace = true rust-version.workspace = true version = "0.1.0" From 8d42e62fc797c1602ee60a42aaa04c0daa534309 Mon Sep 17 00:00:00 2001 From: Marti Date: Wed, 27 Aug 2025 17:50:46 +0200 Subject: [PATCH 005/133] chore: Update external dependencies (#1803) --- Cargo.lock | 101 ++++++++------------------------ crates/miden-objects/Cargo.toml | 4 +- 2 files changed, 26 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 751e9bd807..d9acf7c69c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1060,7 +1060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3e7ecca25db4e82c5d47141b723fb781f7dbb2e0c410ad86d4a038fa92fb646" dependencies = [ "miden-core", - "thiserror 2.0.16", + "thiserror", "winter-air", "winter-prover", ] @@ -1076,7 +1076,7 @@ dependencies = [ "miden-core", "miden-mast-package", "smallvec", - "thiserror 2.0.16", + "thiserror", ] [[package]] @@ -1097,7 +1097,7 @@ dependencies = [ "rustc_version 0.4.1", "semver 1.0.26", "smallvec", - "thiserror 2.0.16", + "thiserror", ] [[package]] @@ -1121,7 +1121,7 @@ version = "0.12.0" dependencies = [ "miden-lib", "miden-objects", - "thiserror 2.0.16", + "thiserror", ] [[package]] @@ -1135,7 +1135,7 @@ dependencies = [ "miden-formatting", "num-derive", "num-traits", - "thiserror 2.0.16", + "thiserror", "winter-math", "winter-utils", ] @@ -1154,7 +1154,7 @@ dependencies = [ "rand", "rand_core", "sha3", - "thiserror 2.0.16", + "thiserror", "winter-crypto", "winter-math", "winter-utils", @@ -1173,8 +1173,8 @@ dependencies = [ "miden-utils-sync", "paste", "serde", - "serde_spanned 1.0.0", - "thiserror 2.0.16", + "serde_spanned", + "thiserror", ] [[package]] @@ -1198,7 +1198,7 @@ dependencies = [ "miden-stdlib", "rand", "regex", - "thiserror 2.0.16", + "thiserror", "walkdir", ] @@ -1239,7 +1239,7 @@ dependencies = [ "syn", "terminal_size", "textwrap", - "thiserror 2.0.16", + "thiserror", "trybuild", "unicode-width 0.1.14", ] @@ -1279,8 +1279,8 @@ dependencies = [ "semver 1.0.26", "serde", "tempfile", - "thiserror 2.0.16", - "toml 0.8.23", + "thiserror", + "toml", "winter-air", "winter-rand-utils", ] @@ -1295,7 +1295,7 @@ dependencies = [ "miden-core", "miden-debug-types", "miden-utils-diagnostics", - "thiserror 2.0.16", + "thiserror", "tokio", "tracing", "winter-prover", @@ -1326,7 +1326,7 @@ dependencies = [ "miden-core", "miden-processor", "miden-utils-sync", - "thiserror 2.0.16", + "thiserror", ] [[package]] @@ -1344,7 +1344,7 @@ dependencies = [ "rand", "rand_chacha", "rstest", - "thiserror 2.0.16", + "thiserror", "tokio", "winter-rand-utils", "winterfell", @@ -1365,7 +1365,7 @@ dependencies = [ "miden-verifier", "rand", "rstest", - "thiserror 2.0.16", + "thiserror", "tokio", ] @@ -1409,7 +1409,7 @@ checksum = "9997989e3f883b70c56ebad49d430fa3b996cd0d50a852c0bca2746d652f2390" dependencies = [ "miden-air", "miden-core", - "thiserror 2.0.16", + "thiserror", "tracing", "winter-verifier", ] @@ -1422,7 +1422,7 @@ checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" dependencies = [ "miden-formatting", "smallvec", - "thiserror 2.0.16", + "thiserror", ] [[package]] @@ -1711,9 +1711,9 @@ dependencies = [ [[package]] name = "pprof" -version = "0.14.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afad4d4df7b31280028245f152d5a575083e2abb822d05736f5e47653e77689f" +checksum = "38a01da47675efa7673b032bf8efd8214f1917d89685e07e395ab125ea42b187" dependencies = [ "aligned-vec", "backtrace", @@ -1729,7 +1729,7 @@ dependencies = [ "spin 0.10.0", "symbolic-demangle", "tempfile", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -2084,15 +2084,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_spanned" version = "1.0.0" @@ -2311,33 +2302,13 @@ dependencies = [ "unicode-width 0.2.1", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.16", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -2420,18 +2391,6 @@ dependencies = [ "tokio-stream", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_edit", -] - [[package]] name = "toml" version = "0.9.5" @@ -2440,7 +2399,7 @@ checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" dependencies = [ "indexmap", "serde", - "serde_spanned 1.0.0", + "serde_spanned", "toml_datetime 0.7.0", "toml_parser", "toml_writer", @@ -2452,9 +2411,6 @@ name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] [[package]] name = "toml_datetime" @@ -2472,10 +2428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", - "serde", - "serde_spanned 0.6.9", "toml_datetime 0.6.11", - "toml_write", "winnow", ] @@ -2488,12 +2441,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "toml_writer" version = "1.0.2" @@ -2574,7 +2521,7 @@ dependencies = [ "serde_json", "target-triple", "termcolor", - "toml 0.9.5", + "toml", ] [[package]] diff --git a/crates/miden-objects/Cargo.toml b/crates/miden-objects/Cargo.toml index 01b2ea006a..b95a1c8795 100644 --- a/crates/miden-objects/Cargo.toml +++ b/crates/miden-objects/Cargo.toml @@ -50,7 +50,7 @@ rand_xoshiro = { default-features = false, optional = true, version = "0.7" } semver = { features = ["serde"], version = "1.0" } serde = { features = ["derive"], optional = true, version = "1.0" } thiserror = { workspace = true } -toml = { optional = true, version = "0.8" } +toml = { optional = true, version = "0.9" } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { features = ["wasm_js"], version = "0.3" } @@ -60,7 +60,7 @@ anyhow = { features = ["backtrace", "std"], workspace = true } assert_matches = { workspace = true } criterion = { default-features = false, features = ["html_reports"], version = "0.5" } miden-objects = { features = ["testing"], path = "." } -pprof = { default-features = false, features = ["criterion", "flamegraph"], version = "0.14" } +pprof = { default-features = false, features = ["criterion", "flamegraph"], version = "0.15" } rstest = { workspace = true } tempfile = { version = "3.19" } winter-air = { version = "0.13" } From f87ffde53cbc0fbfa3723a69709c27c10e819c8f Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Thu, 28 Aug 2025 11:57:19 +0530 Subject: [PATCH 006/133] feat: add prove_dummy APIs on TransactionProver (#1674) * feat: add prove_dummy APIs on TransactionProver * fix: apply suggestions * fix: make clippy happy * fix: add changelog * chore: Use `prove_dummy` in mock chain * fix: use testing feature * fix: apply suggestions * fix: add test for TrasactionWitness * fix: lint * fix: cleanup * fix: refactor * chore: cleanup changelog * chore: cleanup changelog 2 * chore: cleanup changelog 3 * chore: cleanup --------- Co-authored-by: Philipp Gackstatter --- CHANGELOG.md | 6 +- .../src/transaction/executed_tx.rs | 1 + .../src/transaction/tx_witness.rs | 71 ++++++++ crates/miden-testing/src/mock_chain/chain.rs | 9 +- .../src/mock_chain/proven_tx_ext.rs | 44 +---- crates/miden-tx/src/prover/mod.rs | 158 +++++++++++------- 6 files changed, 183 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ac4219a1..3a3bb4d82d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,15 +21,15 @@ - Added `PartialBlockchain::num_tracked_blocks()` ([#1643](https://github.com/0xMiden/miden-base/pull/1643)). - Removed `TransactionScript::compile` & `NoteScript::compile` methods in favor of `ScriptBuilder` ([#1665](https://github.com/0xMiden/miden-base/pull/1665)). - Added `get_initial_code_commitment`, `get_initial_storage_commitment` and `get_initial_vault_root` procedures to `miden::account` module ([#1667](https://github.com/0xMiden/miden-base/pull/1667)). -- Added `FeeParameters` to `BlockHeader` ([#1652](https://github.com/0xMiden/miden-base/pull/1652)). - Added `input_note_get_recipient`, `output_note_get_recipient`, `input_note_get_metadata`, `output_note_get_metadata` procedures to the transaction kernel ([#1648](https://github.com/0xMiden/miden-base/pull/1648)). - Added `input_notes::get_assets` and `output_notes::get_assets` procedures to `miden` library ([#1648](https://github.com/0xMiden/miden-base/pull/1648)). - Added issuance accessor for fungible faucet accounts. ([#1660](https://github.com/0xMiden/miden-base/pull/1660)). -- Added `FeeParameters` to `BlockHeader`, implement `compute_fee` and output `FEE_ASSET` on the transaction stack ([#1652](https://github.com/0xMiden/miden-base/pull/1652), [#1654](https://github.com/0xMiden/miden-base/pull/1654), [#1659](https://github.com/0xMiden/miden-base/pull/1659)). +- Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). +- Added multi-signature authentication component as standard authentication component ([#1599](https://github.com/0xMiden/miden-base/issues/1599)). - Added `FeeParameters` to `BlockHeader` and automatically compute and remove fees from account in the transaction kernel epilogue ([#1652](https://github.com/0xMiden/miden-base/pull/1652), [#1654](https://github.com/0xMiden/miden-base/pull/1654), [#1659](https://github.com/0xMiden/miden-base/pull/1659), [#1664](https://github.com/0xMiden/miden-base/pull/1664), [#1775](https://github.com/0xMiden/miden-base/pull/1775)). +- Added `Address` type to represent account-id based addresses ([#1713](https://github.com/0xMiden/miden-base/pull/1713), [#1750](https://github.com/0xMiden/miden-base/pull/1750)). - [BREAKING] Consolidated to a single async interface and drop `#[maybe_async]` usage ([#1666](https://github.com/0xMiden/miden-base/pull/#1666)). - [BREAKING] Made transaction execution and transaction authentication asynchronous ([#1699](https://github.com/0xMiden/miden-base/pull/1699)). -- Added `Address` type to represent account-id based addresses ([#1713](https://github.com/0xMiden/miden-base/pull/1713), [#1750](https://github.com/0xMiden/miden-base/pull/1750)). - [BREAKING] Return dedicated insufficient fee error from transaction host if account balance is too low ([#1744](https://github.com/0xMiden/miden-base/pull/#1744)). - Added `asset_vault::peek_balance` ([#1745](https://github.com/0xMiden/miden-base/pull/1745)). - Added `get_auth_scheme` method to `AccountComponentInterface` and `AccountInterface` for better authentication scheme extraction ([#1759](https://github.com/0xMiden/miden-base/pull/1759)). diff --git a/crates/miden-objects/src/transaction/executed_tx.rs b/crates/miden-objects/src/transaction/executed_tx.rs index de3eaf44ec..4539755117 100644 --- a/crates/miden-objects/src/transaction/executed_tx.rs +++ b/crates/miden-objects/src/transaction/executed_tx.rs @@ -177,6 +177,7 @@ impl ExecutedTransaction { tx_args: self.tx_args, advice_witness: self.advice_witness, }; + (self.account_delta, self.tx_outputs, tx_witness, self.tx_measurements) } } diff --git a/crates/miden-objects/src/transaction/tx_witness.rs b/crates/miden-objects/src/transaction/tx_witness.rs index df2ce3dc3a..1a7de4fab8 100644 --- a/crates/miden-objects/src/transaction/tx_witness.rs +++ b/crates/miden-objects/src/transaction/tx_witness.rs @@ -46,6 +46,77 @@ impl Deserializable for TransactionWitness { let tx_inputs = TransactionInputs::read_from(source)?; let tx_args = TransactionArgs::read_from(source)?; let advice_witness = AdviceInputs::read_from(source)?; + Ok(Self { tx_inputs, tx_args, advice_witness }) } } + +#[cfg(test)] +mod tests { + use anyhow::Context; + use miden_crypto::Word; + + use crate::account::{AccountBuilder, AccountComponent, StorageSlot}; + use crate::assembly::Assembler; + use crate::asset::FungibleAsset; + use crate::block::{BlockHeader, BlockNumber}; + use crate::testing::noop_auth_component::NoopAuthComponent; + use crate::transaction::{ + InputNotes, + PartialBlockchain, + TransactionArgs, + TransactionInputs, + TransactionWitness, + }; + use crate::vm::AdviceInputs; + + #[test] + fn transaction_witness_serialization_roundtrip() -> anyhow::Result<()> { + use crate::utils::serde::{Deserializable, Serializable}; + + let component = AccountComponent::compile( + "export.foo add.1 end", + Assembler::default(), + vec![StorageSlot::Value(Word::empty())], + )? + .with_supports_all_types(); + let asset = FungibleAsset::mock(200); + let account = AccountBuilder::new([1; 32]) + .with_auth_component(NoopAuthComponent) + .with_component(component) + .with_assets([asset]) + .build_existing()?; + + let partial_blockchain = PartialBlockchain::default(); + let block_header = BlockHeader::mock( + BlockNumber::GENESIS, + Some(partial_blockchain.peaks().hash_peaks()), + None, + &[], + Word::empty(), + ); + + let tx_inputs = TransactionInputs::new( + account.clone(), + None, + block_header.clone(), + partial_blockchain.clone(), + InputNotes::default(), + ) + .unwrap(); + + let witness = TransactionWitness { + tx_inputs, + tx_args: TransactionArgs::default(), + advice_witness: AdviceInputs::default(), + }; + + let bytes = witness.to_bytes(); + let deserialized = TransactionWitness::read_from_bytes(&bytes) + .context("failed to deserialize tx witness")?; + + assert_eq!(witness, deserialized); + + Ok(()) + } +} diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 5a801eaf4c..9a04a131ed 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -39,6 +39,7 @@ use miden_objects::transaction::{ use miden_objects::{MAX_BATCHES_PER_BLOCK, MAX_OUTPUT_NOTES_PER_BATCH, NoteError}; use miden_processor::crypto::RpoRandomCoin; use miden_processor::{DeserializationError, Word}; +use miden_tx::LocalTransactionProver; use miden_tx::auth::BasicAuthenticator; use miden_tx::utils::{ByteReader, Deserializable, Serializable}; use rand::SeedableRng; @@ -46,7 +47,7 @@ use rand_chacha::ChaCha20Rng; use winterfell::ByteWriter; use super::note::MockChainNote; -use crate::{MockChainBuilder, ProvenTransactionExt, TransactionContextBuilder}; +use crate::{MockChainBuilder, TransactionContextBuilder}; // MOCK CHAIN // ================================================================================================ @@ -908,8 +909,10 @@ impl MockChain { let mut account = transaction.initial_account().clone(); account.apply_delta(transaction.account_delta())?; - // This essentially transforms an executed tx into a proven tx with a dummy proof. - let proven_tx = ProvenTransaction::from_executed_transaction_mocked(transaction.clone()); + // Transform the executed tx into a proven tx with a dummy proof. + let proven_tx = LocalTransactionProver::default() + .prove_dummy(transaction.clone()) + .context("failed to dummy-prove executed transaction into proven transaction")?; self.pending_transactions.push(proven_tx); diff --git a/crates/miden-testing/src/mock_chain/proven_tx_ext.rs b/crates/miden-testing/src/mock_chain/proven_tx_ext.rs index fbc28e7e66..c0517014a0 100644 --- a/crates/miden-testing/src/mock_chain/proven_tx_ext.rs +++ b/crates/miden-testing/src/mock_chain/proven_tx_ext.rs @@ -1,11 +1,5 @@ -use miden_objects::account::delta::AccountUpdateDetails; -use miden_objects::transaction::{ - ExecutedTransaction, - ProvenTransaction, - ProvenTransactionBuilder, -}; -use miden_objects::vm::ExecutionProof; -use winterfell::Proof; +use miden_objects::transaction::{ExecutedTransaction, ProvenTransaction}; +use miden_tx::LocalTransactionProver; /// Extension trait to convert an [`ExecutedTransaction`] into a [`ProvenTransaction`] with a dummy /// proof for testing purposes. @@ -16,38 +10,6 @@ pub trait ProvenTransactionExt { impl ProvenTransactionExt for ProvenTransaction { fn from_executed_transaction_mocked(executed_tx: ExecutedTransaction) -> ProvenTransaction { - let block_reference = executed_tx.block_header(); - let account_delta = executed_tx.account_delta().clone(); - let initial_account = executed_tx.initial_account().clone(); - - let account_update_details = if initial_account.is_onchain() { - if initial_account.is_new() { - let mut account = initial_account; - account.apply_delta(&account_delta).expect("account delta should be applicable"); - - AccountUpdateDetails::New(account) - } else { - AccountUpdateDetails::Delta(account_delta) - } - } else { - AccountUpdateDetails::Private - }; - - ProvenTransactionBuilder::new( - executed_tx.account_id(), - executed_tx.initial_account().init_commitment(), - executed_tx.final_account().commitment(), - executed_tx.account_delta().to_commitment(), - block_reference.block_num(), - block_reference.commitment(), - executed_tx.fee(), - executed_tx.expiration_block_num(), - ExecutionProof::new(Proof::new_dummy(), Default::default()), - ) - .add_input_notes(executed_tx.input_notes()) - .add_output_notes(executed_tx.output_notes().iter().cloned()) - .account_update_details(account_update_details) - .build() - .unwrap() + LocalTransactionProver::default().prove_dummy(executed_tx).unwrap() } } diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index c2d25e0499..eb1b2c74a6 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -3,15 +3,21 @@ use alloc::vec::Vec; use miden_lib::transaction::TransactionKernel; use miden_objects::account::delta::AccountUpdateDetails; +use miden_objects::account::{Account, AccountDelta}; use miden_objects::asset::Asset; +use miden_objects::block::BlockNumber; use miden_objects::transaction::{ + ExecutedTransaction, + InputNote, + InputNotes, OutputNote, ProvenTransaction, ProvenTransactionBuilder, + TransactionOutputs, TransactionWitness, }; pub use miden_prover::ProvingOptions; -use miden_prover::prove; +use miden_prover::{ExecutionProof, Word, prove}; use super::TransactionProverError; use crate::host::{AccountProcedureIndexMap, ScriptMastForestStore}; @@ -39,25 +45,71 @@ impl LocalTransactionProver { proof_options, } } -} -impl Default for LocalTransactionProver { - fn default() -> Self { - Self { - mast_store: Arc::new(TransactionMastStore::new()), - proof_options: Default::default(), - } + fn build_proven_transaction( + &self, + input_notes: &InputNotes, + tx_outputs: TransactionOutputs, + pre_fee_account_delta: AccountDelta, + account: &Account, + ref_block_num: BlockNumber, + ref_block_commitment: Word, + proof: ExecutionProof, + ) -> Result { + // erase private note information (convert private full notes to just headers) + let output_notes: Vec<_> = tx_outputs.output_notes.iter().map(OutputNote::shrink).collect(); + + // Compute the commitment of the pre-fee delta, which goes into the proven transaction, + // since it is the output of the transaction and so is needed for proof verification. + let pre_fee_delta_commitment: Word = pre_fee_account_delta.to_commitment(); + + let builder = ProvenTransactionBuilder::new( + account.id(), + account.init_commitment(), + tx_outputs.account.commitment(), + pre_fee_delta_commitment, + ref_block_num, + ref_block_commitment, + tx_outputs.fee, + tx_outputs.expiration_block_num, + proof, + ) + .add_input_notes(input_notes) + .add_output_notes(output_notes); + + // The full transaction delta is the pre fee delta with the fee asset removed. + let mut post_fee_account_delta = pre_fee_account_delta; + post_fee_account_delta + .vault_mut() + .remove_asset(Asset::from(tx_outputs.fee)) + .map_err(TransactionProverError::RemoveFeeAssetFromDelta)?; + + // If the account is on-chain, add the update details. + let builder = match account.is_onchain() { + true => { + let account_update_details = if account.is_new() { + let mut account = account.clone(); + account + .apply_delta(&post_fee_account_delta) + .map_err(TransactionProverError::AccountDeltaApplyFailed)?; + + AccountUpdateDetails::New(account) + } else { + AccountUpdateDetails::Delta(post_fee_account_delta) + }; + + builder.account_update_details(account_update_details) + }, + false => builder, + }; + + builder.build().map_err(TransactionProverError::ProvenTransactionBuildFailed) } -} -impl LocalTransactionProver { pub fn prove( &self, tx_witness: TransactionWitness, ) -> Result { - let mast_store = self.mast_store.clone(); - let proof_options = self.proof_options.clone(); - let TransactionWitness { tx_inputs, tx_args, advice_witness } = tx_witness; let account = tx_inputs.account(); @@ -65,12 +117,10 @@ impl LocalTransactionProver { let ref_block_num = tx_inputs.block_header().block_num(); let ref_block_commitment = tx_inputs.block_header().commitment(); - // execute and prove let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_witness)) .map_err(TransactionProverError::ConflictingAdviceMapEntry)?; - // load the store with account/note/tx_script MASTs self.mast_store.load_account_code(account.code()); let script_mast_store = ScriptMastForestStore::new( @@ -89,7 +139,7 @@ impl LocalTransactionProver { TransactionProverHost::new( &account.into(), input_notes.clone(), - mast_store.as_ref(), + self.mast_store.as_ref(), script_mast_store, acct_procedure_index_map, ) @@ -102,7 +152,7 @@ impl LocalTransactionProver { stack_inputs, advice_inputs.clone(), &mut host, - proof_options, + self.proof_options.clone(), ) .map_err(TransactionProverError::TransactionProgramExecutionFailed)?; @@ -114,53 +164,43 @@ impl LocalTransactionProver { TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes) .map_err(TransactionProverError::TransactionOutputConstructionFailed)?; - // erase private note information (convert private full notes to just headers) - let output_notes: Vec<_> = tx_outputs.output_notes.iter().map(OutputNote::shrink).collect(); - - // Compute the commitment of the pre-fee delta, which goes into the proven transaction, - // since it is the output of the transaction and so is needed for proof verification. - let pre_fee_delta_commitment = pre_fee_account_delta.to_commitment(); - - let builder = ProvenTransactionBuilder::new( - account.id(), - account.init_commitment(), - tx_outputs.account.commitment(), - pre_fee_delta_commitment, + self.build_proven_transaction( + input_notes, + tx_outputs, + pre_fee_account_delta, + account, ref_block_num, ref_block_commitment, - tx_outputs.fee, - tx_outputs.expiration_block_num, proof, ) - .add_input_notes(input_notes) - .add_output_notes(output_notes); - - // The full transaction delta is the pre fee delta with the fee asset removed. - let mut post_fee_account_delta = pre_fee_account_delta; - post_fee_account_delta - .vault_mut() - .remove_asset(Asset::from(tx_outputs.fee)) - .map_err(TransactionProverError::RemoveFeeAssetFromDelta)?; - - // If the account is on-chain, add the update details. - let builder = match account.is_onchain() { - true => { - let account_update_details = if account.is_new() { - let mut account = account.clone(); - account - .apply_delta(&post_fee_account_delta) - .map_err(TransactionProverError::AccountDeltaApplyFailed)?; - - AccountUpdateDetails::New(account) - } else { - AccountUpdateDetails::Delta(post_fee_account_delta) - }; + } +} - builder.account_update_details(account_update_details) - }, - false => builder, - }; +impl Default for LocalTransactionProver { + fn default() -> Self { + Self { + mast_store: Arc::new(TransactionMastStore::new()), + proof_options: Default::default(), + } + } +} - builder.build().map_err(TransactionProverError::ProvenTransactionBuildFailed) +#[cfg(any(feature = "testing", test))] +impl LocalTransactionProver { + pub fn prove_dummy( + &self, + executed_tx: ExecutedTransaction, + ) -> Result { + let (account_delta, tx_outputs, tx_witness, _) = executed_tx.into_parts(); + + self.build_proven_transaction( + tx_witness.tx_inputs.input_notes(), + tx_outputs, + account_delta, + tx_witness.tx_inputs.account(), + tx_witness.tx_inputs.block_header().block_num(), + tx_witness.tx_inputs.block_header().commitment(), + ExecutionProof::new_dummy(), + ) } } From d302bcdcc84dd94601633e51acd37c2bb7d6cdeb Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Wed, 27 Aug 2025 23:33:03 -0700 Subject: [PATCH 007/133] chore: fix changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a3bb4d82d..c688002fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.12.0 (TBD) +### Features + +- Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). + ### Changes - [BREAKING] Incremented MSRV to 1.89. @@ -24,7 +28,6 @@ - Added `input_note_get_recipient`, `output_note_get_recipient`, `input_note_get_metadata`, `output_note_get_metadata` procedures to the transaction kernel ([#1648](https://github.com/0xMiden/miden-base/pull/1648)). - Added `input_notes::get_assets` and `output_notes::get_assets` procedures to `miden` library ([#1648](https://github.com/0xMiden/miden-base/pull/1648)). - Added issuance accessor for fungible faucet accounts. ([#1660](https://github.com/0xMiden/miden-base/pull/1660)). -- Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). - Added multi-signature authentication component as standard authentication component ([#1599](https://github.com/0xMiden/miden-base/issues/1599)). - Added `FeeParameters` to `BlockHeader` and automatically compute and remove fees from account in the transaction kernel epilogue ([#1652](https://github.com/0xMiden/miden-base/pull/1652), [#1654](https://github.com/0xMiden/miden-base/pull/1654), [#1659](https://github.com/0xMiden/miden-base/pull/1659), [#1664](https://github.com/0xMiden/miden-base/pull/1664), [#1775](https://github.com/0xMiden/miden-base/pull/1775)). - Added `Address` type to represent account-id based addresses ([#1713](https://github.com/0xMiden/miden-base/pull/1713), [#1750](https://github.com/0xMiden/miden-base/pull/1750)). From dc5ee5d885c9323c4ec0e4ebd3a59bcf1f95bee6 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sun, 31 Aug 2025 08:02:21 -0700 Subject: [PATCH 008/133] chore: fix broken links --- crates/miden-block-prover/README.md | 2 +- crates/miden-lib/README.md | 2 +- crates/miden-testing/README.md | 2 +- crates/miden-tx-batch-prover/README.md | 2 +- crates/miden-tx/README.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/miden-block-prover/README.md b/crates/miden-block-prover/README.md index 4227291049..fe59be6504 100644 --- a/crates/miden-block-prover/README.md +++ b/crates/miden-block-prover/README.md @@ -4,4 +4,4 @@ This crate contains tools for executing and proving Miden blocks. ## License -This project is [MIT licensed](../LICENSE). +This project is [MIT licensed](../../LICENSE). diff --git a/crates/miden-lib/README.md b/crates/miden-lib/README.md index 37c37ec5c3..918fa2c761 100644 --- a/crates/miden-lib/README.md +++ b/crates/miden-lib/README.md @@ -8,4 +8,4 @@ At this point, all implementations listed above are considered to be experimenta ## License -This project is [MIT licensed](../LICENSE). +This project is [MIT licensed](../../LICENSE). diff --git a/crates/miden-testing/README.md b/crates/miden-testing/README.md index a7ac101147..57bd495f50 100644 --- a/crates/miden-testing/README.md +++ b/crates/miden-testing/README.md @@ -4,4 +4,4 @@ This crate contains tool for testing Miden transactions, batches and blocks. ## License -This project is [MIT licensed](../LICENSE). +This project is [MIT licensed](../../LICENSE). diff --git a/crates/miden-tx-batch-prover/README.md b/crates/miden-tx-batch-prover/README.md index 85d4babb79..37378de9be 100644 --- a/crates/miden-tx-batch-prover/README.md +++ b/crates/miden-tx-batch-prover/README.md @@ -4,4 +4,4 @@ This crate contains tools for executing and proving Miden transaction batches. ## License -This project is [MIT licensed](../LICENSE). +This project is [MIT licensed](../../LICENSE). diff --git a/crates/miden-tx/README.md b/crates/miden-tx/README.md index 04dd88e7cc..9808957a3d 100644 --- a/crates/miden-tx/README.md +++ b/crates/miden-tx/README.md @@ -42,4 +42,4 @@ verifier.verify(proven_transaction); ## License -This project is [MIT licensed](../LICENSE). +This project is [MIT licensed](../../LICENSE). From c0b09b0f35c38a96b0c8eb82f7cfab31c6d63d27 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Mon, 1 Sep 2025 18:26:15 +0300 Subject: [PATCH 009/133] Remove transaction kernel versioning (#1793) * refactor: remove transaction kernel versioning * chore: update changelog * refactor: update docs and comments, impl SequentialCommit for TransactionKernel * chore: update generated file * chore: update kernel_procedures.rs to make both generated file check and rustfmt happy * chore: update changelog * refactor: impl to_commitment as TransactionKernel method --- CHANGELOG.md | 1 + .../src/local_block_prover.rs | 2 +- .../asm/kernels/transaction/lib/memory.masm | 5 +- .../asm/kernels/transaction/lib/prologue.masm | 101 ++++-------------- crates/miden-lib/build.rs | 16 +-- .../miden-lib/src/errors/tx_kernel_errors.rs | 6 +- crates/miden-lib/src/transaction/inputs.rs | 42 ++------ .../kernel_v0.rs => kernel_procedures.rs} | 8 +- crates/miden-lib/src/transaction/memory.rs | 2 +- crates/miden-lib/src/transaction/mod.rs | 27 ++++- .../src/transaction/procedures/mod.rs | 48 --------- .../src/kernel_tests/tx/test_prologue.rs | 14 +-- .../src/mock_chain/chain_builder.rs | 2 +- 13 files changed, 83 insertions(+), 191 deletions(-) rename crates/miden-lib/src/transaction/{procedures/kernel_v0.rs => kernel_procedures.rs} (97%) delete mode 100644 crates/miden-lib/src/transaction/procedures/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee91198b6..d0ec83136a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Changes - [BREAKING] Incremented MSRV to 1.89. +- [BREAKING] Remove versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). ## 0.11.1 (2025-08-28) diff --git a/crates/miden-block-prover/src/local_block_prover.rs b/crates/miden-block-prover/src/local_block_prover.rs index de55f3dd62..b56b0eca2c 100644 --- a/crates/miden-block-prover/src/local_block_prover.rs +++ b/crates/miden-block-prover/src/local_block_prover.rs @@ -158,7 +158,7 @@ impl LocalBlockProver { // Currently undefined and reserved for future use. // See miden-base/1155. let version = 0; - let tx_kernel_commitment = TransactionKernel::kernel_commitment(); + let tx_kernel_commitment = TransactionKernel.to_commitment(); // For now, we're not actually proving the block. let proof_commitment = Word::empty(); diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index 58ad7e2ed3..de1406e5d9 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -147,11 +147,10 @@ const.PARTIAL_BLOCKCHAIN_PEAKS_PTR=1204 # KERNEL DATA # ------------------------------------------------------------------------------------------------- -# The memory address at which the number of the procedures of the selected kernel is stored. +# The memory address at which the number of the kernel procedures is stored. const.NUM_KERNEL_PROCEDURES_PTR=1600 # The memory address at which the hashes of kernel procedures begin. -# TODO: choose the proper memory location for the kernel procedures. const.KERNEL_PROCEDURES_PTR=1604 # ACCOUNT DATA @@ -753,7 +752,7 @@ end #! Outputs: [TX_KERNEL_COMMITMENT] #! #! Where: -#! - TX_KERNEL_COMMITMENT is an accumulative hash from all kernel commitments. +#! - TX_KERNEL_COMMITMENT is the sequential hash of the kernel procedures. export.get_tx_kernel_commitment padw mem_loadw.TX_KERNEL_COMMITMENT_PTR end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm index 2462f7b34e..945aac00a8 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -59,9 +59,7 @@ const.ERR_PROLOGUE_NUMBER_OF_NOTE_INPUTS_EXCEEDED_LIMIT="number of note inputs e const.ERR_PROLOGUE_NOTE_AUTHENTICATION_FAILED="failed to authenticate note inclusion in block" -const.ERR_PROLOGUE_KERNEL_COMMITMENT_MISMATCH="sequential hash over kernel commitments does not match tx kernel commitment from block" - -const.ERR_PROLOGUE_KERNEL_PROCEDURE_COMMITMENT_MISMATCH="sequential hash over kernel procedures does not match kernel commitment" +const.ERR_PROLOGUE_KERNEL_PROCEDURE_COMMITMENT_MISMATCH="sequential hash over kernel procedures does not match kernel commitment from block" # PUBLIC INPUTS # ================================================================================================= @@ -95,109 +93,56 @@ end # KERNEL DATA # ================================================================================================= -#! Saves the procedure roots of the chosen kernel to memory. Verifies that kernel commitment and kernel -#! hash match the sequential hash of all kernels and sequential hash of kernel procedures -#! respectively. +#! Saves the kernel procedure roots to the memory. Verifies that the kernel commitment match the +#! sequential hash of kernel procedures. #! #! Inputs: #! Operand stack: [] -#! Advice stack: [kernel_version] #! Advice map: { -#! TX_KERNEL_COMMITMENT: [KERNEL_COMMITMENTS] -#! KERNEL_COMMITMENT: [KERNEL_PROCEDURE_ROOTS] +#! TX_KERNEL_COMMITMENT: [KERNEL_PROCEDURE_ROOTS] #! } #! Outputs: #! Operand stack: [] #! Advice stack: [] #! #! Where: -#! - kernel_version is the index of the desired kernel in the array of all kernels available for the -#! current transaction. -#! - TX_KERNEL_COMMITMENT is the accumulative hash from all kernel commitments. -#! - [KERNEL_COMMITMENTS] is the array of each kernel commitment. -#! - [KERNEL_PROCEDURE_ROOTS] is the array of procedure roots of the current kernel. +#! - TX_KERNEL_COMMITMENT is the sequential hash of the kernel procedures. +#! - [KERNEL_PROCEDURE_ROOTS] is the array of the kernel procedure roots. proc.process_kernel_data - # move the kernel offset to the operand stack - adv_push.1 - # OS => [kernel_version] - # AS => [] - - # load the tx kernel commitment from memory + # load the transaction kernel commitment from memory exec.memory::get_tx_kernel_commitment - # OS => [TX_KERNEL_COMMITMENT, kernel_version] - # AS => [] - - # push the kernel commitments from the advice map to the advice stack - adv.push_mapvaln - # OS => [TX_KERNEL_COMMITMENT, kernel_version] - # AS => [len_felts, [KERNEL_COMMITMENTS]] - - # move the number of felt elements in the [KERNEL_COMMITMENTS] array to the stack and get the - # number of Words from it - adv_push.1 div.4 - # OS => [len_words, TX_KERNEL_COMMITMENT, kernel_version] - # AS => [[KERNEL_COMMITMENTS]] - - # get the pointer to the memory where kernel commitments will be stored - # Note: for now we use the same address for kernel commitment and for kernel procedures since there is - # only one kernel and its hash will be overwritten by the procedures anyway. - exec.memory::get_kernel_procedures_ptr swap - # OS => [len_words, kernel_mem_ptr, TX_KERNEL_COMMITMENT, kernel_version] - # AS => [[KERNEL_COMMITMENTS]] - - # store the kernel commitments in memory - exec.mem::pipe_words_to_memory - # OS => [C, B, A, kernel_mem_ptr', TX_KERNEL_COMMITMENT, kernel_version] - # AS => [] - - # extract the resulting hash - exec.rpo::squeeze_digest - # OS => [SEQ_HASH, kernel_mem_ptr', TX_KERNEL_COMMITMENT, kernel_version] - # AS => [] - - # assert that sequential hash matches the precomputed kernel commitment - movup.4 drop assert_eqw.err=ERR_PROLOGUE_KERNEL_COMMITMENT_MISMATCH - # OS => [kernel_version] - # AS => [] - - # get the hash of the kernel which will be used in the current transaction - exec.memory::get_kernel_procedures_ptr add - # OS => [kernel_ptr] - # AS => [] - - padw movup.4 mem_loadw - # OS => [KERNEL_COMMITMENT] + # OS => [TX_KERNEL_COMMITMENT] # AS => [] - # push the procedure roots of the chosen kernel from the advice map to the advice stack + # push the procedure roots of the transaction kernel from the advice map to the advice stack adv.push_mapvaln - # OS => [KERNEL_COMMITMENT] - # AS => [len_felts, [PROC_HASHES]] + # OS => [TX_KERNEL_COMMITMENT] + # AS => [len_felts, [KERNEL_PROCEDURE_ROOTS]] - # move the number of felt elements in the [PROC_HASHES] array to the stack and get the - # number of Words from it + # move the number of felt elements in the [KERNEL_PROCEDURE_ROOTS] array to the stack and get + # the number of procedures from it (which is essentially the number of words) adv_push.1 div.4 - # OS => [len_words, KERNEL_COMMITMENT] - # AS => [[PROC_HASHES]] + # OS => [num_kernel_procedures, TX_KERNEL_COMMITMENT] + # AS => [[KERNEL_PROCEDURE_ROOTS]] # store the number of the procedures of the chosen kernel to the memory dup exec.memory::set_num_kernel_procedures - # OS => [len_words, KERNEL_COMMITMENT] - # AS => [[PROC_HASHES]] + # OS => [num_kernel_procedures, TX_KERNEL_COMMITMENT] + # AS => [[KERNEL_PROCEDURE_ROOTS]] # get the pointer to the memory where hashes of the kernel procedures will be stored exec.memory::get_kernel_procedures_ptr swap - # OS => [len_words, kernel_procs_ptr, KERNEL_COMMITMENT] - # AS => [[PROC_HASHES]] + # OS => [num_kernel_procedures, kernel_procs_ptr, TX_KERNEL_COMMITMENT] + # AS => [[KERNEL_PROCEDURE_ROOTS]] # store the kernel procedures to the memory exec.mem::pipe_words_to_memory - # OS => [C, B, A, kernel_procs_ptr', KERNEL_COMMITMENT] + # OS => [C, B, A, kernel_procs_ptr', TX_KERNEL_COMMITMENT] # AS => [] # extract the resulting hash exec.rpo::squeeze_digest - # OS => [SEQ_HASH, kernel_procs_ptr', KERNEL_COMMITMENT] + # OS => [SEQ_KERNEL_PROC_HASH, kernel_procs_ptr', TX_KERNEL_COMMITMENT] # AS => [] # assert that the precomputed hash matches the computed one @@ -236,7 +181,7 @@ end #! - NULLIFIER_ROOT is the root of the tree with nullifiers of all notes that have ever been #! consumed. #! - TX_COMMITMENT is a commitment to the set of transaction IDs which affected accounts in the block. -#! - TX_KERNEL_COMMITMENT is the accumulative hash from all kernel commitments. +#! - TX_KERNEL_COMMITMENT is the sequential hash of the kernel procedures. #! - PROOF_COMMITMENT is the commitment of the block's STARK proof attesting to the correct state transition. #! - block_num is the reference block number. #! - version is the current protocol version. @@ -1205,7 +1150,7 @@ end #! - INITIAL_ACCOUNT_COMMITMENT is the account state prior to the transaction, EMPTY_WORD for new #! accounts. #! - INPUT_NOTES_COMMITMENT, see `transaction::api::get_input_notes_commitment`. -#! - TX_KERNEL_COMMITMENT is the accumulative hash from all kernel commitments. +#! - TX_KERNEL_COMMITMENT is the sequential hash from all kernel commitments. #! - PREV_BLOCK_COMMITMENT is the commitment to the previous block. #! - PARTIAL_BLOCKCHAIN_COMMITMENT is the sequential hash of the reference MMR. #! - ACCOUNT_ROOT is the root of the tree with latest account states for all accounts. diff --git a/crates/miden-lib/build.rs b/crates/miden-lib/build.rs index 4965182bda..137889393c 100644 --- a/crates/miden-lib/build.rs +++ b/crates/miden-lib/build.rs @@ -38,7 +38,7 @@ const ASM_ACCOUNT_COMPONENTS_DIR: &str = "account_components"; const SHARED_UTILS_DIR: &str = "shared_utils"; const SHARED_MODULES_DIR: &str = "shared_modules"; const ASM_TX_KERNEL_DIR: &str = "kernels/transaction"; -const KERNEL_V0_RS_FILE: &str = "src/transaction/procedures/kernel_v0.rs"; +const KERNEL_PROCEDURES_RS_FILE: &str = "src/transaction/kernel_procedures.rs"; const TX_KERNEL_ERRORS_FILE: &str = "src/errors/tx_kernel_errors.rs"; const NOTE_SCRIPT_ERRORS_FILE: &str = "src/errors/note_script_errors.rs"; @@ -154,7 +154,7 @@ fn compile_tx_kernel(source_dir: &Path, target_dir: &Path) -> Result let kernel_lib = assembler .assemble_kernel_from_dir(source_dir.join("api.masm"), Some(source_dir.join("lib")))?; - // generate `kernel_v0.rs` file + // generate kernel `procedures.rs` file generate_kernel_proc_hash_file(kernel_lib.clone())?; let output_file = target_dir.join("tx_kernel").with_extension(Library::LIBRARY_EXTENSION); @@ -218,7 +218,7 @@ fn compile_tx_script_main( tx_script_main.write_to_file(masb_file_path).into_diagnostic() } -/// Generates `kernel_v0.rs` file based on the kernel library +/// Generates kernel `procedures.rs` file based on the kernel library fn generate_kernel_proc_hash_file(kernel: KernelLibrary) -> Result<()> { // Because the kernel Rust file will be stored under ./src, this should be a no-op if we can't // write there @@ -255,17 +255,17 @@ fn generate_kernel_proc_hash_file(kernel: KernelLibrary) -> Result<()> { }).collect::>().join("\n"); fs::write( - KERNEL_V0_RS_FILE, + KERNEL_PROCEDURES_RS_FILE, format!( r#"//! This file is generated by build.rs, do not modify -use miden_objects::{{word, Word}}; +use miden_objects::{{Word, word}}; -// KERNEL V0 PROCEDURES +// KERNEL PROCEDURES // ================================================================================================ -/// Hashes of all dynamically executed procedures from the kernel 0. -pub const KERNEL0_PROCEDURES: [Word; {proc_count}] = [ +/// Hashes of all dynamically executed kernel procedures. +pub const KERNEL_PROCEDURES: [Word; {proc_count}] = [ {generated_procs} ]; "#, diff --git a/crates/miden-lib/src/errors/tx_kernel_errors.rs b/crates/miden-lib/src/errors/tx_kernel_errors.rs index dcbfda2c8d..f640e4c7a0 100644 --- a/crates/miden-lib/src/errors/tx_kernel_errors.rs +++ b/crates/miden-lib/src/errors/tx_kernel_errors.rs @@ -181,10 +181,8 @@ pub const ERR_PROLOGUE_GLOBAL_INPUTS_PROVIDED_DO_NOT_MATCH_BLOCK_COMMITMENT: Mas pub const ERR_PROLOGUE_GLOBAL_INPUTS_PROVIDED_DO_NOT_MATCH_BLOCK_NUMBER_COMMITMENT: MasmError = MasmError::from_static_str("the provided global inputs do not match the block number commitment"); /// Error Message: "note commitment computed from the input note data does not match given note commitment" pub const ERR_PROLOGUE_INPUT_NOTES_COMMITMENT_MISMATCH: MasmError = MasmError::from_static_str("note commitment computed from the input note data does not match given note commitment"); -/// Error Message: "sequential hash over kernel commitments does not match tx kernel commitment from block" -pub const ERR_PROLOGUE_KERNEL_COMMITMENT_MISMATCH: MasmError = MasmError::from_static_str("sequential hash over kernel commitments does not match tx kernel commitment from block"); -/// Error Message: "sequential hash over kernel procedures does not match kernel commitment" -pub const ERR_PROLOGUE_KERNEL_PROCEDURE_COMMITMENT_MISMATCH: MasmError = MasmError::from_static_str("sequential hash over kernel procedures does not match kernel commitment"); +/// Error Message: "sequential hash over kernel procedures does not match kernel commitment from block" +pub const ERR_PROLOGUE_KERNEL_PROCEDURE_COMMITMENT_MISMATCH: MasmError = MasmError::from_static_str("sequential hash over kernel procedures does not match kernel commitment from block"); /// Error Message: "account IDs provided via global inputs and advice provider do not match" pub const ERR_PROLOGUE_MISMATCH_OF_ACCOUNT_IDS_FROM_GLOBAL_INPUTS_AND_ADVICE_PROVIDER: MasmError = MasmError::from_static_str("account IDs provided via global inputs and advice provider do not match"); /// Error Message: "reference block MMR and note's authentication MMR must match" diff --git a/crates/miden-lib/src/transaction/inputs.rs b/crates/miden-lib/src/transaction/inputs.rs index b48b37110f..0188c90420 100644 --- a/crates/miden-lib/src/transaction/inputs.rs +++ b/crates/miden-lib/src/transaction/inputs.rs @@ -2,6 +2,7 @@ use alloc::vec::Vec; use miden_objects::account::{AccountHeader, AccountId, PartialAccount}; use miden_objects::block::AccountWitness; +use miden_objects::crypto::SequentialCommit; use miden_objects::crypto::merkle::InnerNodeInfo; use miden_objects::transaction::{ InputNote, @@ -10,7 +11,7 @@ use miden_objects::transaction::{ TransactionInputs, }; use miden_objects::vm::AdviceInputs; -use miden_objects::{EMPTY_WORD, Felt, FieldElement, WORD_SIZE, Word, ZERO}; +use miden_objects::{EMPTY_WORD, Felt, FieldElement, Word, ZERO}; use thiserror::Error; use super::TransactionKernel; @@ -36,10 +37,9 @@ impl TransactionAdviceInputs { tx_args: &TransactionArgs, ) -> Result { let mut inputs = TransactionAdviceInputs::default(); - let kernel_version = 0; // TODO: replace with user input - inputs.build_stack(tx_inputs, tx_args, kernel_version); - inputs.add_kernel_commitments(kernel_version); + inputs.build_stack(tx_inputs, tx_args); + inputs.add_kernel_commitment(); inputs.add_partial_blockchain(tx_inputs.blockchain()); inputs.add_input_notes(tx_inputs, tx_args)?; @@ -131,12 +131,7 @@ impl TransactionAdviceInputs { /// TX_SCRIPT_ARGS, /// AUTH_ARGS, /// ] - fn build_stack( - &mut self, - tx_inputs: &TransactionInputs, - tx_args: &TransactionArgs, - kernel_version: u8, - ) { + fn build_stack(&mut self, tx_inputs: &TransactionInputs, tx_args: &TransactionArgs) { let header = tx_inputs.block_header(); // --- block header data (keep in sync with kernel's process_block_data) -- @@ -162,9 +157,6 @@ impl TransactionAdviceInputs { self.extend_stack([ZERO, ZERO, ZERO, ZERO]); self.extend_stack(header.note_root()); - // --- kernel version (keep in sync with process_kernel_data) --------- - self.extend_stack([Felt::from(kernel_version)]); - // --- core account items (keep in sync with process_account_data) ---- let account = tx_inputs.account(); self.extend_stack([ @@ -217,27 +209,13 @@ impl TransactionAdviceInputs { // KERNEL INJECTIONS // -------------------------------------------------------------------------------------------- - /// Inserts kernel commitments and hashes of their procedures into the advice inputs. + /// Inserts the kernel commitment and its procedure roots into the advice map. /// /// Inserts the following entries into the advice map: - /// - The accumulative hash of all kernels |-> array of each kernel commitment. - /// - The hash of the selected kernel |-> array of the kernel's procedure roots. - fn add_kernel_commitments(&mut self, kernel_version: u8) { - const NUM_KERNELS: usize = TransactionKernel::NUM_VERSIONS; - - // insert kernels root with kernel commitments into the advice map - let mut kernel_commitments: Vec = Vec::with_capacity(NUM_KERNELS * WORD_SIZE); - for version in 0..NUM_KERNELS { - let kernel_commitment = TransactionKernel::commitment(version as u8); - kernel_commitments.extend_from_slice(kernel_commitment.as_elements()); - } - self.add_map_entry(TransactionKernel::kernel_commitment(), kernel_commitments); - - // insert the selected kernel commitment with its procedure roots into the advice map - self.add_map_entry( - TransactionKernel::commitment(kernel_version), - TransactionKernel::procedures_as_elements(kernel_version), - ); + /// - The commitment of the kernel |-> array of the kernel's procedure roots. + fn add_kernel_commitment(&mut self) { + // insert the kernel commitment with its procedure roots into the advice map + self.add_map_entry(TransactionKernel.to_commitment(), TransactionKernel.to_elements()); } // ACCOUNT INJECTION diff --git a/crates/miden-lib/src/transaction/procedures/kernel_v0.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs similarity index 97% rename from crates/miden-lib/src/transaction/procedures/kernel_v0.rs rename to crates/miden-lib/src/transaction/kernel_procedures.rs index b2a5491c5a..1704fe5ec5 100644 --- a/crates/miden-lib/src/transaction/procedures/kernel_v0.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -1,12 +1,12 @@ //! This file is generated by build.rs, do not modify -use miden_objects::{word, Word}; +use miden_objects::{Word, word}; -// KERNEL V0 PROCEDURES +// KERNEL PROCEDURES // ================================================================================================ -/// Hashes of all dynamically executed procedures from the kernel 0. -pub const KERNEL0_PROCEDURES: [Word; 48] = [ +/// Hashes of all dynamically executed kernel procedures. +pub const KERNEL_PROCEDURES: [Word; 48] = [ // account_get_initial_commitment word!("0x920898348bacd6d98a399301eb308478fd32b32eab019a5a6ef7a6b44abb61f6"), // account_compute_current_commitment diff --git a/crates/miden-lib/src/transaction/memory.rs b/crates/miden-lib/src/transaction/memory.rs index f00fc1844f..e33fa73848 100644 --- a/crates/miden-lib/src/transaction/memory.rs +++ b/crates/miden-lib/src/transaction/memory.rs @@ -215,7 +215,7 @@ pub const PARTIAL_BLOCKCHAIN_PEAKS_PTR: MemoryAddress = 1204; // KERNEL DATA // ------------------------------------------------------------------------------------------------ -/// The memory address at which the number of the procedures of the selected kernel is stored. +/// The memory address at which the number of the kernel procedures is stored. pub const NUM_KERNEL_PROCEDURES_PTR: MemoryAddress = 1600; /// The memory address at which the section, where the hashes of the kernel procedures are stored, diff --git a/crates/miden-lib/src/transaction/mod.rs b/crates/miden-lib/src/transaction/mod.rs index eeed669290..a4c2841780 100644 --- a/crates/miden-lib/src/transaction/mod.rs +++ b/crates/miden-lib/src/transaction/mod.rs @@ -9,6 +9,7 @@ use miden_objects::assembly::debuginfo::SourceManagerSync; use miden_objects::assembly::{Assembler, DefaultSourceManager, KernelLibrary}; use miden_objects::asset::FungibleAsset; use miden_objects::block::BlockNumber; +use miden_objects::crypto::SequentialCommit; use miden_objects::transaction::{ OutputNote, OutputNotes, @@ -47,7 +48,8 @@ pub use crate::errors::{ TransactionTraceParsingError, }; -mod procedures; +mod kernel_procedures; +use kernel_procedures::KERNEL_PROCEDURES; // CONSTANTS // ================================================================================================ @@ -82,6 +84,12 @@ static TX_SCRIPT_MAIN: LazyLock = LazyLock::new(|| { pub struct TransactionKernel; impl TransactionKernel { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// Array of kernel procedures. + pub const PROCEDURES: &'static [Word] = &KERNEL_PROCEDURES; + // KERNEL SOURCE CODE // -------------------------------------------------------------------------------------------- @@ -432,6 +440,14 @@ impl TransactionKernel { Ok((final_account_commitment, account_delta_commitment)) } + + // UTILITY METHODS + // -------------------------------------------------------------------------------------------- + + /// Computes the sequential hash of all kernel procedures. + pub fn to_commitment(&self) -> Word { + ::to_commitment(self) + } } #[cfg(any(feature = "testing", test))] @@ -490,6 +506,15 @@ impl TransactionKernel { } } +impl SequentialCommit for TransactionKernel { + type Commitment = Word; + + /// Returns kernel procedures as vector of Felts. + fn to_elements(&self) -> Vec { + Word::words_as_elements(Self::PROCEDURES).to_vec() + } +} + #[cfg(all(any(feature = "testing", test), feature = "std"))] mod source_manager_ext { use std::path::{Path, PathBuf}; diff --git a/crates/miden-lib/src/transaction/procedures/mod.rs b/crates/miden-lib/src/transaction/procedures/mod.rs deleted file mode 100644 index fd657d57e7..0000000000 --- a/crates/miden-lib/src/transaction/procedures/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -use alloc::vec::Vec; - -use kernel_v0::KERNEL0_PROCEDURES; -use miden_objects::{Felt, Hasher, Word}; - -use super::TransactionKernel; - -// Include kernel v0 procedure roots generated in build.rs -#[rustfmt::skip] -mod kernel_v0; - -// TRANSACTION KERNEL -// ================================================================================================ - -impl TransactionKernel { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - - /// Number of currently used kernel versions. - pub const NUM_VERSIONS: usize = 1; - - /// Array of all available kernels. - pub const PROCEDURES: [&'static [Word]; Self::NUM_VERSIONS] = [&KERNEL0_PROCEDURES]; - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns procedures of the kernel specified by the `kernel_version` as vector of Felts. - pub fn procedures_as_elements(kernel_version: u8) -> Vec { - Word::words_as_elements( - Self::PROCEDURES - .get(kernel_version as usize) - .expect("provided kernel index is out of bounds"), - ) - .to_vec() - } - - /// Computes the accumulative hash of all procedures of the kernel specified by the - /// `kernel_version`. - pub fn commitment(kernel_version: u8) -> Word { - Hasher::hash_elements(&Self::procedures_as_elements(kernel_version)) - } - - /// Computes a hash from all kernel commitments. - pub fn kernel_commitment() -> Word { - Hasher::hash_elements(&[Self::commitment(0).as_elements()].concat()) - } -} diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index 531db003c3..e991dfc68b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -353,23 +353,17 @@ fn partial_blockchain_memory_assertions(process: &Process, prepared_tx: &Transac } fn kernel_data_memory_assertions(process: &Process) { - let latest_version_procedures = TransactionKernel::PROCEDURES - .last() - .expect("kernel should have at least one version"); - // check that the number of kernel procedures stored in the memory is equal to the number of - // kernel procedures in the `TransactionKernel` array. - // - // By default we check procedures of the latest kernel version + // procedures in the `TransactionKernel::PROCEDURES` array assert_eq!( process.get_kernel_mem_word(NUM_KERNEL_PROCEDURES_PTR)[0].as_int(), - latest_version_procedures.len() as u64, + TransactionKernel::PROCEDURES.len() as u64, "Number of the kernel procedures should be stored at the NUM_KERNEL_PROCEDURES_PTR" ); // check that the hashes of the kernel procedures stored in the memory is equal to the hashes in - // `TransactionKernel`'s procedures array - for (i, &proc_hash) in latest_version_procedures.iter().enumerate() { + // `TransactionKernel::PROCEDURES` array + for (i, &proc_hash) in TransactionKernel::PROCEDURES.iter().enumerate() { assert_eq!( process.get_kernel_mem_word(KERNEL_PROCEDURES_PTR + (i * WORD_SIZE) as u32), proc_hash, diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 8bca03847d..6626352d69 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -157,7 +157,7 @@ impl MockChainBuilder { let nullifier_root = NullifierTree::new().root(); let note_root = note_tree.root(); let tx_commitment = transactions.commitment(); - let tx_kernel_commitment = TransactionKernel::kernel_commitment(); + let tx_kernel_commitment = TransactionKernel.to_commitment(); let proof_commitment = Word::empty(); let timestamp = MockChain::TIMESTAMP_START_SECS; let fee_parameters = FeeParameters::new(self.native_asset_id, self.verification_base_fee) From 30339540f879aa309abdd511e31975542d627cc7 Mon Sep 17 00:00:00 2001 From: Marti Date: Tue, 2 Sep 2025 15:20:46 +0200 Subject: [PATCH 010/133] feat: Simplify signature loading into the `AdviceMap` (#1725) * feat: add_signature helper to modify tx args * chore: move new method to the state mutators sec * chore: use new helper in tests * chore: lints * chore: changelog entry --- CHANGELOG.md | 1 + .../miden-objects/src/transaction/tx_args.rs | 14 ++++++++- .../miden-testing/src/tx_context/builder.rs | 18 +++++++++++ crates/miden-testing/tests/auth/multisig.rs | 30 ++++++------------- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0ec83136a..ed13038a0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). - Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). ### Changes diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index be29d4edb2..952dbbbb9c 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -2,9 +2,10 @@ use alloc::collections::{BTreeMap, BTreeSet}; use alloc::sync::Arc; use alloc::vec::Vec; +use miden_crypto::dsa::rpo_falcon512::PublicKey; use miden_crypto::merkle::InnerNodeInfo; -use super::{AccountInputs, Felt, Word}; +use super::{AccountInputs, Felt, Hasher, Word}; use crate::note::{NoteId, NoteRecipient}; use crate::utils::serde::{ ByteReader, @@ -187,6 +188,17 @@ impl TransactionArgs { self.advice_inputs.extend(AdviceInputs::default().with_map(new_elements)); } + /// Adds the `signature` corresponding to `public_key` on `message` to the advice inputs' map. + /// + /// The advice inputs' map is extended with the following key: + /// + /// - hash(public_key, message) |-> signature. + pub fn add_signature(&mut self, public_key: PublicKey, message: Word, signature: Vec) { + self.advice_inputs + .map + .insert(Hasher::merge(&[public_key.into(), message]), signature); + } + /// Populates the advice inputs with the specified note recipient details. /// /// The advice inputs' map is extended with the following keys: diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 3200395a4d..d8e429e648 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -12,6 +12,7 @@ use miden_objects::EMPTY_WORD; use miden_objects::account::Account; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::debuginfo::SourceManagerSync; +use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; use miden_objects::note::{Note, NoteId}; use miden_objects::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; use miden_objects::testing::noop_auth_component::NoopAuthComponent; @@ -77,6 +78,7 @@ pub struct TransactionContextBuilder { note_args: BTreeMap, transaction_inputs: Option, auth_args: Word, + signatures: Vec<(PublicKey, Word, Vec)>, } impl TransactionContextBuilder { @@ -95,6 +97,7 @@ impl TransactionContextBuilder { note_args: BTreeMap::new(), foreign_account_inputs: vec![], auth_args: EMPTY_WORD, + signatures: Vec::new(), } } @@ -224,6 +227,17 @@ impl TransactionContextBuilder { self } + /// Add a new signature for the message and the public key. + pub fn add_signature( + mut self, + public_key: PublicKey, + message: Word, + signature: Vec, + ) -> Self { + self.signatures.push((public_key, message, signature)); + self + } + /// Builds the [TransactionContext]. /// /// If no transaction inputs were provided manually, an ad-hoc MockChain is created in order @@ -271,6 +285,10 @@ impl TransactionContextBuilder { tx_args.extend_advice_inputs(self.advice_inputs.clone()); tx_args.extend_output_note_recipients(self.expected_output_notes.clone()); + for (public_key, message, signature) in self.signatures { + tx_args.add_signature(public_key, message, signature); + } + let mast_store = { let mast_forest_store = TransactionMastStore::new(); mast_forest_store.load_account_code(tx_inputs.account().code()); diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index a0320403af..724450495a 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -16,8 +16,7 @@ use miden_objects::testing::account_id::{ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, }; use miden_objects::transaction::OutputNote; -use miden_objects::vm::AdviceMap; -use miden_objects::{Felt, Hasher, Word}; +use miden_objects::{Felt, Word}; use miden_testing::{Auth, MockChainBuilder, assert_transaction_executor_error}; use miden_tx::TransactionExecutorError; use miden_tx::auth::{BasicAuthenticator, SigningInputs, TransactionAuthenticator}; @@ -141,16 +140,12 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { let sig_1 = authenticators[0].get_signature(public_keys[0].into(), &tx_summary).await?; let sig_2 = authenticators[1].get_signature(public_keys[1].into(), &tx_summary).await?; - // Populate advice map with signatures - let mut advice_map = AdviceMap::default(); - advice_map.insert(Hasher::merge(&[public_keys[0].into(), msg]), sig_1); - advice_map.insert(Hasher::merge(&[public_keys[1].into(), msg]), sig_2); - // Execute transaction with signatures - should succeed let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? .extend_expected_output_notes(vec![OutputNote::Full(output_note)]) - .extend_advice_map(advice_map.iter().map(|(k, v)| (*k, v.to_vec()))) + .add_signature(public_keys[0], msg, sig_1) + .add_signature(public_keys[1], msg, sig_2) .auth_args(salt) .build()? .execute() @@ -227,16 +222,12 @@ async fn test_multisig_2_of_4_all_signer_combinations() -> anyhow::Result<()> { .get_signature(public_keys[*signer2_idx].into(), &tx_summary) .await?; - // Populate advice map with signatures from the chosen signers - let mut advice_map = AdviceMap::default(); - advice_map.insert(Hasher::merge(&[public_keys[*signer1_idx].into(), msg]), sig_1); - advice_map.insert(Hasher::merge(&[public_keys[*signer2_idx].into(), msg]), sig_2); - // Execute transaction with signatures - should succeed for any combination let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .extend_advice_map(advice_map.iter().map(|(k, v)| (*k, v.to_vec()))) .auth_args(salt) + .add_signature(public_keys[*signer1_idx], msg, sig_1) + .add_signature(public_keys[*signer2_idx], msg, sig_2) .build()?; let executed_tx = tx_context_execute.execute().await.unwrap_or_else(|_| { @@ -294,15 +285,11 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { let sig_1 = authenticators[0].get_signature(public_keys[0].into(), &tx_summary).await?; let sig_2 = authenticators[1].get_signature(public_keys[1].into(), &tx_summary).await?; - // Populate advice map with signatures - let mut advice_map = AdviceMap::default(); - advice_map.insert(Hasher::merge(&[public_keys[0].into(), msg]), sig_1); - advice_map.insert(Hasher::merge(&[public_keys[1].into(), msg]), sig_2); - // Execute transaction with signatures - should succeed (first execution) let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .extend_advice_map(advice_map.iter().map(|(k, v)| (*k, v.to_vec()))) + .add_signature(public_keys[0], msg, sig_1.clone()) + .add_signature(public_keys[1], msg, sig_2.clone()) .auth_args(salt) .build()?; @@ -315,7 +302,8 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { // Now attempt to execute the same transaction again - should fail due to replay protection let tx_context_replay = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .extend_advice_map(advice_map.iter().map(|(k, v)| (*k, v.to_vec()))) + .add_signature(public_keys[0], msg, sig_1) + .add_signature(public_keys[1], msg, sig_2) .auth_args(salt) .build()?; From 39d7158a27f14b757da2c60c9c7609e731c93970 Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Wed, 3 Sep 2025 11:49:15 +0530 Subject: [PATCH 011/133] feat: remove bech32 types from public NetworkID (#1787) * feat: remove bech32 types from public NetworkID * fix: add changelog * fix: refactor CustomNetworkId * fix: make clippy happy * apply suggestions * Update crates/miden-objects/src/account/account_id/network_id.rs --------- Co-authored-by: Marti --- CHANGELOG.md | 1 + .../src/account/account_id/mod.rs | 2 +- .../src/account/account_id/network_id.rs | 59 +++++++++++++++++-- crates/miden-objects/src/account/mod.rs | 1 + crates/miden-objects/src/address/mod.rs | 13 ++-- 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed13038a0b..d975687a7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ - Document `Address` in Miden book ([#1792](https://github.com/0xMiden/miden-base/pull/1792)). - Add `asset_vault::peek_balance` ([#1745](https://github.com/0xMiden/miden-base/pull/1745)). - Add `get_auth_scheme` method to `AccountComponentInterface` and `AccountInterface` for better authentication scheme extraction ([#1759](https://github.com/0xMiden/miden-base/pull/1759)). +- Add `CustomNetworkId` in `NetworkID` ([#1787](https://github.com/0xMiden/miden-base/pull/1787)). ### Changes diff --git a/crates/miden-objects/src/account/account_id/mod.rs b/crates/miden-objects/src/account/account_id/mod.rs index b4528c28d8..88adac5f22 100644 --- a/crates/miden-objects/src/account/account_id/mod.rs +++ b/crates/miden-objects/src/account/account_id/mod.rs @@ -7,7 +7,7 @@ pub use id_prefix::AccountIdPrefix; mod seed; mod network_id; -pub use network_id::NetworkId; +pub use network_id::{CustomNetworkId, NetworkId}; mod account_type; pub use account_type::AccountType; diff --git a/crates/miden-objects/src/account/account_id/network_id.rs b/crates/miden-objects/src/account/account_id/network_id.rs index 9b38a73d15..dc26b449ca 100644 --- a/crates/miden-objects/src/account/account_id/network_id.rs +++ b/crates/miden-objects/src/account/account_id/network_id.rs @@ -1,5 +1,6 @@ +use alloc::boxed::Box; +use alloc::str::FromStr; use alloc::string::ToString; -use core::str::FromStr; use bech32::Hrp; @@ -9,12 +10,13 @@ use crate::errors::NetworkIdError; // the public API since that crate does not have a stable release. /// The identifier of a Miden network. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum NetworkId { Mainnet, Testnet, Devnet, - Custom(Hrp), + // Box the [`CustomNetworkId`] to keep the stack size of network ID relatively small. + Custom(Box), } impl NetworkId { @@ -43,7 +45,7 @@ impl NetworkId { NetworkId::MAINNET => NetworkId::Mainnet, NetworkId::TESTNET => NetworkId::Testnet, NetworkId::DEVNET => NetworkId::Devnet, - _ => NetworkId::Custom(hrp), + _ => NetworkId::Custom(Box::new(CustomNetworkId::from_hrp(hrp))), } } @@ -59,7 +61,7 @@ impl NetworkId { Hrp::parse(NetworkId::TESTNET).expect("testnet hrp should be valid") }, NetworkId::Devnet => Hrp::parse(NetworkId::DEVNET).expect("devnet hrp should be valid"), - NetworkId::Custom(custom) => custom, + NetworkId::Custom(custom) => custom.as_hrp(), } } @@ -105,3 +107,50 @@ impl core::fmt::Display for NetworkId { f.write_str(self.as_str()) } } + +// CUSTOM NETWORK ID +// ================================================================================================ + +/// A wrapper around bech32 HRP(human-readable part) for custom network identifiers. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct CustomNetworkId { + hrp: Hrp, +} + +impl CustomNetworkId { + /// Creates a new [`CustomNetworkId`] from a [`bech32::Hrp`]. + pub(crate) fn from_hrp(hrp: Hrp) -> Self { + CustomNetworkId { hrp } + } + + /// Converts this [`CustomNetworkId`] to a [`bech32::Hrp`]. + pub(crate) fn as_hrp(&self) -> Hrp { + self.hrp + } + + /// Returns the string representation of this custom HRP. + pub fn as_str(&self) -> &str { + self.hrp.as_str() + } +} + +impl FromStr for CustomNetworkId { + type Err = NetworkIdError; + + /// Creates a [`CustomNetworkId`] from a String. + /// # Errors + /// + /// Returns an error if the string is not a valid HRP according to bech32 rules + fn from_str(hrp_str: &str) -> Result { + Ok(CustomNetworkId { + hrp: Hrp::parse(hrp_str) + .map_err(|source| NetworkIdError::NetworkIdParseError(source.to_string().into()))?, + }) + } +} + +impl core::fmt::Display for CustomNetworkId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.as_str()) + } +} diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index d3555e2586..4156787d3f 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -17,6 +17,7 @@ pub use account_id::{ AccountIdVersion, AccountStorageMode, AccountType, + CustomNetworkId, NetworkId, }; diff --git a/crates/miden-objects/src/address/mod.rs b/crates/miden-objects/src/address/mod.rs index 03608f845d..023316b3e9 100644 --- a/crates/miden-objects/src/address/mod.rs +++ b/crates/miden-objects/src/address/mod.rs @@ -324,11 +324,14 @@ impl TryFrom<[u8; AccountIdAddress::SERIALIZED_SIZE]> for AccountIdAddress { #[cfg(test)] mod tests { + use alloc::boxed::Box; + use alloc::str::FromStr; + use assert_matches::assert_matches; - use bech32::{Bech32, Hrp, NoChecksum}; + use bech32::{Bech32, NoChecksum}; use super::*; - use crate::account::AccountType; + use crate::account::{AccountType, CustomNetworkId}; use crate::testing::account_id::{ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, AccountIdBuilder}; /// Tests that an account ID address can be encoded and decoded. @@ -343,8 +346,8 @@ mod tests { let rng = &mut rand::rng(); for network_id in [ NetworkId::Mainnet, - NetworkId::Custom(Hrp::parse("custom").unwrap()), - NetworkId::Custom(Hrp::parse(longest_possible_hrp).unwrap()), + NetworkId::Custom(Box::new(CustomNetworkId::from_str("custom").unwrap())), + NetworkId::Custom(Box::new(CustomNetworkId::from_str(longest_possible_hrp).unwrap())), ] { for (idx, account_id) in [ AccountIdBuilder::new() @@ -367,7 +370,7 @@ mod tests { AccountIdAddress::new(account_id, AddressInterface::BasicWallet); let address = Address::from(account_id_address); - let bech32_string = address.to_bech32(network_id); + let bech32_string = address.to_bech32(network_id.clone()); let (decoded_network_id, decoded_address) = Address::from_bech32(&bech32_string).unwrap(); From a2082b99c4ef542d25de3269b0620c8726db74bc Mon Sep 17 00:00:00 2001 From: Marti Date: Wed, 3 Sep 2025 10:52:41 +0200 Subject: [PATCH 012/133] chore: update `Makefile` installation commands (#1846) * fix: checking installation should use `cargo nextest` * chore: add machete to install tools * Update Makefile Co-authored-by: Philipp Gackstatter --------- Co-authored-by: Philipp Gackstatter --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0f5e7a3d86..575a5490ac 100644 --- a/Makefile +++ b/Makefile @@ -145,8 +145,9 @@ check-tools: ## Checks if development tools are installed @echo "Checking development tools..." @command -v mdbook >/dev/null 2>&1 && echo "[OK] mdbook is installed" || echo "[MISSING] mdbook is not installed (run: make install-tools)" @command -v typos >/dev/null 2>&1 && echo "[OK] typos is installed" || echo "[MISSING] typos is not installed (run: make install-tools)" - @command -v nextest >/dev/null 2>&1 && echo "[OK] nextest is installed" || echo "[MISSING] nextest is not installed (run: make install-tools)" + @command -v cargo nextest >/dev/null 2>&1 && echo "[OK] cargo-nextest is installed" || echo "[MISSING] cargo-nextest is not installed (run: make install-tools)" @command -v taplo >/dev/null 2>&1 && echo "[OK] taplo is installed" || echo "[MISSING] taplo is not installed (run: make install-tools)" + @command -v cargo-machete >/dev/null 2>&1 && echo "[OK] cargo-machete is installed" || echo "[MISSING] cargo-machete is not installed (run: make install-tools)" .PHONY: install-tools install-tools: ## Installs development tools required by the Makefile (mdbook, typos, nextest, taplo) @@ -155,4 +156,5 @@ install-tools: ## Installs development tools required by the Makefile (mdbook, t cargo install typos-cli --locked cargo install cargo-nextest --locked cargo install taplo-cli --locked + cargo install cargo-machete --locked @echo "Development tools installation complete!" From 6aae32682f6b861a584d8272284b5703cafa618f Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 3 Sep 2025 20:24:11 +0200 Subject: [PATCH 013/133] chore: rename `is_onchain` to `has_public_state` (#1847) --- CHANGELOG.md | 1 + crates/miden-lib/src/account/interface/mod.rs | 6 +-- .../src/account/account_id/id_prefix.rs | 6 +-- .../src/account/account_id/mod.rs | 6 +-- .../src/account/account_id/storage_mode.rs | 4 +- crates/miden-objects/src/account/mod.rs | 6 +-- .../src/block/block_account_update.rs | 5 +- crates/miden-objects/src/errors.rs | 12 ++--- crates/miden-objects/src/note/note_tag.rs | 2 +- .../src/transaction/proven_tx.rs | 52 ++++++++++--------- crates/miden-tx/src/prover/mod.rs | 4 +- 11 files changed, 53 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d975687a7c..c0ba10ce8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - [BREAKING] Incremented MSRV to 1.89. - [BREAKING] Remove versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). +- [BREAKING] Rename the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). ## 0.11.1 (2025-08-28) diff --git a/crates/miden-lib/src/account/interface/mod.rs b/crates/miden-lib/src/account/interface/mod.rs index d8e0b62d8b..92e6acfa6c 100644 --- a/crates/miden-lib/src/account/interface/mod.rs +++ b/crates/miden-lib/src/account/interface/mod.rs @@ -79,12 +79,12 @@ impl AccountInterface { self.account_id.is_regular_account() } - /// Returns `true` if the full state of the account is on chain, i.e. if the modes are + /// Returns `true` if the full state of the account is public on chain, i.e. if the modes are /// [`AccountStorageMode::Public`](miden_objects::account::AccountStorageMode::Public) or /// [`AccountStorageMode::Network`](miden_objects::account::AccountStorageMode::Network), /// `false` otherwise. - pub fn is_onchain(&self) -> bool { - self.account_id.is_onchain() + pub fn has_public_state(&self) -> bool { + self.account_id.has_public_state() } /// Returns `true` if the reference account is a private account, `false` otherwise. diff --git a/crates/miden-objects/src/account/account_id/id_prefix.rs b/crates/miden-objects/src/account/account_id/id_prefix.rs index ff11830bfe..97fa2552b4 100644 --- a/crates/miden-objects/src/account/account_id/id_prefix.rs +++ b/crates/miden-objects/src/account/account_id/id_prefix.rs @@ -119,10 +119,10 @@ impl AccountIdPrefix { } } - /// Returns `true` if the full state of the account is on chain, i.e. if the modes are + /// Returns `true` if the full state of the account is public on chain, i.e. if the modes are /// [`AccountStorageMode::Public`] or [`AccountStorageMode::Network`], `false` otherwise. - pub fn is_onchain(&self) -> bool { - self.storage_mode().is_onchain() + pub fn has_public_state(&self) -> bool { + self.storage_mode().has_public_state() } /// Returns `true` if the storage mode is [`AccountStorageMode::Public`], `false` otherwise. diff --git a/crates/miden-objects/src/account/account_id/mod.rs b/crates/miden-objects/src/account/account_id/mod.rs index 88adac5f22..6e552f6577 100644 --- a/crates/miden-objects/src/account/account_id/mod.rs +++ b/crates/miden-objects/src/account/account_id/mod.rs @@ -231,10 +231,10 @@ impl AccountId { } } - /// Returns `true` if the full state of the account is on chain, i.e. if the modes are + /// Returns `true` if the full state of the account is public on chain, i.e. if the modes are /// [`AccountStorageMode::Public`] or [`AccountStorageMode::Network`], `false` otherwise. - pub fn is_onchain(&self) -> bool { - self.storage_mode().is_onchain() + pub fn has_public_state(&self) -> bool { + self.storage_mode().has_public_state() } /// Returns `true` if the storage mode is [`AccountStorageMode::Public`], `false` otherwise. diff --git a/crates/miden-objects/src/account/account_id/storage_mode.rs b/crates/miden-objects/src/account/account_id/storage_mode.rs index 107ae269b2..12670701ce 100644 --- a/crates/miden-objects/src/account/account_id/storage_mode.rs +++ b/crates/miden-objects/src/account/account_id/storage_mode.rs @@ -28,9 +28,9 @@ pub enum AccountStorageMode { } impl AccountStorageMode { - /// Returns `true` if the full state of the account is on chain, i.e. if the modes are + /// Returns `true` if the full state of the account is public on chain, i.e. if the modes are /// [`Self::Public`] or [`Self::Network`], `false` otherwise. - pub fn is_onchain(&self) -> bool { + pub fn has_public_state(&self) -> bool { matches!(self, Self::Public | Self::Network) } diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index 4156787d3f..9df2485cf1 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -262,10 +262,10 @@ impl Account { self.id.is_regular_account() } - /// Returns `true` if the full state of the account is on chain, i.e. if the storage modes are + /// Returns `true` if the full state of the account is public on chain, i.e. if the modes are /// [`AccountStorageMode::Public`] or [`AccountStorageMode::Network`], `false` otherwise. - pub fn is_onchain(&self) -> bool { - self.id().is_onchain() + pub fn has_public_state(&self) -> bool { + self.id().has_public_state() } /// Returns `true` if the storage mode is [`AccountStorageMode::Public`], `false` otherwise. diff --git a/crates/miden-objects/src/block/block_account_update.rs b/crates/miden-objects/src/block/block_account_update.rs index cac9a6a3b7..d3e2541613 100644 --- a/crates/miden-objects/src/block/block_account_update.rs +++ b/crates/miden-objects/src/block/block_account_update.rs @@ -46,10 +46,9 @@ impl BlockAccountUpdate { self.final_state_commitment } - /// Returns the description of the updates for on-chain accounts. + /// Returns the account update details for this account update. /// - /// These descriptions can be used to build the new account state from the previous account - /// state. + /// These details can be used to build the new account state from the previous account state. pub fn details(&self) -> &AccountUpdateDetails { &self.details } diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index 895d725546..bb25ff909c 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -655,14 +655,14 @@ pub enum ProvenTransactionError { InputNotesError(TransactionInputError), #[error("private account {0} should not have account details")] PrivateAccountWithDetails(AccountId), - #[error("on-chain account {0} is missing its account details")] - OnChainAccountMissingDetails(AccountId), - #[error("new on-chain account {0} is missing its account details")] - NewOnChainAccountRequiresFullDetails(AccountId), + #[error("account {0} with public state is missing its account details")] + PublicStateAccountMissingDetails(AccountId), + #[error("new account {0} with public state is missing its account details")] + NewPublicStateAccountRequiresFullDetails(AccountId), #[error( - "existing on-chain account {0} should only provide delta updates instead of full details" + "existing account {0} with public state should only provide delta updates instead of full details" )] - ExistingOnChainAccountRequiresDeltaDetails(AccountId), + ExistingPublicStateAccountRequiresDeltaDetails(AccountId), #[error("failed to construct output notes for proven transaction")] OutputNotesError(TransactionOutputError), #[error( diff --git a/crates/miden-objects/src/note/note_tag.rs b/crates/miden-objects/src/note/note_tag.rs index 4a7a5acf4f..4da8f012fb 100644 --- a/crates/miden-objects/src/note/note_tag.rs +++ b/crates/miden-objects/src/note/note_tag.rs @@ -32,7 +32,7 @@ const LOCAL_ANY: u32 = 0xc000_0000; /// /// The execution hints are _not_ enforced, therefore function only as hints. For example, if a /// note's tag is created with the [NoteExecutionMode::Network], further validation is necessary to -/// check the account_id is known, that the account's state is on-chain, and the account is +/// check the account_id is known, that the account's state is public on chain, and the account is /// controlled by the network. /// /// The goal of the hint is to allow for a network node to quickly filter notes that are not diff --git a/crates/miden-objects/src/transaction/proven_tx.rs b/crates/miden-objects/src/transaction/proven_tx.rs index 454bf6287d..c903255bca 100644 --- a/crates/miden-objects/src/transaction/proven_tx.rs +++ b/crates/miden-objects/src/transaction/proven_tx.rs @@ -141,21 +141,21 @@ impl ProvenTransaction { /// - The size of the serialized account update exceeds [`ACCOUNT_UPDATE_MAX_SIZE`]. /// - The transaction is empty, which is the case if the account state is unchanged or the /// number of input notes is zero. - /// - The transaction was executed against a _new_ on-chain account and its account ID does not - /// match the ID in the account update. - /// - The transaction was executed against a _new_ on-chain account and its commitment does not - /// match the final state commitment of the account update. + /// - The transaction was executed against a _new_ account with public state and its account ID + /// does not match the ID in the account update. + /// - The transaction was executed against a _new_ account with public state and its commitment + /// does not match the final state commitment of the account update. /// - The transaction was executed against a private account and the account update is _not_ of /// type [`AccountUpdateDetails::Private`]. - /// - The transaction was executed against an on-chain account and the update is of type - /// [`AccountUpdateDetails::Private`]. - /// - The transaction was executed against an _existing_ on-chain account and the update is of - /// type [`AccountUpdateDetails::New`]. - /// - The transaction creates a _new_ on-chain account and the update is of type + /// - The transaction was executed against an account with public state and the update is of + /// type [`AccountUpdateDetails::Private`]. + /// - The transaction was executed against an _existing_ account with public state and the + /// update is of type [`AccountUpdateDetails::New`]. + /// - The transaction creates a _new_ account with public state and the update is of type /// [`AccountUpdateDetails::Delta`]. fn validate(self) -> Result { - // If the account is on-chain, then the account update details must be present. - if self.account_id().is_onchain() { + // If the account's state is public, then the account update details must be present. + if self.account_id().has_public_state() { self.account_update.validate()?; // check that either the account state was changed or at least one note was consumed, @@ -170,14 +170,14 @@ impl ProvenTransaction { let is_new_account = self.account_update.initial_state_commitment() == Word::empty(); match self.account_update.details() { AccountUpdateDetails::Private => { - return Err(ProvenTransactionError::OnChainAccountMissingDetails( + return Err(ProvenTransactionError::PublicStateAccountMissingDetails( self.account_id(), )); }, AccountUpdateDetails::New(account) => { if !is_new_account { return Err( - ProvenTransactionError::ExistingOnChainAccountRequiresDeltaDetails( + ProvenTransactionError::ExistingPublicStateAccountRequiresDeltaDetails( self.account_id(), ), ); @@ -197,9 +197,11 @@ impl ProvenTransaction { }, AccountUpdateDetails::Delta(_) => { if is_new_account { - return Err(ProvenTransactionError::NewOnChainAccountRequiresFullDetails( - self.account_id(), - )); + return Err( + ProvenTransactionError::NewPublicStateAccountRequiresFullDetails( + self.account_id(), + ), + ); } }, } @@ -380,17 +382,17 @@ impl ProvenTransactionBuilder { /// - The size of the serialized account update exceeds [`ACCOUNT_UPDATE_MAX_SIZE`]. /// - The transaction is empty, which is the case if the account state is unchanged or the /// number of input notes is zero. - /// - The transaction was executed against a _new_ on-chain account and its account ID does not - /// match the ID in the account update. - /// - The transaction was executed against a _new_ on-chain account and its commitment does not - /// match the final state commitment of the account update. + /// - The transaction was executed against a _new_ account with public state and its account ID + /// does not match the ID in the account update. + /// - The transaction was executed against a _new_ account with public state and its commitment + /// does not match the final state commitment of the account update. /// - The transaction was executed against a private account and the account update is _not_ of /// type [`AccountUpdateDetails::Private`]. - /// - The transaction was executed against an on-chain account and the update is of type - /// [`AccountUpdateDetails::Private`]. - /// - The transaction was executed against an _existing_ on-chain account and the update is of - /// type [`AccountUpdateDetails::New`]. - /// - The transaction creates a _new_ on-chain account and the update is of type + /// - The transaction was executed against an account with public state and the update is of + /// type [`AccountUpdateDetails::Private`]. + /// - The transaction was executed against an _existing_ account with public state and the + /// update is of type [`AccountUpdateDetails::New`]. + /// - The transaction creates a _new_ account with public state and the update is of type /// [`AccountUpdateDetails::Delta`]. pub fn build(self) -> Result { let input_notes = diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index eb1b2c74a6..6475fce882 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -84,8 +84,8 @@ impl LocalTransactionProver { .remove_asset(Asset::from(tx_outputs.fee)) .map_err(TransactionProverError::RemoveFeeAssetFromDelta)?; - // If the account is on-chain, add the update details. - let builder = match account.is_onchain() { + // If the account's state is public, add the update details. + let builder = match account.has_public_state() { true => { let account_update_details = if account.is_new() { let mut account = account.clone(); From 269472f179d37f83a5ce1e4a234bc5ca376ed562 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 3 Sep 2025 23:05:03 +0200 Subject: [PATCH 014/133] feat: Use `PartialAccount` in `TransactionInputs` (#1840) --- CHANGELOG.md | 2 + crates/miden-lib/src/transaction/inputs.rs | 6 +- crates/miden-lib/src/transaction/mod.rs | 6 +- crates/miden-objects/src/account/mod.rs | 2 +- crates/miden-objects/src/account/partial.rs | 56 +++++++- .../src/account/storage/partial.rs | 7 + .../src/transaction/executed_tx.rs | 10 +- .../src/transaction/inputs/mod.rs | 30 +++-- crates/miden-objects/src/transaction/mod.rs | 2 +- .../src/transaction/tx_witness.rs | 2 +- .../block/proposed_block_errors.rs | 10 +- .../block/proposed_block_success.rs | 13 +- .../kernel_tests/block/proven_block_error.rs | 2 +- .../src/kernel_tests/tx/test_tx.rs | 4 +- crates/miden-testing/src/mock_chain/chain.rs | 36 +---- .../miden-testing/src/tx_context/builder.rs | 1 + .../miden-testing/src/tx_context/context.rs | 7 +- crates/miden-tx/src/executor/exec_host.rs | 15 ++- crates/miden-tx/src/executor/mod.rs | 9 +- crates/miden-tx/src/host/mod.rs | 11 +- crates/miden-tx/src/prover/mod.rs | 125 ++++++++++++------ crates/miden-tx/src/prover/prover_host.rs | 4 +- 22 files changed, 239 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0ba10ce8e..103bcb5e1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ - [BREAKING] Incremented MSRV to 1.89. - [BREAKING] Remove versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). +- [BREAKING] Replaced `Account` with `PartialAccount` in `TransactionInputs` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). +- [BREAKING] Renamed `Account::init_commitment` to `Account::initial_commitment` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). - [BREAKING] Rename the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). ## 0.11.1 (2025-08-28) diff --git a/crates/miden-lib/src/transaction/inputs.rs b/crates/miden-lib/src/transaction/inputs.rs index 0188c90420..3c3eb24c44 100644 --- a/crates/miden-lib/src/transaction/inputs.rs +++ b/crates/miden-lib/src/transaction/inputs.rs @@ -56,13 +56,13 @@ impl TransactionAdviceInputs { // --- native account injection --------------------------------------- - let native_acc = PartialAccount::from(tx_inputs.account()); - inputs.add_account(&native_acc)?; + let partial_native_acc = tx_inputs.account(); + inputs.add_account(partial_native_acc)?; // if a seed was provided, extend the map appropriately if let Some(seed) = tx_inputs.account_seed() { // ACCOUNT_ID |-> ACCOUNT_SEED - let account_id_key = build_account_id_key(native_acc.id()); + let account_id_key = build_account_id_key(partial_native_acc.id()); inputs.add_map_entry(account_id_key, seed.to_vec()); } diff --git a/crates/miden-lib/src/transaction/mod.rs b/crates/miden-lib/src/transaction/mod.rs index a4c2841780..1e51532876 100644 --- a/crates/miden-lib/src/transaction/mod.rs +++ b/crates/miden-lib/src/transaction/mod.rs @@ -142,7 +142,7 @@ impl TransactionKernel { let stack_inputs = TransactionKernel::build_input_stack( account.id(), - account.init_commitment(), + account.initial_commitment(), tx_inputs.input_notes().commitment(), tx_inputs.block_header().commitment(), tx_inputs.block_header().block_num(), @@ -204,7 +204,7 @@ impl TransactionKernel { /// - INPUT_NOTES_COMMITMENT, see `transaction::api::get_input_notes_commitment`. pub fn build_input_stack( account_id: AccountId, - init_account_commitment: Word, + initial_account_commitment: Word, input_notes_commitment: Word, block_commitment: Word, block_num: BlockNumber, @@ -215,7 +215,7 @@ impl TransactionKernel { inputs.push(account_id.suffix()); inputs.push(account_id.prefix().as_felt()); inputs.extend(input_notes_commitment); - inputs.extend_from_slice(init_account_commitment.as_elements()); + inputs.extend_from_slice(initial_account_commitment.as_elements()); inputs.extend_from_slice(block_commitment.as_elements()); StackInputs::new(inputs) .map_err(|e| e.to_string()) diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index 9df2485cf1..ace7eaaf6a 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -214,7 +214,7 @@ impl Account { /// [crate::EMPTY_WORD] to distinguish new accounts from existing accounts. The actual /// commitment of the initial account state (and the initial state itself), are provided to /// the VM via the advice provider. - pub fn init_commitment(&self) -> Word { + pub fn initial_commitment(&self) -> Word { if self.is_new() { Word::empty() } else { diff --git a/crates/miden-objects/src/account/partial.rs b/crates/miden-objects/src/account/partial.rs index 914fcfd891..0f77155d9b 100644 --- a/crates/miden-objects/src/account/partial.rs +++ b/crates/miden-objects/src/account/partial.rs @@ -1,7 +1,9 @@ -use miden_core::Felt; use miden_core::utils::{Deserializable, Serializable}; +use miden_core::{Felt, ZERO}; use super::{Account, AccountCode, AccountId, PartialStorage}; +use crate::Word; +use crate::account::hash_account; use crate::asset::PartialVault; /// A partial representation of an account. @@ -9,6 +11,8 @@ use crate::asset::PartialVault; /// A partial account is used as inputs to the transaction kernel and contains only the essential /// data needed for verification and transaction processing without requiring the full account /// state. +/// +/// For new accounts, the partial storage must be the full initial account storage. #[derive(Clone, Debug, PartialEq, Eq)] pub struct PartialAccount { /// The ID for the partial account @@ -67,6 +71,56 @@ impl PartialAccount { pub fn vault(&self) -> &PartialVault { &self.partial_vault } + + /// Returns true if the account is new (i.e. its nonce is zero and it hasn't been registered on + /// chain yet). + pub fn is_new(&self) -> bool { + self.nonce == ZERO + } + + /// Returns the commitment of this account. + /// + /// The commitment of an account is computed as: + /// + /// ```text + /// hash(id, nonce, vault_root, storage_commitment, code_commitment). + /// ``` + pub fn commitment(&self) -> Word { + hash_account( + self.id, + self.nonce, + self.vault().root(), + self.storage().commitment(), + self.code().commitment(), + ) + } + + /// Returns the commitment of this account as used for the initial account state commitment in + /// transaction proofs. + /// + /// For existing accounts, this is exactly the same as [Account::commitment()], however, for new + /// accounts this value is set to [`Word::empty`]. This is because when a transaction is + /// executed against a new account, public input for the initial account state is set to + /// [`Word::empty`] to distinguish new accounts from existing accounts. The actual + /// commitment of the initial account state (and the initial state itself), are provided to + /// the VM via the advice provider. + pub fn initial_commitment(&self) -> Word { + if self.is_new() { + Word::empty() + } else { + self.commitment() + } + } + + /// Returns `true` if the full state of the account is public on chain, and `false` otherwise. + pub fn has_public_state(&self) -> bool { + self.id.has_public_state() + } + + /// Consumes self and returns the underlying parts of the partial account. + pub fn into_parts(self) -> (AccountId, Felt, AccountCode, PartialStorage, PartialVault) { + (self.id, self.nonce, self.account_code, self.partial_storage, self.partial_vault) + } } impl From for PartialAccount { diff --git a/crates/miden-objects/src/account/storage/partial.rs b/crates/miden-objects/src/account/storage/partial.rs index c5ba46217d..e2f993a235 100644 --- a/crates/miden-objects/src/account/storage/partial.rs +++ b/crates/miden-objects/src/account/storage/partial.rs @@ -58,6 +58,13 @@ impl PartialStorage { self.commitment } + // TODO: Consider removing once no longer needed so we don't commit to the underlying BTreeMap + // type. + /// Consumes self and returns the underlying parts. + pub fn into_parts(self) -> (Word, AccountStorageHeader, BTreeMap) { + (self.commitment, self.header, self.maps) + } + // TODO: Add from account storage with (slot/[key])? // ITERATORS diff --git a/crates/miden-objects/src/transaction/executed_tx.rs b/crates/miden-objects/src/transaction/executed_tx.rs index 4539755117..2396646363 100644 --- a/crates/miden-objects/src/transaction/executed_tx.rs +++ b/crates/miden-objects/src/transaction/executed_tx.rs @@ -1,7 +1,6 @@ use alloc::vec::Vec; use super::{ - Account, AccountDelta, AccountHeader, AccountId, @@ -17,6 +16,7 @@ use super::{ TransactionOutputs, TransactionWitness, }; +use crate::account::PartialAccount; use crate::asset::FungibleAsset; use crate::block::BlockNumber; use crate::utils::serde::{ @@ -73,7 +73,7 @@ impl ExecutedTransaction { // we create the id from the content, so we cannot construct the // `id` value after construction `Self {..}` without moving let id = TransactionId::new( - tx_inputs.account().init_commitment(), + tx_inputs.account().initial_commitment(), tx_outputs.account.commitment(), tx_inputs.input_notes().commitment(), tx_outputs.output_notes.commitment(), @@ -103,12 +103,12 @@ impl ExecutedTransaction { self.initial_account().id() } - /// Returns the description of the account before the transaction was executed. - pub fn initial_account(&self) -> &Account { + /// Returns the partial state of the account before the transaction was executed. + pub fn initial_account(&self) -> &PartialAccount { self.tx_inputs.account() } - /// Returns description of the account after the transaction was executed. + /// Returns the header of the account state after the transaction was executed. pub fn final_account(&self) -> &AccountHeader { &self.tx_outputs.account } diff --git a/crates/miden-objects/src/transaction/inputs/mod.rs b/crates/miden-objects/src/transaction/inputs/mod.rs index b489b6e898..170681826c 100644 --- a/crates/miden-objects/src/transaction/inputs/mod.rs +++ b/crates/miden-objects/src/transaction/inputs/mod.rs @@ -1,7 +1,7 @@ use core::fmt::Debug; use super::PartialBlockchain; -use crate::account::{Account, AccountId}; +use crate::account::{AccountId, PartialAccount}; use crate::block::BlockHeader; use crate::note::{Note, NoteInclusionProof}; use crate::utils::serde::{ @@ -25,7 +25,7 @@ pub use notes::{InputNote, InputNotes, ToInputNoteCommitments}; /// Contains the data required to execute a transaction. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TransactionInputs { - account: Account, + account: PartialAccount, account_seed: Option, block_header: BlockHeader, block_chain: PartialBlockchain, @@ -42,14 +42,16 @@ impl TransactionInputs { /// - For a new account, account seed is not provided or the provided seed is invalid. /// - For an existing account, account seed was provided. pub fn new( - account: Account, + account: impl Into, account_seed: Option, block_header: BlockHeader, block_chain: PartialBlockchain, input_notes: InputNotes, ) -> Result { + let partial_account: PartialAccount = account.into(); + // validate the seed - validate_account_seed(&account, account_seed)?; + validate_account_seed(&partial_account, account_seed)?; // check the block_chain and block_header are consistent let block_num = block_header.block_num(); @@ -85,7 +87,7 @@ impl TransactionInputs { } Ok(Self { - account, + account: partial_account, account_seed, block_header, block_chain, @@ -96,8 +98,8 @@ impl TransactionInputs { // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns account against which the transaction is to be executed. - pub fn account(&self) -> &Account { + /// Returns the account against which the transaction is executed. + pub fn account(&self) -> &PartialAccount { &self.account } @@ -128,7 +130,13 @@ impl TransactionInputs { /// Consumes these transaction inputs and returns their underlying components. pub fn into_parts( self, - ) -> (Account, Option, BlockHeader, PartialBlockchain, InputNotes) { + ) -> ( + PartialAccount, + Option, + BlockHeader, + PartialBlockchain, + InputNotes, + ) { ( self.account, self.account_seed, @@ -151,12 +159,12 @@ impl Serializable for TransactionInputs { impl Deserializable for TransactionInputs { fn read_from(source: &mut R) -> Result { - let account = Account::read_from(source)?; + let partial_account = PartialAccount::read_from(source)?; let account_seed = source.read()?; let block_header = BlockHeader::read_from(source)?; let block_chain = PartialBlockchain::read_from(source)?; let input_notes = InputNotes::read_from(source)?; - Self::new(account, account_seed, block_header, block_chain, input_notes) + Self::new(partial_account, account_seed, block_header, block_chain, input_notes) .map_err(|err| DeserializationError::InvalidValue(format!("{err}"))) } } @@ -166,7 +174,7 @@ impl Deserializable for TransactionInputs { /// Validates that the provided seed is valid for this account. fn validate_account_seed( - account: &Account, + account: &PartialAccount, account_seed: Option, ) -> Result<(), TransactionInputError> { match (account.is_new(), account_seed) { diff --git a/crates/miden-objects/src/transaction/mod.rs b/crates/miden-objects/src/transaction/mod.rs index a63f0e4587..da9531d128 100644 --- a/crates/miden-objects/src/transaction/mod.rs +++ b/crates/miden-objects/src/transaction/mod.rs @@ -1,4 +1,4 @@ -use super::account::{Account, AccountDelta, AccountHeader, AccountId}; +use super::account::{AccountDelta, AccountHeader, AccountId}; use super::block::BlockHeader; use super::note::{NoteId, Nullifier}; use super::vm::AdviceInputs; diff --git a/crates/miden-objects/src/transaction/tx_witness.rs b/crates/miden-objects/src/transaction/tx_witness.rs index 1a7de4fab8..1bfd070ac6 100644 --- a/crates/miden-objects/src/transaction/tx_witness.rs +++ b/crates/miden-objects/src/transaction/tx_witness.rs @@ -97,7 +97,7 @@ mod tests { ); let tx_inputs = TransactionInputs::new( - account.clone(), + account, None, block_header.clone(), partial_blockchain.clone(), diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs index cf7739bf1f..1a5c1df88d 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs @@ -538,7 +538,7 @@ fn proposed_block_fails_on_conflicting_transactions_updating_same_account() -> a first_batch_id, second_batch_id } if account_id == account1.id() && - initial_state_commitment == account1.init_commitment() && + initial_state_commitment == account1.initial_commitment() && first_batch_id == batch0.id() && second_batch_id == batch1.id() ); @@ -582,7 +582,7 @@ fn proposed_block_fails_on_inconsistent_account_state_transition() -> anyhow::Re ); let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); + let mut account1 = accounts.remove(&1).unwrap(); let note0 = generate_tracked_note_with_asset(&mut chain, account0.id(), account1.id(), asset); let note1 = generate_tracked_note_with_asset(&mut chain, account0.id(), account1.id(), asset); @@ -595,13 +595,15 @@ fn proposed_block_fails_on_inconsistent_account_state_transition() -> anyhow::Re let executed_tx0 = generate_executed_tx_with_authenticated_notes(&chain, account1.clone(), &[note0.id()]); + account1.apply_delta(executed_tx0.account_delta())?; // Builds a tx on top of the account state from tx0. let executed_tx1 = - generate_executed_tx_with_authenticated_notes(&chain, executed_tx0.clone(), &[note1.id()]); + generate_executed_tx_with_authenticated_notes(&chain, account1.clone(), &[note1.id()]); + account1.apply_delta(executed_tx1.account_delta())?; // Builds a tx on top of the account state from tx1. let executed_tx2 = - generate_executed_tx_with_authenticated_notes(&chain, executed_tx1.clone(), &[note2.id()]); + generate_executed_tx_with_authenticated_notes(&chain, account1.clone(), &[note2.id()]); // We will only include tx0 and tx2 and leave out tx1, which will trigger the error condition // that there is no transition from tx0 -> tx2. diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs index 69defbc89c..6eafa032ae 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs @@ -119,7 +119,7 @@ fn proposed_block_aggregates_account_state_transition() -> anyhow::Result<()> { ); let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); + let mut account1 = accounts.remove(&1).unwrap(); let note0 = generate_tracked_note_with_asset(&mut chain, account0.id(), account1.id(), asset); let note1 = generate_tracked_note_with_asset(&mut chain, account0.id(), account1.id(), asset); @@ -132,11 +132,13 @@ fn proposed_block_aggregates_account_state_transition() -> anyhow::Result<()> { let executed_tx0 = generate_executed_tx_with_authenticated_notes(&chain, account1.id(), &[note0.id()]); + account1.apply_delta(executed_tx0.account_delta())?; let executed_tx1 = - generate_executed_tx_with_authenticated_notes(&chain, executed_tx0.clone(), &[note1.id()]); + generate_executed_tx_with_authenticated_notes(&chain, account1.clone(), &[note1.id()]); + account1.apply_delta(executed_tx1.account_delta())?; let executed_tx2 = - generate_executed_tx_with_authenticated_notes(&chain, executed_tx1.clone(), &[note2.id()]); + generate_executed_tx_with_authenticated_notes(&chain, account1.clone(), &[note2.id()]); let [tx0, tx1, tx2] = [executed_tx0, executed_tx1, executed_tx2] .into_iter() @@ -270,7 +272,7 @@ fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow: let mut builder = MockChain::builder(); - let account0 = builder.add_account_from_builder( + let mut account0 = builder.add_account_from_builder( Auth::Conditional, account_builder, AccountState::Exists, @@ -279,7 +281,8 @@ fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow: let mut chain = builder.build()?; let noop_tx = generate_conditional_tx(&mut chain, account0.id(), false); - let state_updating_tx = generate_conditional_tx(&mut chain, noop_tx.clone(), true); + account0.apply_delta(noop_tx.account_delta())?; + let state_updating_tx = generate_conditional_tx(&mut chain, account0.clone(), true); // sanity check: NOOP transaction's init and final commitment should be the same. assert_eq!(noop_tx.initial_account().commitment(), noop_tx.final_account().commitment()); diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs index 6525384022..1ca01c614a 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs @@ -286,7 +286,7 @@ fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> a existing_id.suffix(), "test should work if suffixes are different, so we want to ensure it" ); - assert_eq!(account.init_commitment(), Word::empty()); + assert_eq!(account.initial_commitment(), Word::empty()); let existing_account = Account::mock(existing_id.into(), auth_component); builder.add_account(existing_account.clone())?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 6451366021..668a1d5bff 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -948,7 +948,7 @@ async fn advice_inputs_from_transaction_witness_are_sufficient_to_reexecute_tran .unwrap(); TransactionExecutorHost::<'_, '_, _, UnreachableAuth>::new( - &tx_inputs.account().into(), + tx_inputs.account(), tx_inputs.input_notes().clone(), mast_store.as_ref(), scripts_mast_store, @@ -976,7 +976,7 @@ async fn advice_inputs_from_transaction_witness_are_sufficient_to_reexecute_tran ..Default::default() }; - let (_, output_notes, _signatures, _tx_progress) = host.into_parts(); + let (_, _input_notes, output_notes, _signatures, _tx_progress) = host.into_parts(); let tx_outputs = TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes) .unwrap(); diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 9a04a131ed..cf3b0cabd2 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -1,4 +1,3 @@ -use alloc::boxed::Box; use alloc::collections::{BTreeMap, BTreeSet}; use alloc::string::ToString; use alloc::vec::Vec; @@ -579,17 +578,13 @@ impl MockChain { /// from the chain for the public account identified by the ID. /// - [`TxContextInput::Account`]: Initialize the builder with [`TransactionInputs`] where the /// account is passed as-is to the inputs. - /// - [`TxContextInput::ExecutedTransaction`]: Initialize the builder with [`TransactionInputs`] - /// where the account passed to the inputs is the final account of the executed transaction. - /// This is the initial account of the transaction with the account delta applied. /// /// In all cases, if the chain contains a seed or authenticator for the account, they are added /// to the builder. /// - /// [`TxContextInput::Account`] and [`TxContextInput::ExecutedTransaction`] can be used to build - /// a chain of transactions against the same account that build on top of each other. For - /// example, transaction A modifies an account from state 0 to 1, and transaction B modifies - /// it from state 1 to 2. + /// [`TxContextInput::Account`] can be used to build a chain of transactions against the same + /// account that build on top of each other. For example, transaction A modifies an account + /// from state 0 to 1, and transaction B modifies it from state 1 to 2. pub fn build_tx_context_at( &self, reference_block: impl Into, @@ -627,14 +622,6 @@ impl MockChain { .clone() }, TxContextInput::Account(account) => account, - TxContextInput::ExecutedTransaction(executed_transaction) => { - let mut initial_account = executed_transaction.initial_account().clone(); - initial_account - .apply_delta(executed_transaction.account_delta()) - .context("could not apply delta from previous transaction")?; - - initial_account - }, }; let tx_inputs = self @@ -905,10 +892,7 @@ impl MockChain { pub fn add_pending_executed_transaction( &mut self, transaction: &ExecutedTransaction, - ) -> anyhow::Result { - let mut account = transaction.initial_account().clone(); - account.apply_delta(transaction.account_delta())?; - + ) -> anyhow::Result<()> { // Transform the executed tx into a proven tx with a dummy proof. let proven_tx = LocalTransactionProver::default() .prove_dummy(transaction.clone()) @@ -916,7 +900,7 @@ impl MockChain { self.pending_transactions.push(proven_tx); - Ok(account) + Ok(()) } /// Adds the given [`ProvenTransaction`] to the list of pending transactions. @@ -1332,7 +1316,6 @@ impl Deserializable for AccountCredentials { pub enum TxContextInput { AccountId(AccountId), Account(Account), - ExecutedTransaction(Box), } impl TxContextInput { @@ -1341,9 +1324,6 @@ impl TxContextInput { match self { TxContextInput::AccountId(account_id) => *account_id, TxContextInput::Account(account) => account.id(), - TxContextInput::ExecutedTransaction(executed_transaction) => { - executed_transaction.account_id() - }, } } } @@ -1360,12 +1340,6 @@ impl From for TxContextInput { } } -impl From for TxContextInput { - fn from(tx: ExecutedTransaction) -> Self { - Self::ExecutedTransaction(Box::new(tx)) - } -} - // TESTS // ================================================================================================ diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index d8e429e648..63d641d8cd 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -301,6 +301,7 @@ impl TransactionContextBuilder { }; Ok(TransactionContext { + account: self.account, expected_output_notes: self.expected_output_notes, tx_args, tx_inputs, diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index a40fd183bd..ac92e91a51 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -49,6 +49,7 @@ use crate::tx_context::builder::MockAuthenticator; /// It implements [`DataStore`], so transactions may be executed with /// [TransactionExecutor](miden_tx::TransactionExecutor) pub struct TransactionContext { + pub(super) account: Account, pub(super) expected_output_notes: Vec, pub(super) tx_args: TransactionArgs, pub(super) tx_inputs: TransactionInputs, @@ -154,7 +155,7 @@ impl TransactionContext { } pub fn account(&self) -> &Account { - self.tx_inputs.account() + &self.account } pub fn expected_output_notes(&self) -> &[Note] { @@ -196,8 +197,8 @@ impl DataStore for TransactionContext { Result<(Account, Option, BlockHeader, PartialBlockchain), DataStoreError>, > { assert_eq!(account_id, self.account().id()); - let (account, seed, header, mmr, _) = self.tx_inputs.clone().into_parts(); - async move { Ok((account, seed, header, mmr)) } + let (_partial_account, seed, header, mmr, _) = self.tx_inputs.clone().into_parts(); + async move { Ok((self.account.clone(), seed, header, mmr)) } } } diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 68caa653ec..45e3616734 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -214,12 +214,19 @@ where /// Consumes `self` and returns the account delta, output notes, generated signatures and /// transaction progress. + #[allow(clippy::type_complexity)] pub fn into_parts( self, - ) -> (AccountDelta, Vec, BTreeMap>, TransactionProgress) { - let (account_delta, output_notes, tx_progress) = self.base_host.into_parts(); - - (account_delta, output_notes, self.generated_signatures, tx_progress) + ) -> ( + AccountDelta, + InputNotes, + Vec, + BTreeMap>, + TransactionProgress, + ) { + let (account_delta, input_notes, output_notes, tx_progress) = self.base_host.into_parts(); + + (account_delta, input_notes, output_notes, self.generated_signatures, tx_progress) } } diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index 04917584da..c327d7e095 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -4,7 +4,7 @@ use alloc::vec::Vec; use miden_lib::errors::TransactionKernelError; use miden_lib::transaction::TransactionKernel; -use miden_objects::account::AccountId; +use miden_objects::account::{AccountId, PartialAccount}; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::debuginfo::SourceManagerSync; use miden_objects::asset::Asset; @@ -272,7 +272,8 @@ where validate_account_inputs(tx_args, &ref_block)?; - let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes) + let partial_account = PartialAccount::from(account); + let tx_inputs = TransactionInputs::new(partial_account, seed, ref_block, mmr, notes) .map_err(TransactionExecutorError::InvalidTransactionInputs)?; let (stack_inputs, advice_inputs) = @@ -298,7 +299,7 @@ where .map_err(TransactionExecutorError::TransactionHostCreationFailed)?; let host = TransactionExecutorHost::new( - &tx_inputs.account().into(), + tx_inputs.account(), input_notes.clone(), self.data_store, script_mast_store, @@ -327,7 +328,7 @@ fn build_executed_transaction Result { // Note that the account delta does not contain the removed transaction fee, so it is the // "pre-fee" delta of the transaction. - let (pre_fee_account_delta, output_notes, generated_signatures, tx_progress) = + let (pre_fee_account_delta, _input_notes, output_notes, generated_signatures, tx_progress) = host.into_parts(); let tx_outputs = diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index e1fdbd7787..f52d32e817 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -155,10 +155,17 @@ where } /// Consumes `self` and returns the account delta, output notes and transaction progress. - pub fn into_parts(self) -> (AccountDelta, Vec, TransactionProgress) { + pub fn into_parts( + self, + ) -> (AccountDelta, InputNotes, Vec, TransactionProgress) { let output_notes = self.output_notes.into_values().map(|builder| builder.build()).collect(); - (self.account_delta.into_delta(), output_notes, self.tx_progress) + ( + self.account_delta.into_delta(), + self.input_notes, + output_notes, + self.tx_progress, + ) } // EVENT HANDLERS diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index 6475fce882..ed355d34f4 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -3,9 +3,20 @@ use alloc::vec::Vec; use miden_lib::transaction::TransactionKernel; use miden_objects::account::delta::AccountUpdateDetails; -use miden_objects::account::{Account, AccountDelta}; -use miden_objects::asset::Asset; +use miden_objects::account::{ + Account, + AccountDelta, + AccountStorage, + PartialAccount, + PartialStorage, + PartialStorageMap, + StorageMap, + StorageSlot, + StorageSlotType, +}; +use miden_objects::asset::{Asset, AssetVault}; use miden_objects::block::BlockNumber; +use miden_objects::crypto::merkle::SmtLeaf; use miden_objects::transaction::{ ExecutedTransaction, InputNote, @@ -51,7 +62,7 @@ impl LocalTransactionProver { input_notes: &InputNotes, tx_outputs: TransactionOutputs, pre_fee_account_delta: AccountDelta, - account: &Account, + account: PartialAccount, ref_block_num: BlockNumber, ref_block_commitment: Word, proof: ExecutionProof, @@ -65,7 +76,7 @@ impl LocalTransactionProver { let builder = ProvenTransactionBuilder::new( account.id(), - account.init_commitment(), + account.initial_commitment(), tx_outputs.account.commitment(), pre_fee_delta_commitment, ref_block_num, @@ -84,11 +95,10 @@ impl LocalTransactionProver { .remove_asset(Asset::from(tx_outputs.fee)) .map_err(TransactionProverError::RemoveFeeAssetFromDelta)?; - // If the account's state is public, add the update details. let builder = match account.has_public_state() { true => { let account_update_details = if account.is_new() { - let mut account = account.clone(); + let mut account = partial_account_to_full(account); account .apply_delta(&post_fee_account_delta) .map_err(TransactionProverError::AccountDeltaApplyFailed)?; @@ -112,38 +122,29 @@ impl LocalTransactionProver { ) -> Result { let TransactionWitness { tx_inputs, tx_args, advice_witness } = tx_witness; - let account = tx_inputs.account(); - let input_notes = tx_inputs.input_notes(); - let ref_block_num = tx_inputs.block_header().block_num(); - let ref_block_commitment = tx_inputs.block_header().commitment(); - let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_witness)) .map_err(TransactionProverError::ConflictingAdviceMapEntry)?; - self.mast_store.load_account_code(account.code()); + self.mast_store.load_account_code(tx_inputs.account().code()); let script_mast_store = ScriptMastForestStore::new( tx_args.tx_script(), - input_notes.iter().map(|n| n.note().script()), + tx_inputs.input_notes().iter().map(|n| n.note().script()), ); - let mut host = { - let acct_procedure_index_map = AccountProcedureIndexMap::from_transaction_params( - &tx_inputs, - &tx_args, - &advice_inputs, - ) - .map_err(TransactionProverError::TransactionHostCreationFailed)?; - - TransactionProverHost::new( - &account.into(), - input_notes.clone(), - self.mast_store.as_ref(), - script_mast_store, - acct_procedure_index_map, - ) - }; + let acct_procedure_index_map = + AccountProcedureIndexMap::from_transaction_params(&tx_inputs, &tx_args, &advice_inputs) + .map_err(TransactionProverError::TransactionHostCreationFailed)?; + + let (partial_account, _, ref_block, _, input_notes) = tx_inputs.into_parts(); + let mut host = TransactionProverHost::new( + &partial_account, + input_notes, + self.mast_store.as_ref(), + script_mast_store, + acct_procedure_index_map, + ); let advice_inputs = advice_inputs.into_advice_inputs(); @@ -159,18 +160,18 @@ impl LocalTransactionProver { // Extract transaction outputs and process transaction data. // Note that the account delta does not contain the removed transaction fee, so it is the // "pre-fee" delta of the transaction. - let (pre_fee_account_delta, output_notes, _tx_progress) = host.into_parts(); + let (pre_fee_account_delta, input_notes, output_notes, _tx_progress) = host.into_parts(); let tx_outputs = TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes) .map_err(TransactionProverError::TransactionOutputConstructionFailed)?; self.build_proven_transaction( - input_notes, + &input_notes, tx_outputs, pre_fee_account_delta, - account, - ref_block_num, - ref_block_commitment, + partial_account, + ref_block.block_num(), + ref_block.commitment(), proof, ) } @@ -185,6 +186,52 @@ impl Default for LocalTransactionProver { } } +fn partial_account_to_full(partial_account: PartialAccount) -> Account { + let (id, nonce, code, partial_storage, partial_vault) = partial_account.into_parts(); + + // For new accounts, the partial storage must represent the full initial account + // storage. + let storage = partial_storage_to_full(partial_storage); + + // The vault of a new account should be empty. + debug_assert_eq!(partial_vault.leaves().count(), 0); + let vault = AssetVault::default(); + + Account::from_parts(id, vault, storage, code, nonce) +} + +fn partial_storage_to_full(partial_storage: PartialStorage) -> AccountStorage { + let (_, header, mut maps) = partial_storage.into_parts(); + let mut storage_slots = Vec::new(); + for (slot_type, slot_value) in header.slots() { + match slot_type { + StorageSlotType::Value => { + storage_slots.push(StorageSlot::Value(*slot_value)); + }, + StorageSlotType::Map => { + let storage_map = maps + .remove(slot_value) + .map(partial_storage_map_to_storage_map) + .expect("partial storage map should be present in partial storage"); + storage_slots.push(StorageSlot::Map(storage_map)); + }, + } + } + + AccountStorage::new(storage_slots) + .expect("partial storage should not contain more than max allowed storage slots") +} + +fn partial_storage_map_to_storage_map(partial_storage_map: PartialStorageMap) -> StorageMap { + let mut storage_map = StorageMap::new(); + for (key, value) in + partial_storage_map.leaves().map(|(_, leaf)| leaf).flat_map(SmtLeaf::entries) + { + storage_map.insert(*key, *value); + } + storage_map +} + #[cfg(any(feature = "testing", test))] impl LocalTransactionProver { pub fn prove_dummy( @@ -193,13 +240,15 @@ impl LocalTransactionProver { ) -> Result { let (account_delta, tx_outputs, tx_witness, _) = executed_tx.into_parts(); + let (partial_account, _, ref_block, _, input_notes) = tx_witness.tx_inputs.into_parts(); + self.build_proven_transaction( - tx_witness.tx_inputs.input_notes(), + &input_notes, tx_outputs, account_delta, - tx_witness.tx_inputs.account(), - tx_witness.tx_inputs.block_header().block_num(), - tx_witness.tx_inputs.block_header().commitment(), + partial_account, + ref_block.block_num(), + ref_block.commitment(), ExecutionProof::new_dummy(), ) } diff --git a/crates/miden-tx/src/prover/prover_host.rs b/crates/miden-tx/src/prover/prover_host.rs index 2204cd3884..c3c2accda0 100644 --- a/crates/miden-tx/src/prover/prover_host.rs +++ b/crates/miden-tx/src/prover/prover_host.rs @@ -72,7 +72,9 @@ where } /// Consumes `self` and returns the account delta, output notes and transaction progress. - pub fn into_parts(self) -> (AccountDelta, Vec, TransactionProgress) { + pub fn into_parts( + self, + ) -> (AccountDelta, InputNotes, Vec, TransactionProgress) { self.base_host.into_parts() } } From 82c7ab82573ad5d0bcbfc15524dd4fb5b37faa5c Mon Sep 17 00:00:00 2001 From: Serge Radinovich <47865535+sergerad@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:52:05 +1200 Subject: [PATCH 015/133] fix: Miscellaneous Note Consumption Checker clean up (#1808) --- bin/bench-note-checker/benches/benchmarks.rs | 7 +- bin/bench-note-checker/src/lib.rs | 12 +- .../src/transaction/inputs/notes.rs | 6 + .../kernel_tests/tx/test_account_interface.rs | 198 ++++++++++++++---- crates/miden-tx/src/errors/mod.rs | 32 ++- crates/miden-tx/src/executor/mod.rs | 7 +- crates/miden-tx/src/executor/notes_checker.rs | 119 ++++++----- crates/miden-tx/src/lib.rs | 1 + 8 files changed, 276 insertions(+), 106 deletions(-) diff --git a/bin/bench-note-checker/benches/benchmarks.rs b/bin/bench-note-checker/benches/benchmarks.rs index 754f368df8..801de09028 100644 --- a/bin/bench-note-checker/benches/benchmarks.rs +++ b/bin/bench-note-checker/benches/benchmarks.rs @@ -4,6 +4,7 @@ use std::time::Duration; use bench_note_checker::benchmark_names::{BENCH_GROUP, BENCH_MIXED_NOTES}; use bench_note_checker::{MixedNotesConfig, run_mixed_notes_check, setup_mixed_notes_benchmark}; use criterion::{Criterion, SamplingMode, criterion_group, criterion_main}; +use miden_tx::MAX_NUM_CHECKER_NOTES; fn note_checker_benchmarks(c: &mut Criterion) { let mut group = c.benchmark_group(BENCH_GROUP); @@ -14,12 +15,12 @@ fn note_checker_benchmarks(c: &mut Criterion) { .warm_up_time(Duration::from_millis(500)) .measurement_time(Duration::from_secs(10)); - // Benchmark with different numbers of failing notes (staying under 1024 total note limit). - for failing_count in [1, 10, 100, 1000] { + // Benchmark with different numbers of failing notes. + for failing_count in [1, 10, MAX_NUM_CHECKER_NOTES] { group.bench_function(format!("{BENCH_MIXED_NOTES}_{failing_count}_failing"), |b| { let setup = setup_mixed_notes_benchmark(MixedNotesConfig { failing_note_count: failing_count }) - .expect("Failed to set up mixed notes benchmark"); + .expect("failed to set up mixed notes benchmark"); b.iter(|| { let runtime = diff --git a/bin/bench-note-checker/src/lib.rs b/bin/bench-note-checker/src/lib.rs index 9fffee1c31..36761b03e6 100644 --- a/bin/bench-note-checker/src/lib.rs +++ b/bin/bench-note-checker/src/lib.rs @@ -8,6 +8,7 @@ use miden_objects::testing::account_id::{ ACCOUNT_ID_SENDER, }; use miden_testing::{Auth, MockChain, TxContextInput}; +use miden_tx::auth::UnreachableAuth; use miden_tx::{NoteConsumptionChecker, TransactionExecutor}; use serde::{Deserialize, Serialize}; @@ -64,7 +65,7 @@ pub struct MixedNotesSetup { pub fn setup_mixed_notes_benchmark(config: MixedNotesConfig) -> anyhow::Result { // Create a mock chain with an account. let mut builder = MockChain::builder(); - let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let account = builder.add_existing_wallet(Auth::IncrNonce)?; let target_account_id = account.id(); // Create the first successful note (P2ID note that the account can consume). @@ -87,7 +88,6 @@ pub fn setup_mixed_notes_benchmark(config: MixedNotesConfig) -> anyhow::Result anyhow::Result<() .build_tx_context(TxContextInput::AccountId(setup.target_account_id), &[], &setup.notes)? .build()?; - let input_notes = tx_context.input_notes().clone(); let block_ref = tx_context.tx_inputs().block_header().block_num(); let tx_args = tx_context.tx_args().clone(); // Create executor and checker. - let executor = TransactionExecutor::new(&tx_context) - .with_authenticator(tx_context.authenticator().unwrap()); + let executor = TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context); let checker = NoteConsumptionChecker::new(&executor); let result = checker - .check_notes_consumability(setup.target_account_id, block_ref, input_notes, tx_args) + .check_notes_consumability(setup.target_account_id, block_ref, setup.notes.clone(), tx_args) .await?; // Validate that we got the expected number of successful notes. assert_eq!( - result.successful.len(), setup.expected_successful_count, + result.successful.len(), "Expected {} successful notes, got {}", setup.expected_successful_count, result.successful.len() diff --git a/crates/miden-objects/src/transaction/inputs/notes.rs b/crates/miden-objects/src/transaction/inputs/notes.rs index 9655abc3cc..35663c12ca 100644 --- a/crates/miden-objects/src/transaction/inputs/notes.rs +++ b/crates/miden-objects/src/transaction/inputs/notes.rs @@ -292,6 +292,12 @@ impl InputNote { } } +impl From> for InputNotes { + fn from(notes: Vec) -> Self { + Self::new_unchecked(notes.into_iter().map(InputNote::unauthenticated).collect::>()) + } +} + impl ToInputNoteCommitments for InputNote { fn nullifier(&self) -> Nullifier { self.note().nullifier() diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index e863328e0a..a80e458703 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -7,13 +7,14 @@ use miden_lib::testing::note::NoteBuilder; use miden_lib::transaction::TransactionKernel; use miden_objects::Word; use miden_objects::account::{Account, AccountId}; -use miden_objects::asset::FungibleAsset; +use miden_objects::asset::{Asset, FungibleAsset}; use miden_objects::note::{Note, NoteType}; use miden_objects::testing::account_id::{ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_SENDER, }; +use miden_objects::transaction::OutputNote; use miden_processor::ExecutionError; use miden_processor::crypto::RpoRandomCoin; use miden_tx::auth::UnreachableAuth; @@ -32,7 +33,6 @@ use crate::{Auth, MockChain, TransactionContextBuilder, TxContextInput}; #[tokio::test] async fn check_note_consumability_well_known_notes_success() -> anyhow::Result<()> { - let (_, authenticator) = Auth::BasicAuth.build_component(); let p2id_note = create_p2id_note( ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(), ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE.try_into().unwrap(), @@ -56,24 +56,21 @@ async fn check_note_consumability_well_known_notes_success() -> anyhow::Result<( let notes = vec![p2ide_note, p2id_note]; let tx_context = TransactionContextBuilder::with_existing_mock_account() .extend_input_notes(notes.clone()) - .authenticator(authenticator) .build()?; - let input_notes = tx_context.input_notes().clone(); let target_account_id = tx_context.account().id(); let block_ref = tx_context.tx_inputs().block_header().block_num(); let tx_args = tx_context.tx_args().clone(); - let executor = TransactionExecutor::new(&tx_context) - .with_authenticator(tx_context.authenticator().unwrap()) - .with_tracing(); + let executor = + TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context).with_tracing(); let notes_checker = NoteConsumptionChecker::new(&executor); - let execution_check_result = notes_checker - .check_notes_consumability(target_account_id, block_ref, input_notes, tx_args) + let consumption_info = notes_checker + .check_notes_consumability(target_account_id, block_ref, notes.clone(), tx_args) .await?; - assert_matches!(execution_check_result, NoteConsumptionInfo { successful, failed, .. } => { + assert_matches!(consumption_info, NoteConsumptionInfo { successful, failed, .. } => { assert_eq!(successful.len(), notes.len()); successful.iter().zip(notes.iter()).for_each(|(success, note)| { assert_eq!(success, note); @@ -85,7 +82,6 @@ async fn check_note_consumability_well_known_notes_success() -> anyhow::Result<( } #[rstest::rstest] -#[case::empty(vec![])] #[case::one(vec![create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(100)])])] #[tokio::test] async fn check_note_consumability_custom_notes_success( @@ -101,7 +97,6 @@ async fn check_note_consumability_custom_notes_success( .build()? }; - let input_notes = tx_context.input_notes().clone(); let account_id = tx_context.account().id(); let block_ref = tx_context.tx_inputs().block_header().block_num(); let tx_args = tx_context.tx_args().clone(); @@ -111,11 +106,11 @@ async fn check_note_consumability_custom_notes_success( .with_tracing(); let notes_checker = NoteConsumptionChecker::new(&executor); - let execution_check_result = notes_checker - .check_notes_consumability(account_id, block_ref, input_notes, tx_args) + let consumption_info = notes_checker + .check_notes_consumability(account_id, block_ref, notes.clone(), tx_args) .await?; - assert_matches!(execution_check_result, NoteConsumptionInfo { successful, failed, .. }=> { + assert_matches!(consumption_info, NoteConsumptionInfo { successful, failed, .. }=> { if notes.is_empty() { assert!(successful.is_empty()); assert!(failed.is_empty()); @@ -129,7 +124,7 @@ async fn check_note_consumability_custom_notes_success( #[tokio::test] async fn check_note_consumability_partial_success() -> anyhow::Result<()> { let mut builder = MockChain::builder(); - let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let account = builder.add_existing_wallet(Auth::IncrNonce)?; let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); @@ -171,46 +166,45 @@ async fn check_note_consumability_partial_success() -> anyhow::Result<()> { )?; let mock_chain = builder.build()?; + let notes = vec![ + successful_note_2.clone(), + successful_note_1.clone(), + failing_note_2.clone(), + failing_note_1.clone(), + successful_note_3.clone(), + ]; let tx_context = mock_chain - .build_tx_context( - TxContextInput::Account(account), - &[], - &[ - successful_note_2.clone(), - successful_note_1.clone(), - failing_note_2.clone(), - failing_note_1.clone(), - successful_note_3.clone(), - ], - )? + .build_tx_context(TxContextInput::Account(account), &[], ¬es)? .build()?; - let input_notes = tx_context.input_notes().clone(); let account_id = tx_context.account().id(); let block_ref = tx_context.tx_inputs().block_header().block_num(); let tx_args = tx_context.tx_args().clone(); - let executor = TransactionExecutor::new(&tx_context) - .with_authenticator(tx_context.authenticator().unwrap()); + let executor = + TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context).with_tracing(); let notes_checker = NoteConsumptionChecker::new(&executor); - let execution_check_result = notes_checker - .check_notes_consumability(account_id, block_ref, input_notes, tx_args) + let consumption_info = notes_checker + .check_notes_consumability(account_id, block_ref, notes, tx_args) .await?; assert_matches!( - execution_check_result, + consumption_info, NoteConsumptionInfo { successful, failed } => { + assert_eq!(failed.len(), 2); + assert_eq!(successful.len(), 3); + // First failing note. assert_matches!( failed.first().expect("first failed notes should exist"), FailedNote { note, - error: Some(TransactionExecutorError::TransactionProgramExecutionFailed( - ExecutionError::DivideByZero { .. })) + error: TransactionExecutorError::TransactionProgramExecutionFailed( + ExecutionError::DivideByZero { .. }) } => { assert_eq!( note.id(), @@ -223,8 +217,8 @@ async fn check_note_consumability_partial_success() -> anyhow::Result<()> { failed.get(1).expect("second failed note should exist"), FailedNote { note, - error: Some(TransactionExecutorError::TransactionProgramExecutionFailed( - ExecutionError::DivideByZero { .. })) + error: TransactionExecutorError::TransactionProgramExecutionFailed( + ExecutionError::DivideByZero { .. }) } => { assert_eq!( note.id(), @@ -245,6 +239,8 @@ async fn check_note_consumability_partial_success() -> anyhow::Result<()> { #[tokio::test] async fn check_note_consumability_epilogue_failure() -> anyhow::Result<()> { let mut builder = MockChain::builder(); + + // Use basic auth which will cause epilogue failure when paired up with unreachable auth. let account = builder.add_existing_wallet(Auth::BasicAuth)?; let successful_note = builder.add_p2id_note( @@ -255,26 +251,26 @@ async fn check_note_consumability_epilogue_failure() -> anyhow::Result<()> { )?; let mock_chain = builder.build()?; + let notes = vec![successful_note.clone()]; let tx_context = mock_chain - .build_tx_context(TxContextInput::Account(account), &[], &[successful_note])? + .build_tx_context(TxContextInput::Account(account), &[], ¬es)? .build()?; - let input_notes = tx_context.input_notes().clone(); let account_id = tx_context.account().id(); let block_ref = tx_context.tx_inputs().block_header().block_num(); let tx_args = tx_context.tx_args().clone(); - // Use an auth that fails in order to force an epilogue failure. + // Use an auth that fails in order to force an epilogue failure when paired up with basic auth. let executor = TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context).with_tracing(); let notes_checker = NoteConsumptionChecker::new(&executor); - let execution_check_result = notes_checker - .check_notes_consumability(account_id, block_ref, input_notes, tx_args) + let consumption_info = notes_checker + .check_notes_consumability(account_id, block_ref, notes, tx_args) .await?; assert_matches!( - execution_check_result, + consumption_info, NoteConsumptionInfo { successful, failed @@ -285,3 +281,119 @@ async fn check_note_consumability_epilogue_failure() -> anyhow::Result<()> { ); Ok(()) } + +#[tokio::test] +async fn check_note_consumability_epilogue_failure_with_new_combination() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let account = builder.add_existing_wallet(Auth::IncrNonce)?; + + // Prepare set of notes expected to succeed despite the fact that they will be grouped with + // notes that cause epilogue failure and transaction execution failure. The epilogue failure + // in particular will cause the note checker to execute + // `find_largest_executable_combination()` which this test is mainly concerned about. + let successful_note_1 = builder.add_p2id_note( + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(10)], + NoteType::Public, + )?; + let successful_note_2 = builder.add_p2id_note( + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(145)], + NoteType::Public, + )?; + let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); + let successful_note_3 = NoteBuilder::new( + sender, + ChaCha20Rng::from_seed(ChaCha20Rng::from_seed([0_u8; 32]).random()), + ) + .code("begin push.1 drop push.1 div end") + .dynamically_linked_libraries([TransactionKernel::library()]) + .build()?; + let failing_note_1 = NoteBuilder::new( + sender, + ChaCha20Rng::from_seed(ChaCha20Rng::from_seed([0_u8; 32]).random()), + ) + .code("begin push.1 drop push.0 div end") + .dynamically_linked_libraries([TransactionKernel::library()]) + .build()?; + + // Create a note that causes epilogue failure. Adds assets to the transaction without moving + // them anywhere which causes an "asset imbalance" that violates the asset preservation rules. + let note_asset = FungibleAsset::mock(700).unwrap_fungible(); + let fail_epilogue_note = NoteBuilder::new(account.id(), &mut rand::rng()) + .add_assets([Asset::from(note_asset)]) + .build()?; + builder.add_note(OutputNote::Full(fail_epilogue_note.clone())); + + let mock_chain = builder.build()?; + let notes = vec![ + successful_note_1.clone(), + fail_epilogue_note.clone(), + successful_note_2.clone(), + failing_note_1.clone(), + successful_note_3.clone(), + ]; + let tx_context = mock_chain + .build_tx_context(TxContextInput::Account(account), &[], ¬es)? + .build()?; + + let account_id = tx_context.account().id(); + let block_ref = tx_context.tx_inputs().block_header().block_num(); + let tx_args = tx_context.tx_args().clone(); + + let executor = + TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context).with_tracing(); + let notes_checker = NoteConsumptionChecker::new(&executor); + + let consumption_info = notes_checker + .check_notes_consumability(account_id, block_ref, notes, tx_args) + .await?; + + assert_matches!( + consumption_info, + NoteConsumptionInfo { + successful, + failed + } => { + assert_eq!(failed.len(), 2); + assert_eq!(successful.len(), 3); + + // First failing note should be the note that does not cause epilogue failure. + assert_matches!( + failed.first().expect("first failed notes should exist"), + FailedNote { + note, + error: TransactionExecutorError::TransactionProgramExecutionFailed( + ExecutionError::DivideByZero { .. }) + } => { + assert_eq!( + note.id(), + failing_note_1.id(), + ); + } + ); + // Second failing note should be the note that causes epilogue failure. + assert_matches!( + failed.get(1).expect("second failed note should exist"), + FailedNote { + note, + error: TransactionExecutorError::TransactionProgramExecutionFailed( + ExecutionError::FailedAssertion { .. }) + } => { + assert_eq!( + note.id(), + fail_epilogue_note.id(), + ); + } + ); + // Successful notes. + assert_eq!( + [successful[0].id(), successful[1].id(), successful[2].id()], + [successful_note_1.id(), successful_note_2.id(), successful_note_3.id()], + ); + } + ); + Ok(()) +} diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index 480002eca6..442adfd27d 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -27,19 +27,43 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum NoteCheckerError { + #[error("invalid input note count {0} is out of range)")] + InputNoteCountOutOfRange(usize), #[error("transaction preparation failed: {0}")] - TransactionPreparationFailed(#[source] TransactionExecutorError), + TransactionPreparation(#[source] TransactionExecutorError), #[error("transaction execution prologue failed: {0}")] - PrologueExecutionFailed(#[source] TransactionExecutorError), + PrologueExecution(#[source] TransactionExecutorError), +} + +// TRANSACTION CHECKER ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub(crate) enum TransactionCheckerError { + #[error("transaction preparation failed: {0}")] + TransactionPreparation(#[source] TransactionExecutorError), + #[error("transaction execution prologue failed: {0}")] + PrologueExecution(#[source] TransactionExecutorError), #[error("transaction execution epilogue failed: {0}")] - EpilogueExecutionFailed(#[source] TransactionExecutorError), + EpilogueExecution(#[source] TransactionExecutorError), #[error("transaction note execution failed on note index {failed_note_index}: {error}")] - NoteExecutionFailed { + NoteExecution { failed_note_index: usize, error: TransactionExecutorError, }, } +impl From for TransactionExecutorError { + fn from(error: TransactionCheckerError) -> Self { + match error { + TransactionCheckerError::TransactionPreparation(error) => error, + TransactionCheckerError::PrologueExecution(error) => error, + TransactionCheckerError::EpilogueExecution(error) => error, + TransactionCheckerError::NoteExecution { error, .. } => error, + } + } +} + // TRANSACTION EXECUTOR ERROR // ================================================================================================ diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index c327d7e095..ffe2e74aa2 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -35,7 +35,12 @@ mod data_store; pub use data_store::DataStore; mod notes_checker; -pub use notes_checker::{FailedNote, NoteConsumptionChecker, NoteConsumptionInfo}; +pub use notes_checker::{ + FailedNote, + MAX_NUM_CHECKER_NOTES, + NoteConsumptionChecker, + NoteConsumptionInfo, +}; // TRANSACTION EXECUTOR // ================================================================================================ diff --git a/crates/miden-tx/src/executor/notes_checker.rs b/crates/miden-tx/src/executor/notes_checker.rs index fe899d8bc3..1bd2379470 100644 --- a/crates/miden-tx/src/executor/notes_checker.rs +++ b/crates/miden-tx/src/executor/notes_checker.rs @@ -1,3 +1,4 @@ +use alloc::collections::BTreeMap; use alloc::vec::Vec; use miden_lib::note::well_known_note::WellKnownNote; @@ -10,8 +11,17 @@ use miden_processor::fast::FastProcessor; use super::TransactionExecutor; use crate::auth::TransactionAuthenticator; +use crate::errors::TransactionCheckerError; use crate::{DataStore, NoteCheckerError, TransactionExecutorError}; +// CONSTANTS +// ================================================================================================ + +/// Maximum number of notes that can be checked at once. +/// +/// Fixed at an amount that should keep each run of note consumption checking to a maximum of ~50ms. +pub const MAX_NUM_CHECKER_NOTES: usize = 20; + // NOTE CONSUMPTION INFO // ================================================================================================ @@ -19,12 +29,12 @@ use crate::{DataStore, NoteCheckerError, TransactionExecutorError}; #[derive(Debug)] pub struct FailedNote { pub note: Note, - pub error: Option, + pub error: TransactionExecutorError, } impl FailedNote { /// Constructs a new `FailedNote`. - pub fn new(note: Note, error: Option) -> Self { + pub fn new(note: Note, error: TransactionExecutorError) -> Self { Self { note, error } } } @@ -74,34 +84,37 @@ where /// This function attempts to find the maximum set of notes that can be successfully executed /// together by the target account. /// - /// If some notes succeed but others fail, the failed notes are removed from the candidate set + /// Because of the runtime complexity involved in this function, a limited range of + /// [`MAX_NUM_CHECKER_NOTES`] input notes is allowed. + /// + /// If some notes succeed and others fail, the failed notes are removed from the candidate set /// and the remaining notes (successful + unattempted) are retried in the next iteration. This /// process continues until either all remaining notes succeed or no notes can be successfully /// executed /// - /// # Example Execution Flow - /// - /// Given notes A, B, C, D, E: + /// For example, given notes A, B, C, D, E, the execution flow would be as follows: /// - Try [A, B, C, D, E] → A, B succeed, C fails → Remove C, try again. /// - Try [A, B, D, E] → A, B, D succeed, E fails → Remove E, try again. /// - Try [A, B, D] → All succeed → Return successful=[A, B, D], failed=[C, E]. /// - /// # Returns + /// If a failure occurs at the epilogue phase of the transaction execution, the relevant set of + /// otherwise-successful notes are retried in various combinations in an attempt to find a + /// combination that passes the epilogue phase successfully. /// - /// Returns [`NoteConsumptionInfo`] containing: - /// - `successful`: Notes that can be consumed together by the account. - /// - `failed`: Notes that failed during execution attempts. + /// Returns a list of successfully consumed notes and a list of failed notes. pub async fn check_notes_consumability( &self, target_account_id: AccountId, block_ref: BlockNumber, - input_notes: InputNotes, + mut notes: Vec, tx_args: TransactionArgs, ) -> Result { + let num_notes = notes.len(); + if num_notes == 0 || num_notes > MAX_NUM_CHECKER_NOTES { + return Err(NoteCheckerError::InputNoteCountOutOfRange(num_notes)); + } // Ensure well-known notes are ordered first. - let mut notes = input_notes.into_vec(); - notes.sort_unstable_by_key(|note| WellKnownNote::from_note(note.note()).is_none()); - let notes = InputNotes::::new_unchecked(notes); + notes.sort_unstable_by_key(|note| WellKnownNote::from_note(note).is_none()); // Attempt to find an executable set of notes. self.find_executable_notes_by_elimination(target_account_id, block_ref, notes, tx_args) @@ -119,10 +132,10 @@ where &self, target_account_id: AccountId, block_ref: BlockNumber, - notes: InputNotes, + notes: Vec, tx_args: TransactionArgs, ) -> Result { - let mut candidate_notes = notes.into_vec(); + let mut candidate_notes = notes; let mut failed_notes = Vec::new(); // Attempt to execute notes in a loop. Reduce the set of notes based on failures until @@ -134,21 +147,20 @@ where .try_execute_notes( target_account_id, block_ref, - InputNotes::::new_unchecked(candidate_notes.clone()), + candidate_notes.clone().into(), &tx_args, ) .await { Ok(()) => { // A full set of successful notes has been found. - let successful = - candidate_notes.into_iter().map(InputNote::into_note).collect::>(); + let successful = candidate_notes; return Ok(NoteConsumptionInfo::new(successful, failed_notes)); }, - Err(NoteCheckerError::NoteExecutionFailed { failed_note_index, error }) => { + Err(TransactionCheckerError::NoteExecution { failed_note_index, error }) => { // SAFETY: Failed note index is in bounds of the candidate notes. - let failed_note = candidate_notes.remove(failed_note_index).into_note(); - failed_notes.push(FailedNote::new(failed_note, Some(error))); + let failed_note = candidate_notes.remove(failed_note_index); + failed_notes.push(FailedNote::new(failed_note, error)); // All possible candidate combinations have been attempted. if candidate_notes.is_empty() { @@ -156,7 +168,7 @@ where } // Continue and process the next set of candidates. }, - Err(NoteCheckerError::EpilogueExecutionFailed(_)) => { + Err(TransactionCheckerError::EpilogueExecution(_)) => { let consumption_info = self .find_largest_executable_combination( target_account_id, @@ -168,7 +180,12 @@ where .await; return Ok(consumption_info); }, - Err(error) => return Err(error), + Err(TransactionCheckerError::PrologueExecution(err)) => { + return Err(NoteCheckerError::PrologueExecution(err)); + }, + Err(TransactionCheckerError::TransactionPreparation(err)) => { + return Err(NoteCheckerError::TransactionPreparation(err)); + }, } } } @@ -183,12 +200,13 @@ where &self, target_account_id: AccountId, block_ref: BlockNumber, - input_notes: Vec, + input_notes: Vec, mut failed_notes: Vec, tx_args: &TransactionArgs, ) -> NoteConsumptionInfo { - let mut successful_notes: Vec = Vec::new(); + let mut successful_notes = Vec::new(); let mut remaining_notes = input_notes; + let mut failed_note_index = BTreeMap::new(); // Iterate by note count: try 1 note, then 2, then 3, etc. for size in 1..=remaining_notes.len() { @@ -198,45 +216,50 @@ where } // Try adding each remaining note to the current successful combination. - let mut test_notes = successful_notes.clone(); for (idx, note) in remaining_notes.iter().enumerate() { - test_notes.push(note.clone()); + successful_notes.push(note.clone()); match self .try_execute_notes( target_account_id, block_ref, - InputNotes::::new_unchecked(test_notes.clone()), + InputNotes::::new_unchecked( + successful_notes + .iter() + .cloned() + .map(InputNote::unauthenticated) + .collect::>(), + ), tx_args, ) .await { Ok(()) => { + // The successfully added note might have failed earlier. Remove it from the + // failed list. + failed_note_index.remove(¬e.id()); // This combination succeeded; remove the most recently added note from // the remaining set. remaining_notes.remove(idx); - successful_notes = test_notes; break; }, - _ => { + Err(error) => { // This combination failed; remove the last note from the test set and // continue to next note. - test_notes.pop(); + let failed_note = + successful_notes.pop().expect("successful notes should not be empty"); + // Record the failed note (overwrite previous failures for the relevant + // note). + failed_note_index + .insert(failed_note.id(), FailedNote::new(failed_note, error.into())); }, } } } - // Convert successful InputNotes to Notes. - let successful = successful_notes.into_iter().map(InputNote::into_note).collect::>(); - - // Update failed_notes with notes that weren't included in successful combination. - // TODO: Replace `None` with meaningful error for `FailedNote` below. - let newly_failed_notes = - remaining_notes.into_iter().map(|note| FailedNote::new(note.into_note(), None)); - failed_notes.extend(newly_failed_notes); - - NoteConsumptionInfo::new(successful, failed_notes) + // Append failed notes to the list of failed notes provided as input. + failed_notes.extend(failed_note_index.into_values()); + NoteConsumptionInfo::new(successful_notes, failed_notes) } /// Attempts to execute a transaction with the provided input notes. @@ -250,7 +273,7 @@ where block_ref: BlockNumber, notes: InputNotes, tx_args: &TransactionArgs, - ) -> Result<(), NoteCheckerError> { + ) -> Result<(), TransactionCheckerError> { if notes.is_empty() { return Ok(()); } @@ -259,12 +282,12 @@ where // check (rather than doing this every time when we try to execute some subset of notes), // but we currently cannot do this because transaction preparation includes input notes; // we should refactor the preparation process to separate input note preparation from the - // rest, and then we can prepare the rest of the inputs once for the whole check + // rest, and then we can prepare the rest of the inputs once for the whole check. let (mut host, _, stack_inputs, advice_inputs) = self .0 .prepare_transaction(account_id, block_ref, notes, tx_args, None) .await - .map_err(NoteCheckerError::TransactionPreparationFailed)?; + .map_err(TransactionCheckerError::TransactionPreparation)?; let processor = FastProcessor::new_with_advice_inputs(stack_inputs.as_slice(), advice_inputs); @@ -281,7 +304,7 @@ where // Empty notes vector means that we didn't process the notes, so an error // occurred. if notes.is_empty() { - return Err(NoteCheckerError::PrologueExecutionFailed(error)); + return Err(TransactionCheckerError::PrologueExecution(error)); } let ((_, last_note_interval), success_notes) = @@ -290,11 +313,11 @@ where // If the interval end of the last note is specified, then an error occurred after // notes processing. if last_note_interval.end().is_some() { - Err(NoteCheckerError::EpilogueExecutionFailed(error)) + Err(TransactionCheckerError::EpilogueExecution(error)) } else { // Return the index of the failed note. let failed_note_index = success_notes.len(); - Err(NoteCheckerError::NoteExecutionFailed { failed_note_index, error }) + Err(TransactionCheckerError::NoteExecution { failed_note_index, error }) } }, } diff --git a/crates/miden-tx/src/lib.rs b/crates/miden-tx/src/lib.rs index a7f47be780..2aa3523c76 100644 --- a/crates/miden-tx/src/lib.rs +++ b/crates/miden-tx/src/lib.rs @@ -13,6 +13,7 @@ pub use executor::{ DataStore, ExecutionOptions, FailedNote, + MAX_NUM_CHECKER_NOTES, MastForestStore, NoteConsumptionChecker, NoteConsumptionInfo, From adb7981728fed0829d1c5020da31a72c1c9352e0 Mon Sep 17 00:00:00 2001 From: Marti Date: Thu, 4 Sep 2025 10:19:16 +0200 Subject: [PATCH 016/133] chore: split auth module into separate files (#1849) * chore: split auth module into separate files * chore: rename module to rpo_falcon_512_multisig --- crates/miden-lib/src/account/auth/mod.rs | 580 +----------------- crates/miden-lib/src/account/auth/no_auth.rs | 58 ++ .../src/account/auth/rpo_falcon_512.rs | 39 ++ .../src/account/auth/rpo_falcon_512_acl.rs | 325 ++++++++++ .../account/auth/rpo_falcon_512_multisig.rs | 170 +++++ 5 files changed, 600 insertions(+), 572 deletions(-) create mode 100644 crates/miden-lib/src/account/auth/no_auth.rs create mode 100644 crates/miden-lib/src/account/auth/rpo_falcon_512.rs create mode 100644 crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs create mode 100644 crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs diff --git a/crates/miden-lib/src/account/auth/mod.rs b/crates/miden-lib/src/account/auth/mod.rs index a389176b49..2861b784b0 100644 --- a/crates/miden-lib/src/account/auth/mod.rs +++ b/crates/miden-lib/src/account/auth/mod.rs @@ -1,575 +1,11 @@ -use alloc::vec::Vec; +mod no_auth; +pub use no_auth::NoAuth; -use miden_objects::account::{AccountCode, AccountComponent, StorageMap, StorageSlot}; -use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; -use miden_objects::{AccountError, Word}; +mod rpo_falcon_512; +pub use rpo_falcon_512::AuthRpoFalcon512; -use crate::account::components::{ - multisig_library, - no_auth_library, - rpo_falcon_512_acl_library, - rpo_falcon_512_library, -}; +mod rpo_falcon_512_acl; +pub use rpo_falcon_512_acl::{AuthRpoFalcon512Acl, AuthRpoFalcon512AclConfig}; -/// An [`AccountComponent`] implementing the RpoFalcon512 signature scheme for authentication of -/// transactions. -/// -/// It reexports the procedures from `miden::contracts::auth::basic`. When linking against this -/// component, the `miden` library (i.e. [`MidenLib`](crate::MidenLib)) must be available to the -/// assembler which is the case when using [`TransactionKernel::assembler()`][kasm]. The procedures -/// of this component are: -/// - `auth__tx_rpo_falcon512`, which can be used to verify a signature provided via the advice -/// stack to authenticate a transaction. -/// -/// This component supports all account types. -/// -/// [kasm]: crate::transaction::TransactionKernel::assembler -pub struct AuthRpoFalcon512 { - public_key: PublicKey, -} - -impl AuthRpoFalcon512 { - /// Creates a new [`AuthRpoFalcon512`] component with the given `public_key`. - pub fn new(public_key: PublicKey) -> Self { - Self { public_key } - } -} - -impl From for AccountComponent { - fn from(falcon: AuthRpoFalcon512) -> Self { - AccountComponent::new( - rpo_falcon_512_library(), - vec![StorageSlot::Value(falcon.public_key.into())], - ) - .expect("falcon component should satisfy the requirements of a valid account component") - .with_supports_all_types() - } -} - -/// Configuration for [`AuthRpoFalcon512Acl`] component. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AuthRpoFalcon512AclConfig { - /// List of procedure roots that require authentication when called. - pub auth_trigger_procedures: Vec, - /// When `false`, creating output notes (sending notes to other accounts) requires - /// authentication. When `true`, output notes can be created without authentication. - pub allow_unauthorized_output_notes: bool, - /// When `false`, consuming input notes (processing notes sent to this account) requires - /// authentication. When `true`, input notes can be consumed without authentication. - pub allow_unauthorized_input_notes: bool, -} - -impl AuthRpoFalcon512AclConfig { - /// Creates a new configuration with no trigger procedures and both flags set to `false` (most - /// restrictive). - pub fn new() -> Self { - Self { - auth_trigger_procedures: vec![], - allow_unauthorized_output_notes: false, - allow_unauthorized_input_notes: false, - } - } - - /// Sets the list of procedure roots that require authentication when called. - pub fn with_auth_trigger_procedures(mut self, procedures: Vec) -> Self { - self.auth_trigger_procedures = procedures; - self - } - - /// Sets whether unauthorized output notes are allowed. - pub fn with_allow_unauthorized_output_notes(mut self, allow: bool) -> Self { - self.allow_unauthorized_output_notes = allow; - self - } - - /// Sets whether unauthorized input notes are allowed. - pub fn with_allow_unauthorized_input_notes(mut self, allow: bool) -> Self { - self.allow_unauthorized_input_notes = allow; - self - } -} - -impl Default for AuthRpoFalcon512AclConfig { - fn default() -> Self { - Self::new() - } -} - -/// An [`AccountComponent`] implementing a procedure-based Access Control List (ACL) using the -/// RpoFalcon512 signature scheme for authentication of transactions. -/// -/// This component provides fine-grained authentication control based on three conditions: -/// 1. **Procedure-based authentication**: Requires authentication when any of the specified trigger -/// procedures are called during the transaction. -/// 2. **Output note authentication**: Controls whether creating output notes requires -/// authentication. Output notes are new notes created by the account and sent to other accounts -/// (e.g., when transferring assets). When `allow_unauthorized_output_notes` is `false`, any -/// transaction that creates output notes must be authenticated, ensuring account owners control -/// when their account sends assets to other accounts. -/// 3. **Input note authentication**: Controls whether consuming input notes requires -/// authentication. Input notes are notes that were sent to this account by other accounts (e.g., -/// incoming asset transfers). When `allow_unauthorized_input_notes` is `false`, any transaction -/// that consumes input notes must be authenticated, ensuring account owners control when their -/// account processes incoming notes. -/// -/// ## Authentication Logic -/// -/// Authentication is required if ANY of the following conditions are true: -/// - Any trigger procedure from the ACL was called -/// - Output notes were created AND `allow_unauthorized_output_notes` is `false` -/// - Input notes were consumed AND `allow_unauthorized_input_notes` is `false` -/// -/// If none of these conditions are met, only the nonce is incremented without requiring a -/// signature. -/// -/// ## Use Cases -/// -/// - **Restrictive mode** (`allow_unauthorized_output_notes=false`, -/// `allow_unauthorized_input_notes=false`): All note operations require authentication, providing -/// maximum security. -/// - **Selective mode**: Allow some note operations without authentication while protecting -/// specific procedures, useful for accounts that need to process certain operations -/// automatically. -/// - **Procedure-only mode** (`allow_unauthorized_output_notes=true`, -/// `allow_unauthorized_input_notes=true`): Only specific procedures require authentication, -/// allowing free note processing. -/// -/// ## Storage Layout -/// - Slot 0(value): Public key (same as RpoFalcon512) -/// - Slot 1(value): [num_tracked_procs, allow_unauthorized_output_notes, -/// allow_unauthorized_input_notes, 0] -/// - Slot 2(map): A map with trigger procedure roots -/// -/// ## Important Note on Procedure Detection -/// The procedure-based authentication relies on the `was_procedure_called` kernel function, -/// which only returns `true` if the procedure in question called into a kernel account API -/// that is restricted to the account context. Procedures that don't interact with account -/// state or kernel APIs may not be detected as "called" even if they were executed during -/// the transaction. This is an important limitation to consider when designing trigger -/// procedures for authentication. -/// -/// This component supports all account types. -pub struct AuthRpoFalcon512Acl { - public_key: PublicKey, - config: AuthRpoFalcon512AclConfig, -} - -impl AuthRpoFalcon512Acl { - /// Creates a new [`AuthRpoFalcon512Acl`] component with the given `public_key` and - /// configuration. - /// - /// # Panics - /// Panics if more than [AccountCode::MAX_NUM_PROCEDURES] procedures are specified. - pub fn new( - public_key: PublicKey, - config: AuthRpoFalcon512AclConfig, - ) -> Result { - let max_procedures = AccountCode::MAX_NUM_PROCEDURES; - if config.auth_trigger_procedures.len() > max_procedures { - return Err(AccountError::other(format!( - "Cannot track more than {max_procedures} procedures (account limit)" - ))); - } - - Ok(Self { public_key, config }) - } -} - -impl From for AccountComponent { - fn from(falcon: AuthRpoFalcon512Acl) -> Self { - let mut storage_slots = Vec::with_capacity(3); - - // Slot 0: Public key - storage_slots.push(StorageSlot::Value(falcon.public_key.into())); - - // Slot 1: [num_tracked_procs, allow_unauthorized_output_notes, - // allow_unauthorized_input_notes, 0] - let num_procs = falcon.config.auth_trigger_procedures.len() as u32; - storage_slots.push(StorageSlot::Value(Word::from([ - num_procs, - u32::from(falcon.config.allow_unauthorized_output_notes), - u32::from(falcon.config.allow_unauthorized_input_notes), - 0, - ]))); - - // Slot 2: A map with tracked procedure roots - // We add the map even if there are no trigger procedures, to always maintain the same - // storage layout. - let map_entries = falcon - .config - .auth_trigger_procedures - .iter() - .enumerate() - .map(|(i, proc_root)| (Word::from([i as u32, 0, 0, 0]), *proc_root)); - - // Safe to unwrap because we know that the map keys are unique. - storage_slots.push(StorageSlot::Map(StorageMap::with_entries(map_entries).unwrap())); - - AccountComponent::new(rpo_falcon_512_acl_library(), storage_slots) - .expect( - "ACL auth component should satisfy the requirements of a valid account component", - ) - .with_supports_all_types() - } -} - -/// An [`AccountComponent`] implementing a no-authentication scheme. -/// -/// This component provides **no authentication**! It only checks if the account -/// state has actually changed during transaction execution by comparing the initial -/// account commitment with the current commitment and increments the nonce if -/// they differ. This avoids unnecessary nonce increments for transactions that don't -/// modify the account state. -/// -/// It exports the procedure `auth__no_auth`, which: -/// - Checks if the account state has changed by comparing initial and final commitments -/// - Only increments the nonce if the account state has actually changed -/// - Provides no cryptographic authentication -/// -/// This component supports all account types. -pub struct NoAuth; - -impl NoAuth { - /// Creates a new [`NoAuth`] component. - pub fn new() -> Self { - Self - } -} - -impl Default for NoAuth { - fn default() -> Self { - Self::new() - } -} - -impl From for AccountComponent { - fn from(_: NoAuth) -> Self { - AccountComponent::new(no_auth_library(), vec![]) - .expect("NoAuth component should satisfy the requirements of a valid account component") - .with_supports_all_types() - } -} - -// MULTISIG AUTHENTICATION COMPONENT -// ================================================================================================ - -/// An [`AccountComponent`] implementing a multisig based on RpoFalcon512 signatures. -/// -/// This component requires a threshold number of signatures from a set of approvers. -/// -/// The storage layout is: -/// - Slot 0(value): [threshold, num_approvers, 0, 0] -/// - Slot 1(map): A map with approver public keys (index -> pubkey) -/// - Slot 2(map): A map which stores executed transactions -/// -/// This component supports all account types. -#[derive(Debug)] -pub struct AuthRpoFalcon512Multisig { - threshold: u32, - approvers: Vec, -} - -impl AuthRpoFalcon512Multisig { - /// Creates a new [`AuthRpoFalcon512Multisig`] component with the given `threshold` and - /// list of approver public keys. - /// - /// # Errors - /// Returns an error if threshold is 0 or greater than the number of approvers. - pub fn new(threshold: u32, approvers: Vec) -> Result { - if threshold == 0 { - return Err(AccountError::other("threshold must be at least 1")); - } - - if threshold > approvers.len() as u32 { - return Err(AccountError::other( - "threshold cannot be greater than number of approvers", - )); - } - - Ok(Self { threshold, approvers }) - } -} - -impl From for AccountComponent { - fn from(multisig: AuthRpoFalcon512Multisig) -> Self { - let mut storage_slots = Vec::with_capacity(3); - - // Slot 0: [threshold, num_approvers, 0, 0] - let num_approvers = multisig.approvers.len() as u32; - storage_slots.push(StorageSlot::Value(Word::from([ - multisig.threshold, - num_approvers, - 0, - 0, - ]))); - - // Slot 1: A map with approver public keys - let map_entries = multisig - .approvers - .iter() - .enumerate() - .map(|(i, pub_key)| (Word::from([i as u32, 0, 0, 0]), (*pub_key).into())); - - // Safe to unwrap because we know that the map keys are unique. - storage_slots.push(StorageSlot::Map(StorageMap::with_entries(map_entries).unwrap())); - - // Slot 2: A map which stores executed transactions - let executed_transactions = StorageMap::default(); - storage_slots.push(StorageSlot::Map(executed_transactions)); - - AccountComponent::new(multisig_library(), storage_slots) - .expect("Multisig auth component should satisfy the requirements of a valid account component") - .with_supports_all_types() - } -} - -// TESTS -// ================================================================================================ - -#[cfg(test)] -mod tests { - use alloc::string::ToString; - - use miden_objects::Word; - use miden_objects::account::AccountBuilder; - - use super::*; - use crate::account::components::WellKnownComponent; - use crate::account::wallets::BasicWallet; - - /// Test configuration for parametrized ACL tests - struct AclTestConfig { - /// Whether to include auth trigger procedures - with_procedures: bool, - /// Allow unauthorized output notes flag - allow_unauthorized_output_notes: bool, - /// Allow unauthorized input notes flag - allow_unauthorized_input_notes: bool, - /// Expected slot 1 value [num_procs, allow_output, allow_input, 0] - expected_slot_1: Word, - } - - /// Helper function to get the basic wallet procedures for testing - fn get_basic_wallet_procedures() -> Vec { - // Get the two trigger procedures from BasicWallet: `receive_asset`, `move_asset_to_note`. - let procedures: Vec = WellKnownComponent::BasicWallet.procedure_digests().collect(); - - assert_eq!(procedures.len(), 2); - procedures - } - - /// Parametrized test helper for ACL component testing - fn test_acl_component(config: AclTestConfig) { - let public_key = PublicKey::new(Word::empty()); - - // Build the configuration - let mut acl_config = AuthRpoFalcon512AclConfig::new() - .with_allow_unauthorized_output_notes(config.allow_unauthorized_output_notes) - .with_allow_unauthorized_input_notes(config.allow_unauthorized_input_notes); - - let auth_trigger_procedures = if config.with_procedures { - let procedures = get_basic_wallet_procedures(); - acl_config = acl_config.with_auth_trigger_procedures(procedures.clone()); - procedures - } else { - vec![] - }; - - // Create component and account - let component = - AuthRpoFalcon512Acl::new(public_key, acl_config).expect("component creation failed"); - - let (account, _) = AccountBuilder::new([0; 32]) - .with_auth_component(component) - .with_component(BasicWallet) - .build() - .expect("account building failed"); - - // Assert public key in slot 0 - let public_key_slot = account.storage().get_item(0).expect("storage slot 0 access failed"); - assert_eq!(public_key_slot, Word::from(public_key)); - - // Assert configuration in slot 1 - let slot_1 = account.storage().get_item(1).expect("storage slot 1 access failed"); - assert_eq!(slot_1, config.expected_slot_1); - - // Assert procedure roots in map (slot 2) - if config.with_procedures { - for (i, expected_proc_root) in auth_trigger_procedures.iter().enumerate() { - let proc_root = account - .storage() - .get_map_item(2, Word::from([i as u32, 0, 0, 0])) - .expect("storage map access failed"); - assert_eq!(proc_root, *expected_proc_root); - } - } else { - // When no procedures, the map should return empty for key [0,0,0,0] - let proc_root = account - .storage() - .get_map_item(2, Word::empty()) - .expect("storage map access failed"); - assert_eq!(proc_root, Word::empty()); - } - } - - /// Test ACL component with no procedures and both authorization flags set to false - #[test] - fn test_rpo_falcon_512_acl_no_procedures() { - test_acl_component(AclTestConfig { - with_procedures: false, - allow_unauthorized_output_notes: false, - allow_unauthorized_input_notes: false, - expected_slot_1: Word::empty(), // [0, 0, 0, 0] - }); - } - - /// Test ACL component with two procedures and both authorization flags set to false - #[test] - fn test_rpo_falcon_512_acl_with_two_procedures() { - test_acl_component(AclTestConfig { - with_procedures: true, - allow_unauthorized_output_notes: false, - allow_unauthorized_input_notes: false, - expected_slot_1: Word::from([2u32, 0, 0, 0]), - }); - } - - /// Test ACL component with no procedures and allow_unauthorized_output_notes set to true - #[test] - fn test_rpo_falcon_512_acl_with_allow_unauthorized_output_notes() { - test_acl_component(AclTestConfig { - with_procedures: false, - allow_unauthorized_output_notes: true, - allow_unauthorized_input_notes: false, - expected_slot_1: Word::from([0u32, 1, 0, 0]), - }); - } - - /// Test ACL component with two procedures and allow_unauthorized_output_notes set to true - #[test] - fn test_rpo_falcon_512_acl_with_procedures_and_allow_unauthorized_output_notes() { - test_acl_component(AclTestConfig { - with_procedures: true, - allow_unauthorized_output_notes: true, - allow_unauthorized_input_notes: false, - expected_slot_1: Word::from([2u32, 1, 0, 0]), - }); - } - - /// Test ACL component with no procedures and allow_unauthorized_input_notes set to true - #[test] - fn test_rpo_falcon_512_acl_with_allow_unauthorized_input_notes() { - test_acl_component(AclTestConfig { - with_procedures: false, - allow_unauthorized_output_notes: false, - allow_unauthorized_input_notes: true, - expected_slot_1: Word::from([0u32, 0, 1, 0]), - }); - } - - /// Test ACL component with two procedures and both authorization flags set to true - #[test] - fn test_rpo_falcon_512_acl_with_both_allow_flags() { - test_acl_component(AclTestConfig { - with_procedures: true, - allow_unauthorized_output_notes: true, - allow_unauthorized_input_notes: true, - expected_slot_1: Word::from([2u32, 1, 1, 0]), - }); - } - - #[test] - fn test_no_auth_component() { - // Create an account using the NoAuth component - let (_account, _) = AccountBuilder::new([0; 32]) - .with_auth_component(NoAuth) - .with_component(BasicWallet) - .build() - .expect("account building failed"); - } - - // MULTISIG TESTS - // ============================================================================================ - - /// Test multisig component setup with various configurations - #[test] - fn test_multisig_component_setup() { - // Create test public keys - let pub_key_1 = PublicKey::new(Word::from([1u32, 0, 0, 0])); - let pub_key_2 = PublicKey::new(Word::from([2u32, 0, 0, 0])); - let pub_key_3 = PublicKey::new(Word::from([3u32, 0, 0, 0])); - let approvers = vec![pub_key_1, pub_key_2, pub_key_3]; - let threshold = 2u32; - - // Create multisig component - let multisig_component = AuthRpoFalcon512Multisig::new(threshold, approvers.clone()) - .expect("multisig component creation failed"); - - // Build account with multisig component - let (account, _) = AccountBuilder::new([0; 32]) - .with_auth_component(multisig_component) - .with_component(BasicWallet) - .build() - .expect("account building failed"); - - // Verify slot 0: [threshold, num_approvers, 0, 0] - let threshold_slot = account.storage().get_item(0).expect("storage slot 0 access failed"); - assert_eq!(threshold_slot, Word::from([threshold, approvers.len() as u32, 0, 0])); - - // Verify slot 1: Approver public keys in map - for (i, expected_pub_key) in approvers.iter().enumerate() { - let stored_pub_key = account - .storage() - .get_map_item(1, Word::from([i as u32, 0, 0, 0])) - .expect("storage map access failed"); - assert_eq!(stored_pub_key, Word::from(*expected_pub_key)); - } - } - - /// Test multisig component with minimum threshold (1 of 1) - #[test] - fn test_multisig_component_minimum_threshold() { - let pub_key = PublicKey::new(Word::from([42u32, 0, 0, 0])); - let approvers = vec![pub_key]; - let threshold = 1u32; - - let multisig_component = AuthRpoFalcon512Multisig::new(threshold, approvers.clone()) - .expect("multisig component creation failed"); - - let (account, _) = AccountBuilder::new([0; 32]) - .with_auth_component(multisig_component) - .with_component(BasicWallet) - .build() - .expect("account building failed"); - - // Verify storage layout - let threshold_slot = account.storage().get_item(0).expect("storage slot 0 access failed"); - assert_eq!(threshold_slot, Word::from([threshold, approvers.len() as u32, 0, 0])); - - let stored_pub_key = account - .storage() - .get_map_item(1, Word::from([0u32, 0, 0, 0])) - .expect("storage map access failed"); - assert_eq!(stored_pub_key, Word::from(pub_key)); - } - - /// Test multisig component error cases - #[test] - fn test_multisig_component_error_cases() { - let pub_key = PublicKey::new(Word::from([1u32, 0, 0, 0])); - let approvers = vec![pub_key]; - - // Test threshold = 0 (should fail) - let result = AuthRpoFalcon512Multisig::new(0, approvers.clone()); - assert!(result.unwrap_err().to_string().contains("threshold must be at least 1")); - - // Test threshold > number of approvers (should fail) - let result = AuthRpoFalcon512Multisig::new(2, approvers); - assert!( - result - .unwrap_err() - .to_string() - .contains("threshold cannot be greater than number of approvers") - ); - } -} +mod rpo_falcon_512_multisig; +pub use rpo_falcon_512_multisig::AuthRpoFalcon512Multisig; diff --git a/crates/miden-lib/src/account/auth/no_auth.rs b/crates/miden-lib/src/account/auth/no_auth.rs new file mode 100644 index 0000000000..746db7f0c3 --- /dev/null +++ b/crates/miden-lib/src/account/auth/no_auth.rs @@ -0,0 +1,58 @@ +use miden_objects::account::AccountComponent; + +use crate::account::components::no_auth_library; + +/// An [`AccountComponent`] implementing a no-authentication scheme. +/// +/// This component provides **no authentication**! It only checks if the account +/// state has actually changed during transaction execution by comparing the initial +/// account commitment with the current commitment and increments the nonce if +/// they differ. This avoids unnecessary nonce increments for transactions that don't +/// modify the account state. +/// +/// It exports the procedure `auth__no_auth`, which: +/// - Checks if the account state has changed by comparing initial and final commitments +/// - Only increments the nonce if the account state has actually changed +/// - Provides no cryptographic authentication +/// +/// This component supports all account types. +pub struct NoAuth; + +impl NoAuth { + /// Creates a new [`NoAuth`] component. + pub fn new() -> Self { + Self + } +} + +impl Default for NoAuth { + fn default() -> Self { + Self::new() + } +} + +impl From for AccountComponent { + fn from(_: NoAuth) -> Self { + AccountComponent::new(no_auth_library(), vec![]) + .expect("NoAuth component should satisfy the requirements of a valid account component") + .with_supports_all_types() + } +} + +#[cfg(test)] +mod tests { + use miden_objects::account::AccountBuilder; + + use super::*; + use crate::account::wallets::BasicWallet; + + #[test] + fn test_no_auth_component() { + // Create an account using the NoAuth component + let (_account, _) = AccountBuilder::new([0; 32]) + .with_auth_component(NoAuth) + .with_component(BasicWallet) + .build() + .expect("account building failed"); + } +} diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512.rs new file mode 100644 index 0000000000..b1d7c54cc2 --- /dev/null +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512.rs @@ -0,0 +1,39 @@ +use miden_objects::account::{AccountComponent, StorageSlot}; +use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; + +use crate::account::components::rpo_falcon_512_library; + +/// An [`AccountComponent`] implementing the RpoFalcon512 signature scheme for authentication of +/// transactions. +/// +/// It reexports the procedures from `miden::contracts::auth::basic`. When linking against this +/// component, the `miden` library (i.e. [`MidenLib`](crate::MidenLib)) must be available to the +/// assembler which is the case when using [`TransactionKernel::assembler()`][kasm]. The procedures +/// of this component are: +/// - `auth__tx_rpo_falcon512`, which can be used to verify a signature provided via the advice +/// stack to authenticate a transaction. +/// +/// This component supports all account types. +/// +/// [kasm]: crate::transaction::TransactionKernel::assembler +pub struct AuthRpoFalcon512 { + public_key: PublicKey, +} + +impl AuthRpoFalcon512 { + /// Creates a new [`AuthRpoFalcon512`] component with the given `public_key`. + pub fn new(public_key: PublicKey) -> Self { + Self { public_key } + } +} + +impl From for AccountComponent { + fn from(falcon: AuthRpoFalcon512) -> Self { + AccountComponent::new( + rpo_falcon_512_library(), + vec![StorageSlot::Value(falcon.public_key.into())], + ) + .expect("falcon component should satisfy the requirements of a valid account component") + .with_supports_all_types() + } +} diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs new file mode 100644 index 0000000000..77eeeac10a --- /dev/null +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs @@ -0,0 +1,325 @@ +use alloc::vec::Vec; + +use miden_objects::account::{AccountCode, AccountComponent, StorageMap, StorageSlot}; +use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; +use miden_objects::{AccountError, Word}; + +use crate::account::components::rpo_falcon_512_acl_library; + +/// Configuration for [`AuthRpoFalcon512Acl`] component. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AuthRpoFalcon512AclConfig { + /// List of procedure roots that require authentication when called. + pub auth_trigger_procedures: Vec, + /// When `false`, creating output notes (sending notes to other accounts) requires + /// authentication. When `true`, output notes can be created without authentication. + pub allow_unauthorized_output_notes: bool, + /// When `false`, consuming input notes (processing notes sent to this account) requires + /// authentication. When `true`, input notes can be consumed without authentication. + pub allow_unauthorized_input_notes: bool, +} + +impl AuthRpoFalcon512AclConfig { + /// Creates a new configuration with no trigger procedures and both flags set to `false` (most + /// restrictive). + pub fn new() -> Self { + Self { + auth_trigger_procedures: vec![], + allow_unauthorized_output_notes: false, + allow_unauthorized_input_notes: false, + } + } + + /// Sets the list of procedure roots that require authentication when called. + pub fn with_auth_trigger_procedures(mut self, procedures: Vec) -> Self { + self.auth_trigger_procedures = procedures; + self + } + + /// Sets whether unauthorized output notes are allowed. + pub fn with_allow_unauthorized_output_notes(mut self, allow: bool) -> Self { + self.allow_unauthorized_output_notes = allow; + self + } + + /// Sets whether unauthorized input notes are allowed. + pub fn with_allow_unauthorized_input_notes(mut self, allow: bool) -> Self { + self.allow_unauthorized_input_notes = allow; + self + } +} + +impl Default for AuthRpoFalcon512AclConfig { + fn default() -> Self { + Self::new() + } +} + +/// An [`AccountComponent`] implementing a procedure-based Access Control List (ACL) using the +/// RpoFalcon512 signature scheme for authentication of transactions. +/// +/// This component provides fine-grained authentication control based on three conditions: +/// 1. **Procedure-based authentication**: Requires authentication when any of the specified trigger +/// procedures are called during the transaction. +/// 2. **Output note authentication**: Controls whether creating output notes requires +/// authentication. Output notes are new notes created by the account and sent to other accounts +/// (e.g., when transferring assets). When `allow_unauthorized_output_notes` is `false`, any +/// transaction that creates output notes must be authenticated, ensuring account owners control +/// when their account sends assets to other accounts. +/// 3. **Input note authentication**: Controls whether consuming input notes requires +/// authentication. Input notes are notes that were sent to this account by other accounts (e.g., +/// incoming asset transfers). When `allow_unauthorized_input_notes` is `false`, any transaction +/// that consumes input notes must be authenticated, ensuring account owners control when their +/// account processes incoming notes. +/// +/// ## Authentication Logic +/// +/// Authentication is required if ANY of the following conditions are true: +/// - Any trigger procedure from the ACL was called +/// - Output notes were created AND `allow_unauthorized_output_notes` is `false` +/// - Input notes were consumed AND `allow_unauthorized_input_notes` is `false` +/// +/// If none of these conditions are met, only the nonce is incremented without requiring a +/// signature. +/// +/// ## Use Cases +/// +/// - **Restrictive mode** (`allow_unauthorized_output_notes=false`, +/// `allow_unauthorized_input_notes=false`): All note operations require authentication, providing +/// maximum security. +/// - **Selective mode**: Allow some note operations without authentication while protecting +/// specific procedures, useful for accounts that need to process certain operations +/// automatically. +/// - **Procedure-only mode** (`allow_unauthorized_output_notes=true`, +/// `allow_unauthorized_input_notes=true`): Only specific procedures require authentication, +/// allowing free note processing. +/// +/// ## Storage Layout +/// - Slot 0(value): Public key (same as RpoFalcon512) +/// - Slot 1(value): [num_tracked_procs, allow_unauthorized_output_notes, +/// allow_unauthorized_input_notes, 0] +/// - Slot 2(map): A map with trigger procedure roots +/// +/// ## Important Note on Procedure Detection +/// The procedure-based authentication relies on the `was_procedure_called` kernel function, +/// which only returns `true` if the procedure in question called into a kernel account API +/// that is restricted to the account context. Procedures that don't interact with account +/// state or kernel APIs may not be detected as "called" even if they were executed during +/// the transaction. This is an important limitation to consider when designing trigger +/// procedures for authentication. +/// +/// This component supports all account types. +pub struct AuthRpoFalcon512Acl { + public_key: PublicKey, + config: AuthRpoFalcon512AclConfig, +} + +impl AuthRpoFalcon512Acl { + /// Creates a new [`AuthRpoFalcon512Acl`] component with the given `public_key` and + /// configuration. + /// + /// # Panics + /// Panics if more than [AccountCode::MAX_NUM_PROCEDURES] procedures are specified. + pub fn new( + public_key: PublicKey, + config: AuthRpoFalcon512AclConfig, + ) -> Result { + let max_procedures = AccountCode::MAX_NUM_PROCEDURES; + if config.auth_trigger_procedures.len() > max_procedures { + return Err(AccountError::other(format!( + "Cannot track more than {max_procedures} procedures (account limit)" + ))); + } + + Ok(Self { public_key, config }) + } +} + +impl From for AccountComponent { + fn from(falcon: AuthRpoFalcon512Acl) -> Self { + let mut storage_slots = Vec::with_capacity(3); + + // Slot 0: Public key + storage_slots.push(StorageSlot::Value(falcon.public_key.into())); + + // Slot 1: [num_tracked_procs, allow_unauthorized_output_notes, + // allow_unauthorized_input_notes, 0] + let num_procs = falcon.config.auth_trigger_procedures.len() as u32; + storage_slots.push(StorageSlot::Value(Word::from([ + num_procs, + u32::from(falcon.config.allow_unauthorized_output_notes), + u32::from(falcon.config.allow_unauthorized_input_notes), + 0, + ]))); + + // Slot 2: A map with tracked procedure roots + // We add the map even if there are no trigger procedures, to always maintain the same + // storage layout. + let map_entries = falcon + .config + .auth_trigger_procedures + .iter() + .enumerate() + .map(|(i, proc_root)| (Word::from([i as u32, 0, 0, 0]), *proc_root)); + + // Safe to unwrap because we know that the map keys are unique. + storage_slots.push(StorageSlot::Map(StorageMap::with_entries(map_entries).unwrap())); + + AccountComponent::new(rpo_falcon_512_acl_library(), storage_slots) + .expect( + "ACL auth component should satisfy the requirements of a valid account component", + ) + .with_supports_all_types() + } +} + +#[cfg(test)] +mod tests { + use miden_objects::Word; + use miden_objects::account::AccountBuilder; + + use super::*; + use crate::account::components::WellKnownComponent; + use crate::account::wallets::BasicWallet; + + /// Test configuration for parametrized ACL tests + struct AclTestConfig { + /// Whether to include auth trigger procedures + with_procedures: bool, + /// Allow unauthorized output notes flag + allow_unauthorized_output_notes: bool, + /// Allow unauthorized input notes flag + allow_unauthorized_input_notes: bool, + /// Expected slot 1 value [num_procs, allow_output, allow_input, 0] + expected_slot_1: Word, + } + + /// Helper function to get the basic wallet procedures for testing + fn get_basic_wallet_procedures() -> Vec { + // Get the two trigger procedures from BasicWallet: `receive_asset`, `move_asset_to_note`. + let procedures: Vec = WellKnownComponent::BasicWallet.procedure_digests().collect(); + + assert_eq!(procedures.len(), 2); + procedures + } + + /// Parametrized test helper for ACL component testing + fn test_acl_component(config: AclTestConfig) { + let public_key = PublicKey::new(Word::empty()); + + // Build the configuration + let mut acl_config = AuthRpoFalcon512AclConfig::new() + .with_allow_unauthorized_output_notes(config.allow_unauthorized_output_notes) + .with_allow_unauthorized_input_notes(config.allow_unauthorized_input_notes); + + let auth_trigger_procedures = if config.with_procedures { + let procedures = get_basic_wallet_procedures(); + acl_config = acl_config.with_auth_trigger_procedures(procedures.clone()); + procedures + } else { + vec![] + }; + + // Create component and account + let component = + AuthRpoFalcon512Acl::new(public_key, acl_config).expect("component creation failed"); + + let (account, _) = AccountBuilder::new([0; 32]) + .with_auth_component(component) + .with_component(BasicWallet) + .build() + .expect("account building failed"); + + // Assert public key in slot 0 + let public_key_slot = account.storage().get_item(0).expect("storage slot 0 access failed"); + assert_eq!(public_key_slot, Word::from(public_key)); + + // Assert configuration in slot 1 + let slot_1 = account.storage().get_item(1).expect("storage slot 1 access failed"); + assert_eq!(slot_1, config.expected_slot_1); + + // Assert procedure roots in map (slot 2) + if config.with_procedures { + for (i, expected_proc_root) in auth_trigger_procedures.iter().enumerate() { + let proc_root = account + .storage() + .get_map_item(2, Word::from([i as u32, 0, 0, 0])) + .expect("storage map access failed"); + assert_eq!(proc_root, *expected_proc_root); + } + } else { + // When no procedures, the map should return empty for key [0,0,0,0] + let proc_root = account + .storage() + .get_map_item(2, Word::empty()) + .expect("storage map access failed"); + assert_eq!(proc_root, Word::empty()); + } + } + + /// Test ACL component with no procedures and both authorization flags set to false + #[test] + fn test_rpo_falcon_512_acl_no_procedures() { + test_acl_component(AclTestConfig { + with_procedures: false, + allow_unauthorized_output_notes: false, + allow_unauthorized_input_notes: false, + expected_slot_1: Word::empty(), // [0, 0, 0, 0] + }); + } + + /// Test ACL component with two procedures and both authorization flags set to false + #[test] + fn test_rpo_falcon_512_acl_with_two_procedures() { + test_acl_component(AclTestConfig { + with_procedures: true, + allow_unauthorized_output_notes: false, + allow_unauthorized_input_notes: false, + expected_slot_1: Word::from([2u32, 0, 0, 0]), + }); + } + + /// Test ACL component with no procedures and allow_unauthorized_output_notes set to true + #[test] + fn test_rpo_falcon_512_acl_with_allow_unauthorized_output_notes() { + test_acl_component(AclTestConfig { + with_procedures: false, + allow_unauthorized_output_notes: true, + allow_unauthorized_input_notes: false, + expected_slot_1: Word::from([0u32, 1, 0, 0]), + }); + } + + /// Test ACL component with two procedures and allow_unauthorized_output_notes set to true + #[test] + fn test_rpo_falcon_512_acl_with_procedures_and_allow_unauthorized_output_notes() { + test_acl_component(AclTestConfig { + with_procedures: true, + allow_unauthorized_output_notes: true, + allow_unauthorized_input_notes: false, + expected_slot_1: Word::from([2u32, 1, 0, 0]), + }); + } + + /// Test ACL component with no procedures and allow_unauthorized_input_notes set to true + #[test] + fn test_rpo_falcon_512_acl_with_allow_unauthorized_input_notes() { + test_acl_component(AclTestConfig { + with_procedures: false, + allow_unauthorized_output_notes: false, + allow_unauthorized_input_notes: true, + expected_slot_1: Word::from([0u32, 0, 1, 0]), + }); + } + + /// Test ACL component with two procedures and both authorization flags set to true + #[test] + fn test_rpo_falcon_512_acl_with_both_allow_flags() { + test_acl_component(AclTestConfig { + with_procedures: true, + allow_unauthorized_output_notes: true, + allow_unauthorized_input_notes: true, + expected_slot_1: Word::from([2u32, 1, 1, 0]), + }); + } +} diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs new file mode 100644 index 0000000000..ebba574882 --- /dev/null +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs @@ -0,0 +1,170 @@ +use alloc::vec::Vec; + +use miden_objects::account::{AccountComponent, StorageMap, StorageSlot}; +use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; +use miden_objects::{AccountError, Word}; + +use crate::account::components::multisig_library; + +/// An [`AccountComponent`] implementing a multisig based on RpoFalcon512 signatures. +/// +/// This component requires a threshold number of signatures from a set of approvers. +/// +/// The storage layout is: +/// - Slot 0(value): [threshold, num_approvers, 0, 0] +/// - Slot 1(map): A map with approver public keys (index -> pubkey) +/// - Slot 2(map): A map which stores executed transactions +/// +/// This component supports all account types. +#[derive(Debug)] +pub struct AuthRpoFalcon512Multisig { + threshold: u32, + approvers: Vec, +} + +impl AuthRpoFalcon512Multisig { + /// Creates a new [`AuthRpoFalcon512Multisig`] component with the given `threshold` and + /// list of approver public keys. + /// + /// # Errors + /// Returns an error if threshold is 0 or greater than the number of approvers. + pub fn new(threshold: u32, approvers: Vec) -> Result { + if threshold == 0 { + return Err(AccountError::other("threshold must be at least 1")); + } + + if threshold > approvers.len() as u32 { + return Err(AccountError::other( + "threshold cannot be greater than number of approvers", + )); + } + + Ok(Self { threshold, approvers }) + } +} + +impl From for AccountComponent { + fn from(multisig: AuthRpoFalcon512Multisig) -> Self { + let mut storage_slots = Vec::with_capacity(3); + + // Slot 0: [threshold, num_approvers, 0, 0] + let num_approvers = multisig.approvers.len() as u32; + storage_slots.push(StorageSlot::Value(Word::from([ + multisig.threshold, + num_approvers, + 0, + 0, + ]))); + + // Slot 1: A map with approver public keys + let map_entries = multisig + .approvers + .iter() + .enumerate() + .map(|(i, pub_key)| (Word::from([i as u32, 0, 0, 0]), (*pub_key).into())); + + // Safe to unwrap because we know that the map keys are unique. + storage_slots.push(StorageSlot::Map(StorageMap::with_entries(map_entries).unwrap())); + + // Slot 2: A map which stores executed transactions + let executed_transactions = StorageMap::default(); + storage_slots.push(StorageSlot::Map(executed_transactions)); + + AccountComponent::new(multisig_library(), storage_slots) + .expect("Multisig auth component should satisfy the requirements of a valid account component") + .with_supports_all_types() + } +} + +#[cfg(test)] +mod tests { + use alloc::string::ToString; + + use miden_objects::Word; + use miden_objects::account::AccountBuilder; + + use super::*; + use crate::account::wallets::BasicWallet; + + /// Test multisig component setup with various configurations + #[test] + fn test_multisig_component_setup() { + // Create test public keys + let pub_key_1 = PublicKey::new(Word::from([1u32, 0, 0, 0])); + let pub_key_2 = PublicKey::new(Word::from([2u32, 0, 0, 0])); + let pub_key_3 = PublicKey::new(Word::from([3u32, 0, 0, 0])); + let approvers = vec![pub_key_1, pub_key_2, pub_key_3]; + let threshold = 2u32; + + // Create multisig component + let multisig_component = AuthRpoFalcon512Multisig::new(threshold, approvers.clone()) + .expect("multisig component creation failed"); + + // Build account with multisig component + let (account, _) = AccountBuilder::new([0; 32]) + .with_auth_component(multisig_component) + .with_component(BasicWallet) + .build() + .expect("account building failed"); + + // Verify slot 0: [threshold, num_approvers, 0, 0] + let threshold_slot = account.storage().get_item(0).expect("storage slot 0 access failed"); + assert_eq!(threshold_slot, Word::from([threshold, approvers.len() as u32, 0, 0])); + + // Verify slot 1: Approver public keys in map + for (i, expected_pub_key) in approvers.iter().enumerate() { + let stored_pub_key = account + .storage() + .get_map_item(1, Word::from([i as u32, 0, 0, 0])) + .expect("storage map access failed"); + assert_eq!(stored_pub_key, Word::from(*expected_pub_key)); + } + } + + /// Test multisig component with minimum threshold (1 of 1) + #[test] + fn test_multisig_component_minimum_threshold() { + let pub_key = PublicKey::new(Word::from([42u32, 0, 0, 0])); + let approvers = vec![pub_key]; + let threshold = 1u32; + + let multisig_component = AuthRpoFalcon512Multisig::new(threshold, approvers.clone()) + .expect("multisig component creation failed"); + + let (account, _) = AccountBuilder::new([0; 32]) + .with_auth_component(multisig_component) + .with_component(BasicWallet) + .build() + .expect("account building failed"); + + // Verify storage layout + let threshold_slot = account.storage().get_item(0).expect("storage slot 0 access failed"); + assert_eq!(threshold_slot, Word::from([threshold, approvers.len() as u32, 0, 0])); + + let stored_pub_key = account + .storage() + .get_map_item(1, Word::from([0u32, 0, 0, 0])) + .expect("storage map access failed"); + assert_eq!(stored_pub_key, Word::from(pub_key)); + } + + /// Test multisig component error cases + #[test] + fn test_multisig_component_error_cases() { + let pub_key = PublicKey::new(Word::from([1u32, 0, 0, 0])); + let approvers = vec![pub_key]; + + // Test threshold = 0 (should fail) + let result = AuthRpoFalcon512Multisig::new(0, approvers.clone()); + assert!(result.unwrap_err().to_string().contains("threshold must be at least 1")); + + // Test threshold > number of approvers (should fail) + let result = AuthRpoFalcon512Multisig::new(2, approvers); + assert!( + result + .unwrap_err() + .to_string() + .contains("threshold cannot be greater than number of approvers") + ); + } +} From c4b0c48b1bf7f9258d351deb822869517236afbe Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Thu, 4 Sep 2025 14:35:28 +0300 Subject: [PATCH 017/133] Move `miden::asset::{create_fungible_asset, create_non_fungible_asset}` to `miden::faucet` (#1850) * refactor: move create_fungible_asset and create_non_fungible_asset to faucet * chore: update changelog --- CHANGELOG.md | 1 + crates/miden-lib/asm/miden/asset.masm | 40 ------------------ .../contracts/faucets/basic_fungible.masm | 3 +- crates/miden-lib/asm/miden/faucet.masm | 42 +++++++++++++++++++ .../src/kernel_tests/tx/test_asset.rs | 8 ++-- 5 files changed, 48 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 103bcb5e1d..2790b47f9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - [BREAKING] Incremented MSRV to 1.89. - [BREAKING] Remove versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). +- [BREAKING] Move `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Replaced `Account` with `PartialAccount` in `TransactionInputs` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). - [BREAKING] Renamed `Account::init_commitment` to `Account::initial_commitment` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). - [BREAKING] Rename the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). diff --git a/crates/miden-lib/asm/miden/asset.masm b/crates/miden-lib/asm/miden/asset.masm index 6dfcb0ab9f..d967bcc195 100644 --- a/crates/miden-lib/asm/miden/asset.masm +++ b/crates/miden-lib/asm/miden/asset.masm @@ -40,26 +40,6 @@ export.build_fungible_asset # => [ASSET] end -#! Creates a fungible asset for the faucet the transaction is being executed against. -#! -#! Inputs: [amount] -#! Outputs: [ASSET] -#! -#! Where: -#! - amount is the amount of the asset to create. -#! - ASSET is the created fungible asset. -#! -#! Invocation: exec -export.create_fungible_asset - # fetch the id of the faucet the transaction is being executed against. - exec.account::get_id - # => [id_prefix, id_suffix, amount] - - # build the fungible asset - exec.build_fungible_asset - # => [ASSET] -end - #! Builds a non fungible asset for the specified non-fungible faucet and amount. #! #! Inputs: [faucet_id_prefix, DATA_HASH] @@ -84,26 +64,6 @@ export.build_non_fungible_asset # => [ASSET] end -#! Creates a non-fungible asset for the faucet the transaction is being executed against. -#! -#! Inputs: [DATA_HASH] -#! Outputs: [ASSET] -#! -#! Where: -#! - DATA_HASH is the data hash of the non-fungible asset to create. -#! - ASSET is the created non-fungible asset. -#! -#! Invocation: exec -export.create_non_fungible_asset - # get the id of the faucet the transaction is being executed against - exec.account::get_id swap drop - # => [faucet_id_prefix, DATA_HASH] - - # build the non-fungible asset - exec.build_non_fungible_asset - # => [ASSET] -end - #! Returns the maximum amount of a fungible asset. #! #! Stack: [] diff --git a/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm b/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm index 7fb4eb10bb..8c71a03328 100644 --- a/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm +++ b/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm @@ -8,7 +8,6 @@ # - decimals are the decimals of the token. # - token_symbol as three chars encoded in a Felt. use.miden::account -use.miden::asset use.miden::faucet use.miden::tx use.miden::contracts::auth::basic @@ -69,7 +68,7 @@ export.distribute.4 # => [amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] # creating the asset - exec.asset::create_fungible_asset + exec.faucet::create_fungible_asset # => [ASSET, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] # mint the asset; this is needed to satisfy asset preservation logic. diff --git a/crates/miden-lib/asm/miden/faucet.masm b/crates/miden-lib/asm/miden/faucet.masm index fbc52fe3a8..9fc139d6e9 100644 --- a/crates/miden-lib/asm/miden/faucet.masm +++ b/crates/miden-lib/asm/miden/faucet.masm @@ -1,5 +1,47 @@ +use.miden::asset +use.miden::account use.miden::kernel_proc_offsets +#! Creates a fungible asset for the faucet the transaction is being executed against. +#! +#! Inputs: [amount] +#! Outputs: [ASSET] +#! +#! Where: +#! - amount is the amount of the asset to create. +#! - ASSET is the created fungible asset. +#! +#! Invocation: exec +export.create_fungible_asset + # fetch the id of the faucet the transaction is being executed against. + exec.account::get_id + # => [id_prefix, id_suffix, amount] + + # build the fungible asset + exec.asset::build_fungible_asset + # => [ASSET] +end + +#! Creates a non-fungible asset for the faucet the transaction is being executed against. +#! +#! Inputs: [DATA_HASH] +#! Outputs: [ASSET] +#! +#! Where: +#! - DATA_HASH is the data hash of the non-fungible asset to create. +#! - ASSET is the created non-fungible asset. +#! +#! Invocation: exec +export.create_non_fungible_asset + # get the id of the faucet the transaction is being executed against + exec.account::get_id swap drop + # => [faucet_id_prefix, DATA_HASH] + + # build the non-fungible asset + exec.asset::build_non_fungible_asset + # => [ASSET] +end + #! Mint an asset from the faucet the transaction is being executed against. #! #! Inputs: [ASSET] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs index e6954c2d42..92324c7845 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs @@ -21,14 +21,14 @@ fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { let code = format!( " use.$kernel::prologue - use.miden::asset + use.miden::faucet begin exec.prologue::prepare_transaction # create fungible asset push.{FUNGIBLE_ASSET_AMOUNT} - exec.asset::create_fungible_asset + exec.faucet::create_fungible_asset # truncate the stack swapw dropw @@ -62,14 +62,14 @@ fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { let code = format!( " use.$kernel::prologue - use.miden::asset + use.miden::faucet begin exec.prologue::prepare_transaction # push non-fungible asset data hash onto the stack push.{non_fungible_asset_data_hash} - exec.asset::create_non_fungible_asset + exec.faucet::create_non_fungible_asset # truncate the stack swapw dropw From 5df8d1d584f6ae52c6598fd508fc0705d7b5b349 Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Thu, 4 Sep 2025 21:38:14 +0530 Subject: [PATCH 018/133] chore: move `NetworkId` from account ID to address module (#1851) * chore: move NetworkId from account ID to address module * fix: add changelog --- CHANGELOG.md | 1 + crates/miden-objects/src/account/account_id/mod.rs | 3 --- crates/miden-objects/src/account/mod.rs | 2 -- crates/miden-objects/src/address/mod.rs | 7 +++++-- .../src/{account/account_id => address}/network_id.rs | 0 5 files changed, 6 insertions(+), 7 deletions(-) rename crates/miden-objects/src/{account/account_id => address}/network_id.rs (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2790b47f9f..4147a6bff1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - [BREAKING] Replaced `Account` with `PartialAccount` in `TransactionInputs` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). - [BREAKING] Renamed `Account::init_commitment` to `Account::initial_commitment` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). - [BREAKING] Rename the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). +- [BREAKING] Move `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). ## 0.11.1 (2025-08-28) diff --git a/crates/miden-objects/src/account/account_id/mod.rs b/crates/miden-objects/src/account/account_id/mod.rs index 6e552f6577..2ca927f7ea 100644 --- a/crates/miden-objects/src/account/account_id/mod.rs +++ b/crates/miden-objects/src/account/account_id/mod.rs @@ -6,9 +6,6 @@ pub use id_prefix::AccountIdPrefix; mod seed; -mod network_id; -pub use network_id::{CustomNetworkId, NetworkId}; - mod account_type; pub use account_type::AccountType; diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index ace7eaaf6a..276bdff713 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -17,8 +17,6 @@ pub use account_id::{ AccountIdVersion, AccountStorageMode, AccountType, - CustomNetworkId, - NetworkId, }; pub mod auth; diff --git a/crates/miden-objects/src/address/mod.rs b/crates/miden-objects/src/address/mod.rs index 023316b3e9..35df36380d 100644 --- a/crates/miden-objects/src/address/mod.rs +++ b/crates/miden-objects/src/address/mod.rs @@ -1,15 +1,17 @@ mod r#type; mod interface; +mod network_id; use alloc::string::{String, ToString}; use bech32::Bech32m; use bech32::primitives::decode::{ByteIter, CheckedHrpstring}; pub use interface::AddressInterface; +pub use network_id::{CustomNetworkId, NetworkId}; pub use r#type::AddressType; use crate::AddressError; -use crate::account::{AccountId, AccountStorageMode, NetworkId}; +use crate::account::{AccountId, AccountStorageMode}; use crate::errors::Bech32Error; use crate::note::NoteTag; @@ -331,7 +333,8 @@ mod tests { use bech32::{Bech32, NoChecksum}; use super::*; - use crate::account::{AccountType, CustomNetworkId}; + use crate::account::AccountType; + use crate::address::CustomNetworkId; use crate::testing::account_id::{ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, AccountIdBuilder}; /// Tests that an account ID address can be encoded and decoded. diff --git a/crates/miden-objects/src/account/account_id/network_id.rs b/crates/miden-objects/src/address/network_id.rs similarity index 100% rename from crates/miden-objects/src/account/account_id/network_id.rs rename to crates/miden-objects/src/address/network_id.rs From 2cd278d01b587dec444383f34efebfae334d2aa3 Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Thu, 4 Sep 2025 22:08:17 +0530 Subject: [PATCH 019/133] feat: dummy proof for LocalBatchProver and LocalBlockProver (#1811) * feat: dummy proof for LocalBatchProver and LocalBlockProver * fix: add changelog * apply suggestions * chore: use LocalBatchProver in MockChain * fix: lint * fix: `make toml` --------- Co-authored-by: Philipp Gackstatter --- CHANGELOG.md | 1 + Cargo.lock | 1 + .../src/local_block_prover.rs | 2 +- crates/miden-testing/Cargo.toml | 9 ++-- .../kernel_tests/block/proven_block_error.rs | 20 +++------ .../block/proven_block_success.rs | 6 +-- crates/miden-testing/src/mock_chain/chain.rs | 38 ++-------------- crates/miden-tx-batch-prover/Cargo.toml | 1 + .../src/local_batch_prover.rs | 43 +++++++++++++------ 9 files changed, 53 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4147a6bff1..bb87468521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). - Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). +- Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). ### Changes diff --git a/Cargo.lock b/Cargo.lock index 9b3980d0f0..5a5f3a53aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1341,6 +1341,7 @@ dependencies = [ "miden-objects", "miden-processor", "miden-tx", + "miden-tx-batch-prover", "rand", "rand_chacha", "rstest", diff --git a/crates/miden-block-prover/src/local_block_prover.rs b/crates/miden-block-prover/src/local_block_prover.rs index b56b0eca2c..e98e9533bc 100644 --- a/crates/miden-block-prover/src/local_block_prover.rs +++ b/crates/miden-block-prover/src/local_block_prover.rs @@ -63,7 +63,7 @@ impl LocalBlockProver { /// /// This is exposed for testing purposes. #[cfg(any(feature = "testing", test))] - pub fn prove_without_batch_verification( + pub fn prove_dummy( &self, proposed_block: ProposedBlock, ) -> Result { diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index a50b2c3ba5..065885ef0f 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -17,10 +17,11 @@ std = ["miden-lib/std"] [dependencies] # Workspace dependencies -miden-block-prover = { features = ["testing"], workspace = true } -miden-lib = { features = ["testing"], workspace = true } -miden-objects = { features = ["testing"], workspace = true } -miden-tx = { features = ["testing"], workspace = true } +miden-block-prover = { features = ["testing"], workspace = true } +miden-lib = { features = ["testing"], workspace = true } +miden-objects = { features = ["testing"], workspace = true } +miden-tx = { features = ["testing"], workspace = true } +miden-tx-batch-prover = { features = ["testing"], workspace = true } # Miden dependencies miden-processor = { workspace = true } diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs index 1ca01c614a..88b8a1352c 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs @@ -96,9 +96,7 @@ fn proven_block_fails_on_stale_account_witnesses() -> anyhow::Result<()> { let proposed_block0 = ProposedBlock::new(invalid_account_tree_block_inputs, batches.clone()) .context("failed to propose block 0")?; - let error = LocalBlockProver::new(0) - .prove_without_batch_verification(proposed_block0) - .unwrap_err(); + let error = LocalBlockProver::new(0).prove_dummy(proposed_block0).unwrap_err(); assert_matches!( error, @@ -135,9 +133,7 @@ fn proven_block_fails_on_stale_nullifier_witnesses() -> anyhow::Result<()> { let proposed_block2 = ProposedBlock::new(invalid_nullifier_tree_block_inputs, batches.clone()) .context("failed to propose block 2")?; - let error = LocalBlockProver::new(0) - .prove_without_batch_verification(proposed_block2) - .unwrap_err(); + let error = LocalBlockProver::new(0).prove_dummy(proposed_block2).unwrap_err(); assert_matches!( error, @@ -183,9 +179,7 @@ fn proven_block_fails_on_account_tree_root_mismatch() -> anyhow::Result<()> { let proposed_block1 = ProposedBlock::new(stale_account_witness_block_inputs, batches.clone()) .context("failed to propose block 1")?; - let error = LocalBlockProver::new(0) - .prove_without_batch_verification(proposed_block1) - .unwrap_err(); + let error = LocalBlockProver::new(0).prove_dummy(proposed_block1).unwrap_err(); assert_matches!( error, @@ -233,9 +227,7 @@ fn proven_block_fails_on_nullifier_tree_root_mismatch() -> anyhow::Result<()> { let proposed_block3 = ProposedBlock::new(invalid_nullifier_witness_block_inputs, batches) .context("failed to propose block 3")?; - let error = LocalBlockProver::new(0) - .prove_without_batch_verification(proposed_block3) - .unwrap_err(); + let error = LocalBlockProver::new(0).prove_dummy(proposed_block3).unwrap_err(); assert_matches!( error, @@ -330,7 +322,7 @@ fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> a let block = mock_chain.propose_block(batches).context("failed to propose block")?; - let err = LocalBlockProver::new(0).prove_without_batch_verification(block).unwrap_err(); + let err = LocalBlockProver::new(0).prove_dummy(block).unwrap_err(); // This should fail when we try to _insert_ the same two prefixes into the partial tree. assert_matches!( @@ -425,7 +417,7 @@ fn proven_block_fails_on_creating_account_with_duplicate_account_id_prefix() -> let block = mock_chain.propose_block(batches).context("failed to propose block")?; - let err = LocalBlockProver::new(0).prove_without_batch_verification(block).unwrap_err(); + let err = LocalBlockProver::new(0).prove_dummy(block).unwrap_err(); // This should fail when we try to _track_ the same two prefixes in the partial tree. assert_matches!( diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs index 7d638abef8..1a21339987 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs @@ -126,7 +126,7 @@ fn proven_block_success() -> anyhow::Result<()> { // -------------------------------------------------------------------------------------------- let proven_block = LocalBlockProver::new(MIN_PROOF_SECURITY_LEVEL) - .prove_without_batch_verification(proposed_block) + .prove_dummy(proposed_block) .context("failed to prove proposed block")?; // Check tree/chain commitments against expected values. @@ -325,7 +325,7 @@ fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { assert_eq!(output_notes_batch0, &expected_output_notes_batch0); let proven_block = LocalBlockProver::new(0) - .prove_without_batch_verification(proposed_block) + .prove_dummy(proposed_block) .context("failed to prove block")?; let actual_block_note_tree = proven_block.build_output_note_tree(); @@ -394,7 +394,7 @@ fn proven_block_succeeds_with_empty_batches() -> anyhow::Result<()> { ProposedBlock::new(block_inputs, Vec::new()).context("failed to propose block")?; let proven_block = LocalBlockProver::new(MIN_PROOF_SECURITY_LEVEL) - .prove_without_batch_verification(proposed_block) + .prove_dummy(proposed_block) .context("failed to prove proposed block")?; // Nothing should be created or updated. diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index cf3b0cabd2..5c2aacd688 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -28,11 +28,9 @@ use miden_objects::transaction::{ ExecutedTransaction, InputNote, InputNotes, - OrderedTransactionHeaders, OutputNote, PartialBlockchain, ProvenTransaction, - TransactionHeader, TransactionInputs, }; use miden_objects::{MAX_BATCHES_PER_BLOCK, MAX_OUTPUT_NOTES_PER_BATCH, NoteError}; @@ -41,6 +39,7 @@ use miden_processor::{DeserializationError, Word}; use miden_tx::LocalTransactionProver; use miden_tx::auth::BasicAuthenticator; use miden_tx::utils::{ByteReader, Deserializable, Serializable}; +use miden_tx_batch_prover::LocalBatchProver; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; use winterfell::ByteWriter; @@ -482,37 +481,8 @@ impl MockChain { &self, proposed_batch: ProposedBatch, ) -> anyhow::Result { - let ( - transactions, - block_header, - _partial_blockchain, - _unauthenticated_note_proofs, - id, - account_updates, - input_notes, - output_notes, - batch_expiration_block_num, - ) = proposed_batch.into_parts(); - - // SAFETY: This satisfies the requirements of the ordered tx headers. - let tx_headers = OrderedTransactionHeaders::new_unchecked( - transactions - .iter() - .map(AsRef::as_ref) - .map(TransactionHeader::from) - .collect::>(), - ); - - Ok(ProvenBatch::new( - id, - block_header.commitment(), - block_header.block_num(), - account_updates, - input_notes, - output_notes, - batch_expiration_block_num, - tx_headers, - )?) + let batch_prover = LocalBatchProver::new(0); + Ok(batch_prover.prove_dummy(proposed_batch)?) } // BLOCK APIS @@ -565,7 +535,7 @@ impl MockChain { &self, proposed_block: ProposedBlock, ) -> Result { - LocalBlockProver::new(0).prove_without_batch_verification(proposed_block) + LocalBlockProver::new(0).prove_dummy(proposed_block) } // TRANSACTION APIS diff --git a/crates/miden-tx-batch-prover/Cargo.toml b/crates/miden-tx-batch-prover/Cargo.toml index f7074cd34d..921d73533e 100644 --- a/crates/miden-tx-batch-prover/Cargo.toml +++ b/crates/miden-tx-batch-prover/Cargo.toml @@ -18,6 +18,7 @@ bench = false [features] default = ["std"] std = ["miden-objects/std", "miden-tx/std"] +testing = [] [dependencies] miden-objects = { workspace = true } diff --git a/crates/miden-tx-batch-prover/src/local_batch_prover.rs b/crates/miden-tx-batch-prover/src/local_batch_prover.rs index 73c8a05f71..6ad92a7f6f 100644 --- a/crates/miden-tx-batch-prover/src/local_batch_prover.rs +++ b/crates/miden-tx-batch-prover/src/local_batch_prover.rs @@ -27,9 +27,39 @@ impl LocalBatchProver { /// Returns an error if: /// - a proof of any transaction in the batch fails to verify. pub fn prove(&self, proposed_batch: ProposedBatch) -> Result { + let verifier = TransactionVerifier::new(self.proof_security_level); + + for tx in proposed_batch.transactions() { + verifier.verify(tx).map_err(|source| { + ProvenBatchError::TransactionVerificationFailed { + transaction_id: tx.id(), + source: Box::new(source), + } + })?; + } + + self.prove_inner(proposed_batch) + } + + /// Proves the provided [`ProposedBatch`] into a [`ProvenBatch`], **without verifying batches + /// and proving the block**. + /// + /// This is exposed for testing purposes. + #[cfg(any(feature = "testing", test))] + pub fn prove_dummy( + &self, + proposed_batch: ProposedBatch, + ) -> Result { + self.prove_inner(proposed_batch) + } + + /// Converts a proposed batch into a proven batch. + /// + /// For now, this doesn't do anything interesting. + fn prove_inner(&self, proposed_batch: ProposedBatch) -> Result { let tx_headers = proposed_batch.transaction_headers(); let ( - transactions, + _transactions, block_header, _block_chain, _authenticatable_unauthenticated_notes, @@ -40,17 +70,6 @@ impl LocalBatchProver { batch_expiration_block_num, ) = proposed_batch.into_parts(); - let verifier = TransactionVerifier::new(self.proof_security_level); - - for tx in transactions { - verifier.verify(&tx).map_err(|source| { - ProvenBatchError::TransactionVerificationFailed { - transaction_id: tx.id(), - source: Box::new(source), - } - })?; - } - ProvenBatch::new( id, block_header.commitment(), From e4912663276ab8eebb24b84d318417cb4ea0bba3 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Sat, 6 Sep 2025 07:36:41 +0300 Subject: [PATCH 020/133] feat: implement `account_get_native_id` and `account_get_native_nonce` kernel procedures (#1844) --- CHANGELOG.md | 1 + .../asm/kernels/transaction/api.masm | 61 +++- .../asm/kernels/transaction/lib/account.masm | 46 +-- .../transaction/lib/account_delta.masm | 2 +- .../asm/kernels/transaction/lib/epilogue.masm | 10 +- .../asm/kernels/transaction/lib/memory.masm | 134 ++++---- .../asm/kernels/transaction/lib/prologue.masm | 18 +- crates/miden-lib/asm/miden/account.masm | 59 +++- crates/miden-lib/asm/miden/asset.masm | 4 +- crates/miden-lib/asm/miden/faucet.masm | 6 + .../asm/miden/kernel_proc_offsets.masm | 116 ++++--- .../miden-lib/src/errors/tx_kernel_errors.rs | 2 - .../src/transaction/kernel_procedures.rs | 6 +- .../miden-objects/src/account/builder/mod.rs | 20 +- .../kernel_tests/tx/test_account_interface.rs | 8 +- .../src/kernel_tests/tx/test_asset_vault.rs | 2 +- .../src/kernel_tests/tx/test_epilogue.rs | 2 +- .../src/kernel_tests/tx/test_fpi.rs | 308 +++++++++++++++--- docs/src/protocol_library.md | 8 +- 19 files changed, 599 insertions(+), 214 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90539c2c8a..3e5c0dd97b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). - Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). +- Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). - Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). ### Changes diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index c9061019b4..3ff973e341 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -140,7 +140,7 @@ export.account_compute_delta_commitment # => [ACCOUNT_COMMITMENT, pad(12)] end -#! Returns the account ID of the current account. +#! Returns the ID of the current account. #! #! Inputs: [pad(16)] #! Outputs: [account_id_prefix, account_id_suffix, pad(14)] @@ -151,7 +151,7 @@ end #! #! Invocation: dynexec export.account_get_id - # get the account ID + # get the current account ID exec.account::get_id # => [account_id_prefix, account_id_suffix, pad(16)] @@ -160,7 +160,27 @@ export.account_get_id # => [account_id_prefix, account_id_suffix, pad(14)] end -#! Returns the nonce of the current account. +#! Returns the native account ID. +#! +#! Inputs: [pad(16)] +#! Outputs: [account_id_prefix, account_id_suffix, pad(14)] +#! +#! Where: +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the native account ID of the +#! transaction. +#! +#! Invocation: dynexec +export.account_get_native_id + # get the native account ID + exec.memory::get_native_account_id + # => [account_id_prefix, account_id_suffix, pad(16)] + + # truncate the stack + movup.2 drop movup.2 drop + # => [account_id_prefix, account_id_suffix, pad(14)] +end + +#! Returns the current account nonce. #! #! Inputs: [pad(16)] #! Outputs: [nonce, pad(15)] @@ -179,6 +199,25 @@ export.account_get_nonce # => [nonce, pad(15)] end +#! Returns the native account nonce. +#! +#! Inputs: [pad(16)] +#! Outputs: [native_nonce, pad(15)] +#! +#! Where: +#! - native_nonce is the nonce of the native account. +#! +#! Invocation: dynexec +export.account_get_native_nonce + # get the native account nonce + exec.memory::get_native_account_nonce + # => [native_nonce, pad(16)] + + # truncate the stack + swap drop + # => [native_nonce, pad(15)] +end + #! Increments the account nonce by one and returns the new nonce. #! #! Inputs: [pad(16)] @@ -445,7 +484,7 @@ end #! Invocation: dynexec export.account_get_vault_root # fetch the account vault root - exec.memory::get_acct_vault_root + exec.memory::get_account_vault_root # => [VAULT_ROOT, pad(16)] # truncate the stack @@ -532,8 +571,8 @@ end #! Invocation: dynexec export.account_get_balance # get the vault root - exec.memory::get_acct_vault_root_ptr movdn.2 - # => [faucet_id_prefix, faucet_id_suffix, acct_vault_root_ptr, pad(14)] + exec.memory::get_account_vault_root_ptr movdn.2 + # => [faucet_id_prefix, faucet_id_suffix, account_vault_root_ptr, pad(14)] # get the asset balance exec.asset_vault::get_balance @@ -555,7 +594,7 @@ end #! Invocation: dynexec export.account_has_non_fungible_asset # get the vault root - exec.memory::get_acct_vault_root_ptr movdn.4 + exec.memory::get_account_vault_root_ptr movdn.4 # => [ASSET, vault_root_ptr, pad(12)] # check if the account vault has the non-fungible asset @@ -1234,12 +1273,12 @@ export.tx_start_foreign_context # store the id and nonce of the foreign account to the memory dropw adv_loadw - exec.memory::set_acct_id_and_nonce dropw + exec.memory::set_account_id_and_nonce dropw # OS => [pad(16)] # AS => [VAULT_ROOT, STORAGE_ROOT, CODE_ROOT] # store the vault root of the foreign account to the memory - adv_loadw exec.memory::set_acct_vault_root dropw + adv_loadw exec.memory::set_account_vault_root dropw # OS => [pad(16)] # AS => [STORAGE_ROOT, CODE_ROOT] @@ -1249,7 +1288,7 @@ export.tx_start_foreign_context # AS => [] # store the code root into the memory - exec.memory::set_acct_code_commitment + exec.memory::set_account_code_commitment # OS => [CODE_ROOT, STORAGE_ROOT, pad(16)] # AS => [] @@ -1259,7 +1298,7 @@ export.tx_start_foreign_context # AS => [] # store the storage root to the memory - exec.memory::set_acct_storage_commitment + exec.memory::set_account_storage_commitment # OS => [STORAGE_ROOT, pad(16)] # AS => [] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 2d864cacf2..0ef622a010 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -248,7 +248,7 @@ export.incr_nonce # emit event to signal that account nonce is being incremented emit.ACCOUNT_BEFORE_INCREMENT_NONCE_EVENT - exec.memory::get_acct_nonce + exec.memory::get_account_nonce # => [current_nonce] # if the current nonce is the maximum felt value, then incrementing the nonce would overflow @@ -259,7 +259,7 @@ export.incr_nonce add.1 # => [new_nonce] - dup exec.memory::set_acct_nonce + dup exec.memory::set_account_nonce # => [new_nonce] emit.ACCOUNT_AFTER_INCREMENT_NONCE_EVENT @@ -283,7 +283,7 @@ export.memory::get_account_id->get_id #! #! Where: #! - nonce is the account nonce. -export.memory::get_acct_nonce->get_nonce +export.memory::get_account_nonce->get_nonce #! Returns the native account commitment at the beginning of the transaction. #! @@ -319,7 +319,7 @@ export.memory::get_init_account_storage_commitment->get_initial_storage_commitme #! #! Where: #! - CODE_COMMITMENT is the commitment of the account code. -export.memory::get_acct_code_commitment->get_code_commitment +export.memory::get_account_code_commitment->get_code_commitment #! Computes the storage commitment of the current account. #! @@ -333,7 +333,7 @@ export.compute_storage_commitment exec.refresh_storage_commitment # return the storage commitment - exec.memory::get_acct_storage_commitment + exec.memory::get_account_storage_commitment # => [STORAGE_COMMITMENT] end @@ -457,7 +457,7 @@ end #! - VALUE is the value of the item. export.get_item # get account storage slots section offset - exec.memory::get_acct_storage_slots_section_ptr + exec.memory::get_account_storage_slots_section_ptr # => [acct_storage_slots_section_offset, index] # get the item from storage @@ -650,7 +650,7 @@ export.get_storage_slot_type dup exec.memory::get_num_storage_slots lt assert.err=ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS # => [index] - exec.memory::get_acct_storage_slots_section_ptr + exec.memory::get_account_storage_slots_section_ptr # => [curr_account_storage_slots_section_ptr, index] exec.memory::get_storage_slot_type @@ -676,7 +676,7 @@ export.get_procedure_info # => [index] # get procedure pointer - exec.memory::get_acct_procedure_ptr + exec.memory::get_account_procedure_ptr # => [proc_ptr] # get metadata pointer @@ -789,7 +789,7 @@ export.validate_seed # => [SEED, 0, 0, 0, 0, EMPTY_WORD] # populate last four elements of the hasher rate with the code commitment - exec.memory::get_acct_code_commitment + exec.memory::get_account_code_commitment # => [CODE_COMMITMENT, SEED, 0, 0, 0, 0, EMPTY_WORD] # perform first permutation of seed and code_commitment (from advice stack) @@ -802,7 +802,7 @@ export.validate_seed # => [PERM, EMPTY_WORD] # perform second permutation perm(storage_commitment, 0, 0, 0, 0) - swapw exec.memory::get_acct_storage_commitment swapw + swapw exec.memory::get_account_storage_commitment swapw # => [EMPTY_WORD, STORAGE_COMMITMENT, PERM] hperm @@ -858,7 +858,7 @@ export.add_asset_to_vault # => [ASSET, ASSET] # fetch the account vault root - exec.memory::get_acct_vault_root_ptr movdn.4 + exec.memory::get_account_vault_root_ptr movdn.4 # => [ASSET, acct_vault_root_ptr, ASSET] # add the asset to the account vault @@ -894,7 +894,7 @@ export.remove_asset_from_vault # => [ASSET] # fetch the vault root - exec.memory::get_acct_vault_root_ptr movdn.4 + exec.memory::get_account_vault_root_ptr movdn.4 # => [ASSET, acct_vault_root_ptr] # remove the asset from the account vault @@ -954,7 +954,7 @@ export.save_account_storage_data # AS => [[STORAGE_SLOT_DATA]] # setup acct_storage_slots_ptr and end_ptr for reading from advice stack - mul.8 exec.memory::get_acct_storage_slots_section_ptr dup movdn.2 add swap + mul.8 exec.memory::get_account_storage_slots_section_ptr dup movdn.2 add swap # OS => [acct_storage_slots_ptr, end_ptr, STORAGE_COMMITMENT] # AS => [[STORAGE_SLOT_DATA]] @@ -1028,8 +1028,8 @@ export.save_account_procedure_data # AS => [[ACCOUNT_PROCEDURE_DATA]] # setup acct_proc_offset and end_ptr for reading from advice stack - exec.memory::get_acct_procedure_ptr - push.0 exec.memory::get_acct_procedure_ptr + exec.memory::get_account_procedure_ptr + push.0 exec.memory::get_account_procedure_ptr # OS => [acct_proc_offset, end_ptr, CODE_COMMITMENT] # AS => [[ACCOUNT_PROCEDURE_DATA]] @@ -1078,7 +1078,7 @@ proc.set_item_raw # => [index, NEW_VALUE, OLD_VALUE] # get account storage slots section offset - exec.memory::get_acct_storage_slots_section_ptr + exec.memory::get_account_storage_slots_section_ptr # => [acct_storage_slots_section_offset, index, NEW_VALUE, OLD_VALUE] # update storage @@ -1111,7 +1111,7 @@ proc.get_procedure_root # => [index] # get procedure pointer - exec.memory::get_acct_procedure_ptr + exec.memory::get_account_procedure_ptr # => [proc_ptr] # load procedure root from memory @@ -1132,7 +1132,7 @@ end #! - storage_size is the number of storage slots the procedure is allowed to access. proc.get_procedure_metadata # get procedure storage metadata pointer - padw exec.memory::get_acct_procedure_ptr add.4 + padw exec.memory::get_account_procedure_ptr add.4 # => [storage_offset_ptr, EMPTY_WORD] # load procedure metadata from memory and keep relevant data @@ -1220,7 +1220,7 @@ end #! - the hash of the current account is not represented in the account database. export.validate_current_foreign_account # get the account database root - exec.memory::get_acct_db_root + exec.memory::get_account_db_root # => [ACCOUNT_DB_ROOT] # get the current account ID @@ -1279,7 +1279,7 @@ proc.refresh_storage_commitment # => [num_storage_slots] # setup start and end ptr - mul.8 exec.memory::get_acct_storage_slots_section_ptr dup movdn.2 add swap + mul.8 exec.memory::get_account_storage_slots_section_ptr dup movdn.2 add swap # => [start_ptr, end_ptr] # pad stack to read and hash from memory @@ -1299,7 +1299,7 @@ proc.refresh_storage_commitment # => [DIGEST] # set new account storage commitment - exec.memory::set_acct_storage_commitment dropw + exec.memory::set_account_storage_commitment dropw # => [] # update the storage commitment dirty flag, indicating that the commitment is up-to-date @@ -1340,7 +1340,7 @@ export.was_procedure_called assert_eqw.err=ERR_ACCOUNT_PROC_NOT_PART_OF_ACCOUNT_CODE # => [index] - exec.memory::get_acct_procedures_call_tracking_ptr + exec.memory::get_account_procedures_call_tracking_ptr # => [was_called_offset, index] # load the value of was_called @@ -1358,7 +1358,7 @@ end #! Inputs: [proc_idx] #! Outputs: [] export.set_was_procedure_called - exec.memory::get_acct_procedures_call_tracking_ptr + exec.memory::get_account_procedures_call_tracking_ptr # => [was_called_offset, proc_idx] # save 1 to the was_called address diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm index 5d56f5d39e..426418f990 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm @@ -496,7 +496,7 @@ export.was_nonce_incremented exec.memory::get_init_nonce # => [init_nonce] - exec.memory::get_acct_nonce + exec.memory::get_account_nonce # => [current_nonce, init_nonce] neq diff --git a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm index cb5e9e62f0..457206d1c5 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -12,8 +12,6 @@ use.std::word # ERRORS # ================================================================================================= -const.ERR_ACCOUNT_NONCE_DID_NOT_INCREASE_AFTER_STATE_CHANGE="account nonce did not increase after a state changing transaction" - const.ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME="total number of assets in the account and all involved notes must stay the same" const.ERR_EPILOGUE_EXECUTED_TRANSACTION_IS_EMPTY="executed transaction neither changed the account state, nor consumed any notes" @@ -115,7 +113,7 @@ end #! Outputs: [] proc.build_output_vault # copy final account vault root to output account vault root - exec.memory::get_acct_vault_root exec.memory::set_output_vault_root dropw + exec.memory::get_account_vault_root exec.memory::set_output_vault_root dropw # => [] # get the number of output notes from memory @@ -206,7 +204,7 @@ proc.execute_auth_procedure # => [AUTH_ARGS, pad(12)] # auth procedure is at index 0 within the account procedures section. - push.0 exec.memory::get_acct_procedure_ptr + push.0 exec.memory::get_account_procedure_ptr # => [auth_procedure_ptr, AUTH_ARGS, pad(12)] padw dup.4 mem_loadw @@ -321,7 +319,7 @@ proc.compute_and_remove_fee # are essentially ignored. # fetch the vault root - exec.memory::get_acct_vault_root_ptr movdn.4 + exec.memory::get_account_vault_root_ptr movdn.4 # => [FEE_ASSET, acct_vault_root_ptr] # remove the asset from the account vault @@ -444,7 +442,7 @@ export.finalize_transaction.12 # ------ Insert final account data into advice provider ------ # get the offset for the end of the account data section - exec.memory::get_core_acct_data_end_ptr + exec.memory::get_core_account_data_end_ptr # => [acct_data_end_ptr] # get the offset for the start of the account data section diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index de1406e5d9..41ffe132ed 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -411,14 +411,14 @@ end #! Sets the ID of the native account. #! -#! Inputs: [acct_id_prefix, acct_id_suffix] +#! Inputs: [account_id_prefix, account_id_suffix] #! Outputs: [] #! #! Where: -#! - acct_id_{prefix,suffix} are the prefix and suffix felts of the account ID. -export.set_global_acct_id +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the account ID. +export.set_global_account_id push.0.0 - # => [0, 0, acct_id_prefix, acct_id_suffix] + # => [0, 0, account_id_prefix, account_id_suffix] mem_storew.NATIVE_ACCT_ID_PTR dropw # => [] @@ -427,13 +427,13 @@ end #! Returns the ID of the native account. #! #! Inputs: [] -#! Outputs: [acct_id_prefix, acct_id_suffix] +#! Outputs: [account_id_prefix, account_id_suffix] #! #! Where: -#! - acct_id_{prefix,suffix} are the prefix and suffix felts of the account ID. -export.get_global_acct_id +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the account ID. +export.get_global_account_id padw mem_loadw.NATIVE_ACCT_ID_PTR - # => [0, 0, acct_id_prefix, acct_id_suffix] + # => [0, 0, account_id_prefix, account_id_suffix] drop drop end @@ -720,7 +720,7 @@ end #! #! Where: #! - ACCT_DB_ROOT is the account database root of the transaction reference block. -export.get_acct_db_root +export.get_account_db_root padw mem_loadw.ACCT_DB_ROOT_PTR end @@ -843,10 +843,10 @@ end #! Returns the length of the memory interval that the account data occupies. #! #! Inputs: [] -#! Outputs: [acct_data_length] +#! Outputs: [account_data_length] #! #! Where: -#! - acct_data_length is the length of the memory interval that the account data occupies. +#! - account_data_length is the length of the memory interval that the account data occupies. export.get_account_data_length push.ACCOUNT_DATA_LENGTH end @@ -854,10 +854,10 @@ end #! Returns the largest memory address which can be used to load the foreign account data. #! #! Inputs: [] -#! Outputs: [max_foreign_acct_ptr] +#! Outputs: [max_foreign_account_ptr] #! #! Where: -#! - max_foreign_acct_ptr is the largest memory address which can be used to load the foreign +#! - max_foreign_account_ptr is the largest memory address which can be used to load the foreign #! account data. export.get_max_foreign_account_ptr push.MAX_FOREIGN_ACCOUNT_PTR @@ -872,11 +872,11 @@ export.set_current_account_data_ptr_to_native_account # stored push.ACCOUNT_STACK_TOP_PTR push.MIN_ACCOUNT_STACK_PTR - # => [native_acct_stack_ptr, account_stack_top_ptr] + # => [native_account_stack_ptr, account_stack_top_ptr] # store the native account data pointer into the first stack element. push.NATIVE_ACCOUNT_DATA_PTR dup.1 mem_store - # => [native_acct_stack_ptr, account_stack_top_ptr] + # => [native_account_stack_ptr, account_stack_top_ptr] # store the pointer to the first account stack element at the account stack top # pointer. @@ -989,80 +989,92 @@ end #! #! Where: #! - ptr is the memory address at which the core account data ends. -export.get_core_acct_data_end_ptr +export.get_core_account_data_end_ptr exec.get_current_account_data_ptr add.ACCT_CORE_DATA_SECTION_END_OFFSET end ### ACCOUNT ID AND NONCE ################################################# -#! Returns the id of the current account. +#! Returns the ID of the current account. #! #! Inputs: [] -#! Outputs: [curr_acct_id_prefix, curr_acct_id_suffix] +#! Outputs: [account_id_prefix, account_id_suffix] #! #! Where: -#! - curr_acct_id_{prefix,suffix} are the prefix and suffix felts of the account ID of the currently +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the account ID of the currently #! accessing account. export.get_account_id padw exec.get_current_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET mem_loadw - # => [nonce, 0, curr_acct_id_prefix, curr_acct_id_suffix] + # => [nonce, 0, account_id_prefix, account_id_suffix] drop drop - # => [curr_acct_id_prefix, curr_acct_id_suffix] + # => [account_id_prefix, account_id_suffix] +end + +#! Returns the ID of the native account of the transaction. +#! +#! Inputs: [] +#! Outputs: [account_id_prefix, account_id_suffix] +#! +#! Where: +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the native account +#! of the transaction. +export.get_native_account_id + padw push.NATIVE_ACCOUNT_DATA_PTR add.ACCT_ID_AND_NONCE_OFFSET mem_loadw + # => [nonce, 0, account_id_prefix, account_id_suffix] + drop drop + # => [account_id_prefix, account_id_suffix] end #! Sets the account ID and nonce. #! -#! Inputs: [account_nonce, 0, account_id_prefix, account_id_suffix] -#! Outputs: [account_nonce, 0, account_id_prefix, account_id_suffix] +#! Inputs: [nonce, 0, account_id_prefix, account_id_suffix] +#! Outputs: [nonce, 0, account_id_prefix, account_id_suffix] #! #! Where: -#! - account_id_{prefix,suffix} are the prefix and suffix felts of the id of the currently accessing +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the currently accessing #! account. -#! - account_nonce is the nonce of the currently accessing account. -export.set_acct_id_and_nonce +#! - nonce is the nonce of the currently accessing account. +export.set_account_id_and_nonce exec.get_current_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET mem_storew end -#! Returns the id of the native account. +#! Returns the nonce of the current account. #! #! Inputs: [] -#! Outputs: [native_acct_id_prefix, native_acct_id_suffix] +#! Outputs: [nonce] #! #! Where: -#! - native_acct_id_{prefix,suffix} are the prefix and suffix felts of the id of the native account. -export.get_native_account_id - padw push.NATIVE_ACCOUNT_DATA_PTR add.ACCT_ID_AND_NONCE_OFFSET mem_loadw - # => [nonce, 0, native_acct_id_prefix, native_acct_id_suffix] - drop drop - # => [native_acct_id_prefix, native_acct_id_suffix] +#! - nonce is the nonce of the current account. +export.get_account_nonce + exec.get_current_account_data_ptr add.ACCT_NONCE_OFFSET + mem_load end -#! Returns the account nonce. +#! Returns the nonce of the native account of the transaction. #! #! Inputs: [] -#! Outputs: [acct_nonce] +#! Outputs: [nonce] #! #! Where: -#! - acct_nonce is the account nonce. - -export.get_acct_nonce - exec.get_current_account_data_ptr add.ACCT_NONCE_OFFSET +#! - nonce is the nonce of the native account of the transaction. +export.get_native_account_nonce + push.NATIVE_ACCOUNT_DATA_PTR add.ACCT_NONCE_OFFSET mem_load end -#! Sets the account nonce. +#! Sets the nonce of the current account. #! -#! Inputs: [acct_nonce] +#! Inputs: [nonce] #! Outputs: [] #! #! Where: -#! - acct_nonce is the account nonce. -export.set_acct_nonce +#! - nonce is the nonce of the current account. +export.set_account_nonce exec.get_current_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET padw - # => [0, 0, 0, 0, acct_id_and_nonce_ptr, new_nonce] + # => [0, 0, 0, 0, account_id_and_nonce_ptr, new_nonce] dup.4 mem_loadw - # => [old_nonce, 0, old_id_prefix, old_id_suffix, acct_id_and_nonce_ptr, new_nonce] + # => [old_nonce, 0, old_id_prefix, old_id_suffix, account_id_and_nonce_ptr, new_nonce] drop movup.4 movup.4 mem_storew dropw # => [] end @@ -1072,11 +1084,11 @@ end #! Returns the memory pointer to the account vault root. #! #! Inputs: [] -#! Outputs: [acct_vault_root_ptr] +#! Outputs: [account_vault_root_ptr] #! #! Where: -#! - acct_vault_root_ptr is the memory pointer to the account asset vault root. -export.get_acct_vault_root_ptr +#! - account_vault_root_ptr is the memory pointer to the account asset vault root. +export.get_account_vault_root_ptr exec.get_current_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET end @@ -1087,7 +1099,7 @@ end #! #! Where: #! - ACCT_VAULT_ROOT is the account asset vault root. -export.get_acct_vault_root +export.get_account_vault_root padw exec.get_current_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET mem_loadw @@ -1100,7 +1112,7 @@ end #! #! Where: #! - ACCT_VAULT_ROOT is the account vault root to be set. -export.set_acct_vault_root +export.set_account_vault_root exec.get_current_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET mem_storew end @@ -1114,7 +1126,7 @@ end #! #! Where: #! - CODE_COMMITMENT is the code commitment of the account. -export.get_acct_code_commitment +export.get_account_code_commitment padw exec.get_current_account_data_ptr add.ACCT_CODE_COMMITMENT_OFFSET mem_loadw @@ -1127,7 +1139,7 @@ end #! #! Where: #! - CODE_COMMITMENT is the code commitment to be set. -export.set_acct_code_commitment +export.set_account_code_commitment exec.get_current_account_data_ptr add.ACCT_CODE_COMMITMENT_OFFSET mem_storew end @@ -1185,7 +1197,7 @@ end #! #! Where: #! - account_procedures_section_ptr is the memory pointer to the account procedures section. -export.get_acct_procedures_section_ptr +export.get_account_procedures_section_ptr exec.get_current_account_data_ptr add.ACCT_PROCEDURES_SECTION_OFFSET end @@ -1196,7 +1208,7 @@ end #! #! Where: #! - procedures_call_tracking_ptr is the memory pointer to the procedure call tracking section. -export.get_acct_procedures_call_tracking_ptr +export.get_account_procedures_call_tracking_ptr exec.get_current_account_data_ptr add.ACCT_PROCEDURES_CALL_TRACKING_OFFSET end @@ -1208,8 +1220,8 @@ end #! Where: #! - proc_idx is the index of the account procedure. #! - proc_ptr is the memory pointer to the account procedure at the specified index. -export.get_acct_procedure_ptr - mul.8 exec.get_acct_procedures_section_ptr add +export.get_account_procedure_ptr + mul.8 exec.get_account_procedures_section_ptr add end ### ACCOUNT STORAGE ################################################# @@ -1221,7 +1233,7 @@ end #! #! Where: #! - STORAGE_COMMITMENT is the account storage commitment. -export.get_acct_storage_commitment +export.get_account_storage_commitment padw exec.get_current_account_data_ptr add.ACCT_STORAGE_COMMITMENT_OFFSET mem_loadw @@ -1234,7 +1246,7 @@ end #! #! Where: #! - STORAGE_COMMITMENT is the account storage commitment. -export.set_acct_storage_commitment +export.set_account_storage_commitment exec.get_current_account_data_ptr add.ACCT_STORAGE_COMMITMENT_OFFSET mem_storew end @@ -1304,7 +1316,7 @@ end #! Returns the type of the requested storage slot. #! -#! Inputs: [acct_storage_slots_section_ptr, index] +#! Inputs: [account_storage_slots_section_ptr, index] #! Outputs: [slot_type] #! #! Where: @@ -1323,7 +1335,7 @@ end #! #! Where: #! - storage_slots_section_ptr is the memory pointer to the account storage slots section. -export.get_acct_storage_slots_section_ptr +export.get_account_storage_slots_section_ptr exec.get_current_account_data_ptr add.ACCT_STORAGE_SLOTS_SECTION_OFFSET end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm index 945aac00a8..6313dff3f2 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -87,7 +87,7 @@ proc.process_global_inputs exec.memory::set_block_commitment dropw exec.memory::set_init_account_commitment dropw exec.memory::set_nullifier_commitment dropw - exec.memory::set_global_acct_id + exec.memory::set_global_account_id end # KERNEL DATA @@ -287,13 +287,13 @@ proc.validate_new_account # => [] # Assert the account nonce is 0 - exec.memory::get_acct_nonce eq.0 assert.err=ERR_PROLOGUE_NEW_ACCOUNT_NONCE_MUST_BE_ZERO + exec.memory::get_account_nonce eq.0 assert.err=ERR_PROLOGUE_NEW_ACCOUNT_NONCE_MUST_BE_ZERO # => [] # Assert the initial vault is empty # --------------------------------------------------------------------------------------------- # get the account vault root - exec.memory::get_acct_vault_root + exec.memory::get_account_vault_root # => [ACCT_VAULT_ROOT] # push empty vault root onto stack @@ -417,32 +417,32 @@ proc.process_account_data # => [ACCOUNT_COMMITMENT] # assert the account ID matches the account ID in global inputs - exec.memory::get_global_acct_id + exec.memory::get_global_account_id exec.memory::get_account_id exec.account_id::is_equal assert.err=ERR_PROLOGUE_MISMATCH_OF_ACCOUNT_IDS_FROM_GLOBAL_INPUTS_AND_ADVICE_PROVIDER # => [ACCOUNT_COMMITMENT] # store a copy of the initial nonce in global inputs - exec.memory::get_acct_nonce + exec.memory::get_account_nonce exec.memory::set_init_nonce # => [ACCOUNT_COMMITMENT] # validates and stores account storage slots in memory # account storage commitment is also stored as an initial one in the global inputs - exec.memory::get_acct_storage_commitment + exec.memory::get_account_storage_commitment exec.memory::set_init_account_storage_commitment exec.account::save_account_storage_data # => [ACCOUNT_COMMITMENT] # validates and stores account procedures in memory. - exec.memory::get_acct_code_commitment + exec.memory::get_account_code_commitment exec.account::save_account_procedure_data # => [ACCOUNT_COMMITMENT] # copy the initial account vault root to the input vault root to support transaction asset # invariant checking # this account vault root is also stored as an initial one in the global inputs - exec.memory::get_acct_vault_root + exec.memory::get_account_vault_root exec.memory::set_init_account_vault_root exec.memory::set_input_vault_root dropw # => [ACCOUNT_COMMITMENT] @@ -474,7 +474,7 @@ proc.process_account_data # => [] # assert the nonce of an existing account is non-zero - exec.memory::get_acct_nonce neq.0 + exec.memory::get_account_nonce neq.0 assert.err=ERR_PROLOGUE_EXISTING_ACCOUNT_MUST_HAVE_NON_ZERO_NONCE # => [] end diff --git a/crates/miden-lib/asm/miden/account.masm b/crates/miden-lib/asm/miden/account.masm index 018bedd9d9..06cbe70748 100644 --- a/crates/miden-lib/asm/miden/account.masm +++ b/crates/miden-lib/asm/miden/account.masm @@ -3,7 +3,7 @@ use.miden::kernel_proc_offsets # NATIVE ACCOUNT PROCEDURES # ================================================================================================= -#! Returns the account ID of the current account. +#! Returns the ID of the current account. #! #! Inputs: [] #! Outputs: [account_id_prefix, account_id_suffix] @@ -32,6 +32,35 @@ export.get_id # => [account_id_prefix, account_id_suffix] end +#! Returns the ID of the native account of the transaction. +#! +#! Inputs: [] +#! Outputs: [account_id_prefix, account_id_suffix] +#! +#! Where: +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the native account ID of the +#! transaction. +#! +#! Invocation: exec +export.get_native_id + # start padding the stack + push.0.0.0 + + exec.kernel_proc_offsets::account_get_native_id_offset + # => [offset, 0, 0, 0] + + # pad the stack + padw swapw padw padw swapdw + # => [offset, pad(15)] + + syscall.exec_kernel_proc + # => [account_id_prefix, account_id_suffix, pad(14)] + + # clean the stack + swapdw dropw dropw swapw dropw movdn.3 movdn.3 drop drop + # => [account_id_prefix, account_id_suffix] +end + #! Returns the nonce of the current account. #! #! This procedure always returns the initial account nonce, as the nonce can only be incremented @@ -64,6 +93,34 @@ export.get_nonce # => [nonce] end +#! Returns the nonce of the native account of the transaction. +#! +#! Inputs: [] +#! Outputs: [nonce] +#! +#! Where: +#! - nonce is the nonce of the native account of the transaction. +#! +#! Invocation: exec +export.get_native_nonce + # start padding the stack + push.0.0.0 + + exec.kernel_proc_offsets::account_get_native_nonce_offset + # => [offset, 0, 0, 0] + + # pad the stack + padw swapw padw padw swapdw + # => [offset, pad(15)] + + syscall.exec_kernel_proc + # => [nonce, pad(15)] + + # clean the stack + swapdw dropw dropw swapw dropw movdn.3 drop drop drop + # => [nonce] +end + #! Returns the native account commitment at the beginning of the transaction. #! #! Inputs: [] diff --git a/crates/miden-lib/asm/miden/asset.masm b/crates/miden-lib/asm/miden/asset.masm index d967bcc195..ff4bc9742e 100644 --- a/crates/miden-lib/asm/miden/asset.masm +++ b/crates/miden-lib/asm/miden/asset.masm @@ -24,7 +24,7 @@ const.ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID="failed to build the #! - amount is the amount of the asset to create. #! - ASSET is the built fungible asset. #! -#! Annotation hint: is not used anywhere except this file +#! Invocation: exec export.build_fungible_asset # assert the faucet is a fungible faucet dup exec.account_id::is_fungible_faucet assert.err=ERR_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID @@ -51,7 +51,7 @@ end #! - DATA_HASH is the data hash of the non-fungible asset to build. #! - ASSET is the built non-fungible asset. #! -#! Annotation hint: is not used anywhere except this file +#! Invocation: exec export.build_non_fungible_asset # assert the faucet is a non-fungible faucet dup exec.account_id::is_non_fungible_faucet diff --git a/crates/miden-lib/asm/miden/faucet.masm b/crates/miden-lib/asm/miden/faucet.masm index 9fc139d6e9..f18f26b8a4 100644 --- a/crates/miden-lib/asm/miden/faucet.masm +++ b/crates/miden-lib/asm/miden/faucet.masm @@ -11,6 +11,9 @@ use.miden::kernel_proc_offsets #! - amount is the amount of the asset to create. #! - ASSET is the created fungible asset. #! +#! Panics if: +#! - the current account is not a fungible faucet. +#! #! Invocation: exec export.create_fungible_asset # fetch the id of the faucet the transaction is being executed against. @@ -31,6 +34,9 @@ end #! - DATA_HASH is the data hash of the non-fungible asset to create. #! - ASSET is the created non-fungible asset. #! +#! Panics if: +#! - the current account is not a non-fungible faucet. +#! #! Invocation: exec export.create_non_fungible_asset # get the id of the faucet the transaction is being executed against diff --git a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm index b998afdd1b..efa5dea66f 100644 --- a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm +++ b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm @@ -8,87 +8,89 @@ const.ACCOUNT_COMPUTE_CURRENT_COMMITMENT_OFFSET=1 # ID const.ACCOUNT_GET_ID_OFFSET=2 +const.ACCOUNT_GET_NATIVE_ID_OFFSET=3 # Nonce -const.ACCOUNT_GET_NONCE_OFFSET=3 # accessor -const.ACCOUNT_INCR_NONCE_OFFSET=4 # mutator +const.ACCOUNT_GET_NONCE_OFFSET=4 # accessor +const.ACCOUNT_INCR_NONCE_OFFSET=5 # mutator +const.ACCOUNT_GET_NATIVE_NONCE_OFFSET=6 # Code -const.ACCOUNT_GET_CODE_COMMITMENT_OFFSET=5 +const.ACCOUNT_GET_CODE_COMMITMENT_OFFSET=7 # Storage -const.ACCOUNT_GET_INITIAL_STORAGE_COMMITMENT_OFFSET=6 -const.ACCOUNT_COMPUTE_STORAGE_COMMITMENT_OFFSET=7 -const.ACCOUNT_GET_ITEM_OFFSET=8 -const.ACCOUNT_SET_ITEM_OFFSET=9 -const.ACCOUNT_GET_MAP_ITEM_OFFSET=10 -const.ACCOUNT_SET_MAP_ITEM_OFFSET=11 +const.ACCOUNT_GET_INITIAL_STORAGE_COMMITMENT_OFFSET=8 +const.ACCOUNT_COMPUTE_STORAGE_COMMITMENT_OFFSET=9 +const.ACCOUNT_GET_ITEM_OFFSET=10 +const.ACCOUNT_SET_ITEM_OFFSET=11 +const.ACCOUNT_GET_MAP_ITEM_OFFSET=12 +const.ACCOUNT_SET_MAP_ITEM_OFFSET=13 # Vault -const.ACCOUNT_GET_INITIAL_VAULT_ROOT_OFFSET=12 -const.ACCOUNT_GET_VAULT_ROOT_OFFSET=13 -const.ACCOUNT_ADD_ASSET_OFFSET=14 -const.ACCOUNT_REMOVE_ASSET_OFFSET=15 -const.ACCOUNT_GET_BALANCE_OFFSET=16 -const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=17 +const.ACCOUNT_GET_INITIAL_VAULT_ROOT_OFFSET=14 +const.ACCOUNT_GET_VAULT_ROOT_OFFSET=15 +const.ACCOUNT_ADD_ASSET_OFFSET=16 +const.ACCOUNT_REMOVE_ASSET_OFFSET=17 +const.ACCOUNT_GET_BALANCE_OFFSET=18 +const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=19 # Delta -const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=18 +const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=20 # Procedure introspection -const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=19 +const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=21 ### Faucet ###################################### -const.FAUCET_MINT_ASSET_OFFSET=20 -const.FAUCET_BURN_ASSET_OFFSET=21 -const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=22 -const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=23 +const.FAUCET_MINT_ASSET_OFFSET=22 +const.FAUCET_BURN_ASSET_OFFSET=23 +const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=24 +const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=25 ### Note ######################################## # assets -const.NOTE_GET_ASSETS_INFO_OFFSET=24 # accessor -const.NOTE_ADD_ASSET_OFFSET=25 # mutator +const.NOTE_GET_ASSETS_INFO_OFFSET=26 # accessor +const.NOTE_ADD_ASSET_OFFSET=27 # mutator # note parameters -const.NOTE_GET_SERIAL_NUMBER_OFFSET=26 -const.NOTE_GET_INPUTS_COMMITMENT_AND_LEN_OFFSET=27 -const.NOTE_GET_SENDER_OFFSET=28 -const.NOTE_GET_SCRIPT_ROOT_OFFSET=29 +const.NOTE_GET_SERIAL_NUMBER_OFFSET=28 +const.NOTE_GET_INPUTS_COMMITMENT_AND_LEN_OFFSET=29 +const.NOTE_GET_SENDER_OFFSET=30 +const.NOTE_GET_SCRIPT_ROOT_OFFSET=31 # note introspection -const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=30 -const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=31 +const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=32 +const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=33 -const.INPUT_NOTE_GET_RECIPIENT_OFFSET=32 -const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=33 +const.INPUT_NOTE_GET_RECIPIENT_OFFSET=34 +const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=35 -const.INPUT_NOTE_GET_METADATA_OFFSET=34 -const.OUTPUT_NOTE_GET_METADATA_OFFSET=35 +const.INPUT_NOTE_GET_METADATA_OFFSET=36 +const.OUTPUT_NOTE_GET_METADATA_OFFSET=37 ### Tx ########################################## # creation -const.TX_CREATE_NOTE_OFFSET=36 +const.TX_CREATE_NOTE_OFFSET=38 # input/output notes -const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=37 -const.TX_GET_NUM_INPUT_NOTES_OFFSET=38 +const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=39 +const.TX_GET_NUM_INPUT_NOTES_OFFSET=40 -const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=39 -const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=40 +const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=41 +const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=42 # block info -const.TX_GET_BLOCK_COMMITMENT_OFFSET=41 -const.TX_GET_BLOCK_NUMBER_OFFSET=42 -const.TX_GET_BLOCK_TIMESTAMP_OFFSET=43 +const.TX_GET_BLOCK_COMMITMENT_OFFSET=43 +const.TX_GET_BLOCK_NUMBER_OFFSET=44 +const.TX_GET_BLOCK_TIMESTAMP_OFFSET=45 # foreign context -const.TX_START_FOREIGN_CONTEXT_OFFSET=44 -const.TX_END_FOREIGN_CONTEXT_OFFSET=45 +const.TX_START_FOREIGN_CONTEXT_OFFSET=46 +const.TX_END_FOREIGN_CONTEXT_OFFSET=47 # expiration data -const.TX_GET_EXPIRATION_DELTA_OFFSET=46 # accessor -const.TX_UPDATE_EXPIRATION_BLOCK_NUM_OFFSET=47 # mutator +const.TX_GET_EXPIRATION_DELTA_OFFSET=48 # accessor +const.TX_UPDATE_EXPIRATION_BLOCK_NUM_OFFSET=49 # mutator # ACCESSORS # ------------------------------------------------------------------------------------------------- @@ -143,6 +145,18 @@ export.account_get_id_offset push.ACCOUNT_GET_ID_OFFSET end +#! Returns the offset of the `account_get_native_id` kernel procedure. +#! +#! Inputs: [] +#! Outputs: [proc_offset] +#! +#! Where: +#! - proc_offset is the offset of the `account_get_native_id` kernel procedure required to get the +#! address where this procedure is stored. +export.account_get_native_id_offset + push.ACCOUNT_GET_NATIVE_ID_OFFSET +end + #! Returns the offset of the `account_get_nonce` kernel procedure. #! #! Inputs: [] @@ -167,6 +181,18 @@ export.account_incr_nonce_offset push.ACCOUNT_INCR_NONCE_OFFSET end +#! Returns the offset of the `account_get_native_nonce` kernel procedure. +#! +#! Inputs: [] +#! Outputs: [proc_offset] +#! +#! Where: +#! - proc_offset is the offset of the `account_get_native_nonce` kernel procedure required to get +#! the address where this procedure is stored. +export.account_get_native_nonce_offset + push.ACCOUNT_GET_NATIVE_NONCE_OFFSET +end + #! Returns the offset of the `account_get_code_commitment` kernel procedure. #! #! Inputs: [] diff --git a/crates/miden-lib/src/errors/tx_kernel_errors.rs b/crates/miden-lib/src/errors/tx_kernel_errors.rs index f640e4c7a0..3e07fa48f4 100644 --- a/crates/miden-lib/src/errors/tx_kernel_errors.rs +++ b/crates/miden-lib/src/errors/tx_kernel_errors.rs @@ -34,8 +34,6 @@ pub const ERR_ACCOUNT_IS_NOT_NATIVE: MasmError = MasmError::from_static_str("the pub const ERR_ACCOUNT_NONCE_AT_MAX: MasmError = MasmError::from_static_str("account nonce is already at its maximum possible value"); /// Error Message: "account nonce can only be incremented once" pub const ERR_ACCOUNT_NONCE_CAN_ONLY_BE_INCREMENTED_ONCE: MasmError = MasmError::from_static_str("account nonce can only be incremented once"); -/// Error Message: "account nonce did not increase after a state changing transaction" -pub const ERR_ACCOUNT_NONCE_DID_NOT_INCREASE_AFTER_STATE_CHANGE: MasmError = MasmError::from_static_str("account nonce did not increase after a state changing transaction"); /// Error Message: "provided procedure index is out of bounds" pub const ERR_ACCOUNT_PROC_INDEX_OUT_OF_BOUNDS: MasmError = MasmError::from_static_str("provided procedure index is out of bounds"); /// Error Message: "account procedure is not the authentication procedure; some procedures (e.g. `incr_nonce`) can be called only from the authentication procedure" diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index 1704fe5ec5..b49e19e8e7 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -6,17 +6,21 @@ use miden_objects::{Word, word}; // ================================================================================================ /// Hashes of all dynamically executed kernel procedures. -pub const KERNEL_PROCEDURES: [Word; 48] = [ +pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_initial_commitment word!("0x920898348bacd6d98a399301eb308478fd32b32eab019a5a6ef7a6b44abb61f6"), // account_compute_current_commitment word!("0x1aed40e2cc4d3798448f4efdce1a14c9598611da065eebe58432f144c3bca9de"), // account_get_id word!("0xc9f7e71b294e16d7a297ba283afb2f8c864817e40e73b6ef1d64efc310937fc7"), + // account_get_native_id + word!("0x05eb568956d0174066a1277442cc4602fcbbc6790bd64975416958d28274cb73"), // account_get_nonce word!("0x4a1f11db21ddb1f0ebf7c9fd244f896a95e99bb136008185da3e7d6aa85827a3"), // account_incr_nonce word!("0x72f4595fd7030542ab303c77be42962671948ef18ffeda49b0e88a374f0969f6"), + // account_get_native_nonce + word!("0xeae4dcae877e64a1951aa1ca35ac2adda724e359ee9c7689e55c42dde55d70c4"), // account_get_code_commitment word!("0x02e55aa37f40207bc2a3882383d4c0f1f6633b5f3ea5b7ef814d827632aa7ae8"), // account_get_initial_storage_commitment diff --git a/crates/miden-objects/src/account/builder/mod.rs b/crates/miden-objects/src/account/builder/mod.rs index ff173d7b80..70f0b39020 100644 --- a/crates/miden-objects/src/account/builder/mod.rs +++ b/crates/miden-objects/src/account/builder/mod.rs @@ -38,7 +38,7 @@ use crate::{AccountError, Felt, Word}; /// /// Under the `testing` feature, it is possible to: /// - Build an existing account using [`AccountBuilder::build_existing`] which will set the -/// account's nonce to `1`. +/// account's nonce to `1` by default, or to the configured value. /// - Add assets to the account's vault, however this will only succeed when using /// [`AccountBuilder::build_existing`]. /// @@ -56,6 +56,8 @@ use crate::{AccountError, Felt, Word}; pub struct AccountBuilder { #[cfg(any(feature = "testing", test))] assets: Vec, + #[cfg(any(feature = "testing", test))] + nonce: Option, components: Vec, auth_component: Option, account_type: AccountType, @@ -73,6 +75,8 @@ impl AccountBuilder { Self { #[cfg(any(feature = "testing", test))] assets: vec![], + #[cfg(any(feature = "testing", test))] + nonce: None, components: vec![], auth_component: None, init_seed, @@ -236,6 +240,15 @@ impl AccountBuilder { self } + /// Sets the nonce of an existing account. + /// + /// This method is optional. It must only be used when using [`Self::build_existing`] + /// instead of [`Self::build`] since new accounts must have a nonce of `0`. + pub fn nonce(mut self, nonce: Felt) -> Self { + self.nonce = Some(nonce); + self + } + /// Builds the account as an existing account, that is, with the nonce set to [`Felt::ONE`]. /// /// The [`AccountId`] is constructed by slightly modifying `init_seed[0..8]` to be a valid ID. @@ -255,7 +268,10 @@ impl AccountBuilder { ) }; - Ok(Account::from_parts(account_id, vault, storage, code, Felt::ONE)) + // Use the nonce value set by the Self::nonce method or Felt::ONE as a default. + let nonce = self.nonce.unwrap_or(Felt::ONE); + + Ok(Account::from_parts(account_id, vault, storage, code, nonce)) } } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index a80e458703..a3c3b6dfc5 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -72,9 +72,11 @@ async fn check_note_consumability_well_known_notes_success() -> anyhow::Result<( assert_matches!(consumption_info, NoteConsumptionInfo { successful, failed, .. } => { assert_eq!(successful.len(), notes.len()); - successful.iter().zip(notes.iter()).for_each(|(success, note)| { - assert_eq!(success, note); - }); + + // we asserted that `successful` and `notes` vectors have the same length, so it's safe to + // check their equality that way + successful.iter().for_each(|successful_note| assert!(notes.contains(successful_note))); + assert!(failed.is_empty()); }); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 5e559e638b..8a58eec648 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -73,7 +73,7 @@ fn peek_balance_returns_correct_amount() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - exec.memory::get_acct_vault_root_ptr + exec.memory::get_account_vault_root_ptr push.{suffix}.{prefix} # => [prefix, suffix, account_vault_root_ptr, balance] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 6138095ecf..e237183072 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -475,7 +475,7 @@ fn test_epilogue_increment_nonce_success() -> anyhow::Result<()> { # clean the stack dropw dropw dropw dropw - exec.memory::get_acct_nonce + exec.memory::get_account_nonce push.{expected_nonce} assert_eq end " diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index cc1e2347a6..d29200d925 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -34,6 +34,7 @@ use miden_objects::account::{ StorageSlot, }; use miden_objects::assembly::DefaultSourceManager; +use miden_objects::assembly::diagnostics::NamedSource; use miden_objects::testing::storage::STORAGE_LEAVES_2; use miden_objects::transaction::AccountInputs; use miden_processor::{AdviceInputs, Felt}; @@ -530,7 +531,7 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { "; let foreign_account_component = AccountComponent::compile( - foreign_account_code_source, + NamedSource::new("foreign_account", foreign_account_code_source), TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), storage_slots, )? @@ -538,7 +539,7 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) - .with_component(foreign_account_component) + .with_component(foreign_account_component.clone()) .build_existing()?; let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) @@ -568,8 +569,8 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { # push the index of desired storage item push.0 - # get the hash of the `get_item` account procedure - push.{get_item_foreign_hash} + # get the hash of the `get_item_foreign` account procedure + procref.::foreign_account::get_item_foreign # push the foreign account ID push.{foreign_suffix}.{foreign_prefix} @@ -594,7 +595,7 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { push.1 # get the hash of the `get_map_item_foreign` account procedure - push.{get_map_item_foreign_hash} + procref.::foreign_account::get_map_item_foreign # push the foreign account ID push.{foreign_suffix}.{foreign_prefix} @@ -613,24 +614,24 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { ", foreign_prefix = foreign_account.id().prefix().as_felt(), foreign_suffix = foreign_account.id().suffix(), - get_item_foreign_hash = foreign_account.code().procedures()[1].mast_root(), - get_map_item_foreign_hash = foreign_account.code().procedures()[2].mast_root(), map_key = STORAGE_LEAVES_2[0].0, ); - let tx_script = ScriptBuilder::default().compile_tx_script(code)?; + let tx_script = ScriptBuilder::default() + .with_dynamically_linked_library(foreign_account_component.library())? + .compile_tx_script(code)?; let foreign_account_inputs = mock_chain .get_foreign_account_inputs(foreign_account.id()) .expect("failed to get foreign account inputs"); - let tx_context = mock_chain + + mock_chain .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(vec![foreign_account_inputs]) .tx_script(tx_script) - .build()?; - - let _executed_transaction = tx_context.execute_blocking()?; + .build()? + .execute_blocking()?; Ok(()) } @@ -748,7 +749,7 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { "#; let first_foreign_account_component = AccountComponent::compile( - first_foreign_account_code_source, + NamedSource::new("first_foreign_account", first_foreign_account_code_source), TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), storage_slots, )? @@ -756,7 +757,7 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { let first_foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) - .with_component(first_foreign_account_component) + .with_component(first_foreign_account_component.clone()) .build_existing()?; // ------ NATIVE ACCOUNT --------------------------------------------------------------- @@ -813,8 +814,8 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { padw padw padw push.0.0.0 # => [pad(15)] - # get the hash of the `get_item` account procedure - push.{first_account_foreign_proc_hash} + # get the hash of the `first_account_foreign_proc` procedure + procref.::first_foreign_account::first_account_foreign_proc # push the foreign account ID push.{foreign_suffix}.{foreign_prefix} @@ -835,20 +836,21 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { "#, foreign_prefix = first_foreign_account.id().prefix().as_felt(), foreign_suffix = first_foreign_account.id().suffix(), - first_account_foreign_proc_hash = first_foreign_account.code().procedures()[1].mast_root(), ); - let tx_script = ScriptBuilder::default().compile_tx_script(code)?; + let tx_script = ScriptBuilder::default() + .with_dynamically_linked_library(first_foreign_account_component.library())? + .compile_tx_script(code)?; - let tx_context = mock_chain + mock_chain .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(foreign_account_inputs) .extend_advice_inputs(advice_inputs) .tx_script(tx_script) - .build()?; + .build()? + .execute_blocking()?; - let _executed_transaction = tx_context.execute_blocking()?; Ok(()) } @@ -1002,9 +1004,7 @@ fn test_nested_fpi_stack_overflow() { foreign_suffix = foreign_accounts.last().unwrap().id().suffix(), ); - - - let tx_script = ScriptBuilder::default().compile_tx_script(code).unwrap(); + let tx_script = ScriptBuilder::default().compile_tx_script(code).unwrap(); let tx_context = mock_chain .build_tx_context(native_account.id(), &[], &[]) @@ -1051,7 +1051,7 @@ fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { "; let foreign_account_component = AccountComponent::compile( - foreign_account_code_source, + NamedSource::new("foreign_account", foreign_account_code_source), TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), vec![], )? @@ -1059,7 +1059,7 @@ fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) - .with_component(foreign_account_component) + .with_component(foreign_account_component.clone()) .build_existing()?; // ------ NATIVE ACCOUNT --------------------------------------------------------------- @@ -1073,17 +1073,6 @@ fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { MockChainBuilder::with_accounts([native_account.clone(), foreign_account.clone()])? .build()?; mock_chain.prove_next_block().unwrap(); - let foreign_account_inputs = mock_chain - .get_foreign_account_inputs(foreign_account.id()) - .expect("failed to get foreign account inputs"); - - // push the hash of the native procedure and native account IDs to the advice stack to be able - // to call them dynamically. - let mut advice_inputs = AdviceInputs::default(); - advice_inputs.stack.extend(*native_account.code().procedures()[3].mast_root()); - advice_inputs - .stack - .extend([native_account.id().suffix(), native_account.id().prefix().as_felt()]); let code = format!( " @@ -1114,17 +1103,30 @@ fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { first_account_foreign_proc_hash = foreign_account.code().procedures()[1].mast_root(), ); - let tx_script = ScriptBuilder::default().compile_tx_script(code)?; + let tx_script = ScriptBuilder::default() + .with_dynamically_linked_library(foreign_account_component.library())? + .compile_tx_script(code)?; - let tx_context = mock_chain + let foreign_account_inputs = mock_chain + .get_foreign_account_inputs(foreign_account.id()) + .expect("failed to get foreign account inputs"); + + // push the hash of the native procedure and native account IDs to the advice stack to be able + // to call them dynamically. + let mut advice_inputs = AdviceInputs::default(); + advice_inputs.stack.extend(*native_account.code().procedures()[3].mast_root()); + advice_inputs + .stack + .extend([native_account.id().suffix(), native_account.id().prefix().as_felt()]); + + let result = mock_chain .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(vec![foreign_account_inputs]) .extend_advice_inputs(advice_inputs) .tx_script(tx_script) - .build()?; - - let result = tx_context.execute_blocking(); + .build()? + .execute_blocking(); assert_transaction_executor_error!(result, ERR_FOREIGN_ACCOUNT_CONTEXT_AGAINST_NATIVE_ACCOUNT); Ok(()) @@ -1241,6 +1243,228 @@ fn test_fpi_stale_account() -> anyhow::Result<()> { Ok(()) } +/// This test checks that our `miden::get_id` and `miden::get_native_id` procedures return IDs of +/// the current and native account respectively while being called from the foreign account. +#[test] +fn test_fpi_get_account_id() -> anyhow::Result<()> { + let foreign_account_code_source = " + use.miden::account + + export.get_current_and_native_ids + # get the ID of the current (foreign) account + exec.account::get_id + # => [acct_id_prefix, acct_id_suffix, pad(16)] + + # get the ID of the native account + exec.account::get_native_id + # => [native_acct_id_prefix, native_acct_id_suffix, acct_id_prefix, acct_id_suffix, pad(16)] + + # truncate the stack + swapw dropw + # => [native_acct_id_prefix, native_acct_id_suffix, acct_id_prefix, acct_id_suffix, pad(12)] + end + "; + + let foreign_account_component = AccountComponent::compile( + NamedSource::new("foreign_account", foreign_account_code_source), + TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), + Vec::new(), + )? + .with_supports_all_types(); + + let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(foreign_account_component.clone()) + .build_existing()?; + + let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(MockAccountComponent::with_empty_slots()) + .storage_mode(AccountStorageMode::Public) + .build_existing()?; + + let mut mock_chain = + MockChainBuilder::with_accounts([native_account.clone(), foreign_account.clone()])? + .build()?; + mock_chain.prove_next_block()?; + + let code = format!( + r#" + use.std::sys + + use.miden::tx + use.miden::account + use.miden::account_id + + begin + # get the IDs of the foreign and native accounts + # pad the stack for the `execute_foreign_procedure` execution + padw padw padw push.0.0.0 + # => [pad(15)] + + # get the hash of the `get_current_and_native_ids` foreign account procedure + procref.::foreign_account::get_current_and_native_ids + + # push the foreign account ID + push.{foreign_suffix}.{foreign_prefix} + # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, pad(15)] + + exec.tx::execute_foreign_procedure + # => [native_acct_id_prefix, native_acct_id_suffix, acct_id_prefix, acct_id_suffix] + + # push the expected native account ID and check that it is equal to the one returned + # from the FPI + push.{expected_native_suffix}.{expected_native_prefix} + exec.account_id::is_equal + assert.err="native account ID returned from the FPI is not equal to the expected one" + # => [acct_id_prefix, acct_id_suffix] + + # push the expected foreign account ID and check that it is equal to the one returned + # from the FPI + push.{foreign_suffix}.{foreign_prefix} + exec.account_id::is_equal + assert.err="foreign account ID returned from the FPI is not equal to the expected one" + # => [] + + # truncate the stack + exec.sys::truncate_stack + end + "#, + foreign_suffix = foreign_account.id().suffix(), + foreign_prefix = foreign_account.id().prefix().as_felt(), + expected_native_suffix = native_account.id().suffix(), + expected_native_prefix = native_account.id().prefix().as_felt(), + ); + + let tx_script = ScriptBuilder::default() + .with_dynamically_linked_library(foreign_account_component.library())? + .compile_tx_script(code)?; + + let foreign_account_inputs = mock_chain + .get_foreign_account_inputs(foreign_account.id()) + .expect("failed to get foreign account inputs"); + + mock_chain + .build_tx_context(native_account.id(), &[], &[]) + .expect("failed to build tx context") + .foreign_accounts(vec![foreign_account_inputs]) + .tx_script(tx_script) + .build()? + .execute_blocking()?; + + Ok(()) +} + +/// This test checks that our `miden::get_nonce` and `miden::get_native_nonce` procedures return +/// nonce values of the current and native account respectively while being called from the foreign +/// account. +#[test] +fn test_fpi_get_account_nonce() -> anyhow::Result<()> { + let foreign_account_code_source = " + use.miden::account + + export.get_current_and_native_nonce_values + # get the nonce of the current (foreign) account + exec.account::get_nonce + # => [nonce, pad(16)] + + # get the nonce of the native account + exec.account::get_native_nonce + # => [native_nonce, nonce, pad(16)] + + # truncate the stack + movdn.3 movdn.3 drop drop + # => [native_nonce, nonce, pad(14)] + end + "; + + let foreign_account_component = AccountComponent::compile( + foreign_account_code_source, + TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), + Vec::new(), + )? + .with_supports_all_types(); + + let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(foreign_account_component) + .build_existing()?; + + let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(MockAccountComponent::with_empty_slots()) + .storage_mode(AccountStorageMode::Public) + .nonce(Felt::new(2)) + .build_existing()?; + + let mut mock_chain = + MockChainBuilder::with_accounts([native_account.clone(), foreign_account.clone()])? + .build()?; + mock_chain.prove_next_block()?; + + let code = format!( + r#" + use.std::sys + + use.miden::tx + use.miden::account + + begin + # get the nonce values of the foreign and native accounts + # pad the stack for the `execute_foreign_procedure` execution + padw padw padw push.0.0.0 + # => [pad(15)] + + # get the hash of the `get_current_and_native_nonce_values` foreign account procedure + push.{get_current_and_native_nonce_values} + + # push the foreign account ID + push.{foreign_suffix}.{foreign_prefix} + # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, pad(15)] + + exec.tx::execute_foreign_procedure + # => [native_nonce, foreign_nonce] + + # push the expected native account nonce and check that it is equal to the one returned + # from the FPI + push.{expected_native_nonce} + assert_eq.err="native account nonce returned from the FPI is not equal to the expected one" + # => [foreign_nonce] + + # push the expected foreign account nonce and check that it is equal to the one returned + # from the FPI + push.{expected_foreign_nonce} + assert_eq.err="foreign account nonce returned from the FPI is not equal to the expected one" + # => [] + + # truncate the stack + exec.sys::truncate_stack + end + "#, + get_current_and_native_nonce_values = foreign_account.code().procedures()[1].mast_root(), + foreign_suffix = foreign_account.id().suffix(), + foreign_prefix = foreign_account.id().prefix().as_felt(), + expected_native_nonce = native_account.nonce(), + expected_foreign_nonce = foreign_account.nonce(), + ); + + let tx_script = ScriptBuilder::default().compile_tx_script(code)?; + + let foreign_account_inputs = mock_chain + .get_foreign_account_inputs(foreign_account.id()) + .expect("failed to get foreign account inputs"); + + mock_chain + .build_tx_context(native_account.id(), &[], &[]) + .expect("failed to build tx context") + .foreign_accounts(vec![foreign_account_inputs]) + .tx_script(tx_script) + .build()? + .execute_blocking()?; + + Ok(()) +} + // HELPER FUNCTIONS // ================================================================================================ diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index e859e4b63d..4f0c82cf5a 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -27,8 +27,10 @@ Account procedures can be used to read and write to account storage, add or remo | Procedure | Description | Context | | --- | --- | --- | -| `get_id` | Returns the account ID of the current account.

Inputs: `[]`
Outputs: `[account_id_prefix, account_id_suffix]` | Any | +| `get_id` | Returns the ID of the current account.

Inputs: `[]`
Outputs: `[account_id_prefix, account_id_suffix]` | Any | +| `get_native_id` | Returns the ID of the native account of the transaction.

Inputs: `[]`
Outputs: `[account_id_prefix, account_id_suffix]` | Any | | `get_nonce` | Returns the nonce of the current account. Always returns the initial nonce as it can only be incremented in auth procedures.

Inputs: `[]`
Outputs: `[nonce]` | Any | +| `get_native_nonce` | Returns the nonce of the native account of the transaction.

Inputs: `[]`
Outputs: `[nonce]` | Any | | `incr_nonce` | Increments the account nonce by one and returns the new nonce. Can only be called from auth procedures.

Inputs: `[]`
Outputs: `[final_nonce]` | Auth | | `get_initial_commitment` | Returns the native account commitment at the beginning of the transaction.

Inputs: `[]`
Outputs: `[INIT_COMMITMENT]` | Any | | `compute_current_commitment` | Computes and returns the account commitment from account data stored in memory.

Inputs: `[]`
Outputs: `[ACCOUNT_COMMITMENT]` | Any | @@ -110,6 +112,8 @@ Faucet procedures allow reading and writing to faucet accounts to mint and burn | Procedure | Description | Context | | --- | --- | --- | +| `create_fungible_asset` | Creates a fungible asset for the faucet the transaction is being executed against.

Inputs: `[amount]`
Outputs: `[ASSET]` | Faucet | +| `create_non_fungible_asset` | Creates a non-fungible asset for the faucet the transaction is being executed against.

Inputs: `[DATA_HASH]`
Outputs: `[ASSET]` | Faucet | | `mint` | Mint an asset from the faucet the transaction is being executed against.

Inputs: `[ASSET]`
Outputs: `[ASSET]` | Native & Account & Faucet | | `burn` | Burn an asset from the faucet the transaction is being executed against.

Inputs: `[ASSET]`
Outputs: `[ASSET]` | Native & Account & Faucet | | `get_total_issuance` | Returns the total issuance of the fungible faucet the transaction is being executed against.

Inputs: `[]`
Outputs: `[total_issuance]` | Faucet | @@ -122,6 +126,4 @@ Asset procedures provide utilities for creating fungible and non-fungible assets | Procedure | Description | Context | | --- | --- | --- | | `build_fungible_asset` | Builds a fungible asset for the specified fungible faucet and amount.

Inputs: `[faucet_id_prefix, faucet_id_suffix, amount]`
Outputs: `[ASSET]` | Any | -| `create_fungible_asset` | Creates a fungible asset for the faucet the transaction is being executed against.

Inputs: `[amount]`
Outputs: `[ASSET]` | Faucet | | `build_non_fungible_asset` | Builds a non-fungible asset for the specified non-fungible faucet and data hash.

Inputs: `[faucet_id_prefix, DATA_HASH]`
Outputs: `[ASSET]` | Any | -| `create_non_fungible_asset` | Creates a non-fungible asset for the faucet the transaction is being executed against.

Inputs: `[DATA_HASH]`
Outputs: `[ASSET]` | Faucet | From 9ab59d10a87144d1a416e55d97ccf007f3506937 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 8 Sep 2025 09:03:15 +0200 Subject: [PATCH 021/133] feat: Load assets lazily (#1848) * feat: Return `PartialAccount` from `DataStore` method * chore: Use `DataStore` trait bound * chore: simplify `set_current_account_data_ptr_to_native_account` * feat: Implement `DataStore::get_vault_asset_witness` * chore: Enable selective lazy loading in TX Context * feat: test asset addition with two assets * chore: revert asset vault tests to next state * feat: Introduce `mock::util` library and create lazy loading test * chore: improve input vault event description * chore: Explain native account vault root * chore: add changelog * feat: Introduce `AssetWitness` * chore: Remove advice inputs reexecution test * chore: add missing docs * feat: Only request missing merkle paths * fix: clippy lint * fix: typo * feat: Add `AssetWitness::new` * chore: Implement vault key to leaf index helper * chore: Match only `AdviceError::MerkleStoreLookupFailed` * feat: Add `AssetWitness::authenticated_nodes` * chore: Move current account ID getter to separate function * chore: Use `vault_key` instead of `asset_key` * chore: Don't require `&mut self` on asset data extractor * feat: Rename `TransactionKernelError::ViolatedAssumption` into `Other` * chore: assert account in context and tx inputs match * fix: Enable partial loading also for mock chain built txs * chore: add note on source error * chore: Rename to `initial_account_header` --------- Co-authored-by: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com> --- CHANGELOG.md | 3 +- .../asm/kernels/transaction/lib/account.masm | 14 +- .../asm/kernels/transaction/lib/memory.masm | 19 +-- .../asm/kernels/transaction/lib/prologue.masm | 9 ++ .../src/errors/transaction_errors.rs | 42 ++++++ crates/miden-lib/src/testing/mock_util_lib.rs | 50 +++++++ crates/miden-lib/src/testing/mod.rs | 1 + .../src/transaction/kernel_procedures.rs | 4 +- crates/miden-lib/src/transaction/mod.rs | 16 +- crates/miden-lib/src/utils/script_builder.rs | 14 +- crates/miden-objects/src/asset/mod.rs | 3 +- .../src/asset/vault/asset_witness.rs | 102 +++++++++++++ crates/miden-objects/src/asset/vault/mod.rs | 29 +++- .../miden-objects/src/asset/vault/partial.rs | 19 ++- crates/miden-objects/src/errors.rs | 4 + .../miden-testing/src/kernel_tests/tx/mod.rs | 1 + .../src/kernel_tests/tx/test_lazy_loading.rs | 136 +++++++++++++++++ .../src/kernel_tests/tx/test_tx.rs | 111 +------------- crates/miden-testing/src/mock_chain/chain.rs | 6 +- .../miden-testing/src/tx_context/builder.rs | 61 +++++++- .../miden-testing/src/tx_context/context.rs | 34 ++++- crates/miden-tx/src/executor/data_store.rs | 17 ++- crates/miden-tx/src/executor/exec_host.rs | 88 ++++++++++- crates/miden-tx/src/executor/mod.rs | 5 +- crates/miden-tx/src/host/mod.rs | 141 +++++++++++++++++- crates/miden-tx/src/prover/prover_host.rs | 3 + 26 files changed, 754 insertions(+), 178 deletions(-) create mode 100644 crates/miden-lib/src/testing/mock_util_lib.rs create mode 100644 crates/miden-objects/src/asset/vault/asset_witness.rs create mode 100644 crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e5c0dd97b..2a3ff95a5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ ### Features -- Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). - Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). +- Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). +- Enable lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). - Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). - Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 0ef622a010..704050c025 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -849,10 +849,6 @@ end #! added. #! - the vault already contains the same non-fungible asset. export.add_asset_to_vault - # emit event to signal that an asset is going to be added to the account vault - emit.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT - # => [ASSET] - # duplicate the ASSET to be able to emit an event after an asset is being added dupw # => [ASSET, ASSET] @@ -861,6 +857,9 @@ export.add_asset_to_vault exec.memory::get_account_vault_root_ptr movdn.4 # => [ASSET, acct_vault_root_ptr, ASSET] + # emit event to signal that an asset is going to be added to the account vault + emit.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT + # add the asset to the account vault exec.asset_vault::add_asset # => [ASSET', ASSET] @@ -889,14 +888,13 @@ end #! - the amount of the fungible asset in the vault is less than the amount to be removed. #! - the non-fungible asset is not found in the vault. export.remove_asset_from_vault - # emit event to signal that an asset is going to be removed from the account vault - emit.ACCOUNT_VAULT_BEFORE_REMOVE_ASSET_EVENT - # => [ASSET] - # fetch the vault root exec.memory::get_account_vault_root_ptr movdn.4 # => [ASSET, acct_vault_root_ptr] + # emit event to signal that an asset is going to be removed from the account vault + emit.ACCOUNT_VAULT_BEFORE_REMOVE_ASSET_EVENT + # remove the asset from the account vault exec.asset_vault::remove_asset # => [ASSET] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index 41ffe132ed..efd4a4cee9 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -868,19 +868,12 @@ end #! Inputs: [] #! Outputs: [] export.set_current_account_data_ptr_to_native_account - # get the pointer to the first stack element where pointer to the native account data should be - # stored - push.ACCOUNT_STACK_TOP_PTR - push.MIN_ACCOUNT_STACK_PTR - # => [native_account_stack_ptr, account_stack_top_ptr] - - # store the native account data pointer into the first stack element. - push.NATIVE_ACCOUNT_DATA_PTR dup.1 mem_store - # => [native_account_stack_ptr, account_stack_top_ptr] - - # store the pointer to the first account stack element at the account stack top - # pointer. - swap mem_store + # store the native account data pointer into the first account stack element. + push.NATIVE_ACCOUNT_DATA_PTR mem_store.MIN_ACCOUNT_STACK_PTR + # => [native_acct_stack_ptr, account_stack_top_ptr] + + # store the pointer to the first account stack element into the account stack top pointer. + push.MIN_ACCOUNT_STACK_PTR mem_store.ACCOUNT_STACK_TOP_PTR # => [] end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm index 6313dff3f2..f9a7e706ea 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -767,6 +767,15 @@ proc.add_input_note_assets_to_vault padw dup.5 mem_loadw # => [ASSET, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] + # TODO: Because the input vault is a copy of the account vault, to mutate the input vault, + # asset witnesses for the account vault must be requested. + # This is a temporary solution. We should avoid loading assets one by one here and instead + # pre-load all relevant merkle paths for the note assets before tx execution. + # + # This emitted event is equivalent to ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT. + emit.131072 + # => [ASSET, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] + exec.asset_vault::add_asset dropw # => [assets_start_ptr, assets_end_ptr, input_vault_root_ptr] diff --git a/crates/miden-lib/src/errors/transaction_errors.rs b/crates/miden-lib/src/errors/transaction_errors.rs index 83e6da6e13..38c54e98d7 100644 --- a/crates/miden-lib/src/errors/transaction_errors.rs +++ b/crates/miden-lib/src/errors/transaction_errors.rs @@ -1,4 +1,5 @@ use alloc::boxed::Box; +use alloc::string::String; use alloc::vec::Vec; use core::error::Error; @@ -52,6 +53,7 @@ pub enum TransactionKernelError { data: Vec, // This is always a DeserializationError, but we can't import it directly here without // adding dependencies, so we make it a trait object instead. + // thiserror will return this when calling Error::source on TransactionKernelError. source: Box, }, #[error("recipient data `{0:?}` in the advice provider is not well formed")] @@ -76,6 +78,17 @@ pub enum TransactionKernelError { NonceCanOnlyIncrementOnce, #[error("failed to convert fee asset into fungible asset")] FailedToConvertFeeAsset(#[source] AssetError), + #[error( + "failed to get vault asset witness from data store for vault root {vault_root} and vault_key {vault_key}" + )] + GetVaultAssetWitness { + vault_root: Word, + vault_key: Word, + // TODO: Change to DataStoreError when this error moves to miden-tx. + // This is always a DataStoreError, but we can't import it from miden-tx here. + // thiserror will return this when calling Error::source on TransactionKernelError. + source: Box, + }, #[error( "native asset amount {account_balance} in the account vault is not sufficient to cover the transaction fee of {tx_fee}" )] @@ -84,6 +97,35 @@ pub enum TransactionKernelError { /// missing. #[error("transaction requires a signature")] Unauthorized(Box), + /// A generic error returned when the transaction kernel did not behave as expected. + #[error("{message}")] + Other { + message: Box, + // thiserror will return this when calling Error::source on TransactionKernelError. + source: Option>, + }, +} + +impl TransactionKernelError { + /// Creates a custom error using the [`TransactionKernelError::Other`] variant from an error + /// message. + pub fn other(message: impl Into) -> Self { + let message: String = message.into(); + Self::Other { message: message.into(), source: None } + } + + /// Creates a custom error using the [`TransactionKernelError::Other`] variant from an error + /// message and a source error. + pub fn other_with_source( + message: impl Into, + source: impl Error + Send + Sync + 'static, + ) -> Self { + let message: String = message.into(); + Self::Other { + message: message.into(), + source: Some(Box::new(source)), + } + } } // TRANSACTION EVENT PARSING ERROR diff --git a/crates/miden-lib/src/testing/mock_util_lib.rs b/crates/miden-lib/src/testing/mock_util_lib.rs new file mode 100644 index 0000000000..1d569ffcbb --- /dev/null +++ b/crates/miden-lib/src/testing/mock_util_lib.rs @@ -0,0 +1,50 @@ +use miden_objects::assembly::Library; +use miden_objects::assembly::diagnostics::NamedSource; +use miden_objects::utils::sync::LazyLock; + +use crate::transaction::TransactionKernel; + +const MOCK_UTIL_LIBRARY_CODE: &str = " + use.miden::tx + + # Inputs: [] + # Outputs: [note_idx] + export.create_random_note + push.1.2.3.4 # = RECIPIENT + push.1 # = NoteExecutionHint::Always + push.2 # = NoteType::Private + push.0 # = aux + push.0xc0000000 # = NoteTag::LocalAny + # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] + + exec.tx::create_note + # => [note_idx] + end + + # Inputs: [ASSET] + # Outputs: [note_idx] + export.create_random_note_with_asset + exec.create_random_note + # => [note_idx, ASSET] + + movdn.4 + # => [ASSET, note_idx] + + exec.tx::add_asset_to_note dropw + # => [note_idx] + end +"; + +static MOCK_UTIL_LIBRARY: LazyLock = LazyLock::new(|| { + let source = NamedSource::new("mock::util", MOCK_UTIL_LIBRARY_CODE); + TransactionKernel::assembler() + .assemble_library([source]) + .expect("mock util library should be valid") +}); + +/// Returns the mock test [`Library`] under the `mock::util` namespace. +/// +/// This provides convenient wrappers for testing purposes. +pub fn mock_util_library() -> Library { + MOCK_UTIL_LIBRARY.clone() +} diff --git a/crates/miden-lib/src/testing/mod.rs b/crates/miden-lib/src/testing/mod.rs index f567f0e456..2ae6333ac9 100644 --- a/crates/miden-lib/src/testing/mod.rs +++ b/crates/miden-lib/src/testing/mod.rs @@ -1,4 +1,5 @@ pub mod account_component; pub mod mock_account; pub mod mock_account_code; +pub mod mock_util_lib; pub mod note; diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index b49e19e8e7..474f680ec5 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -40,9 +40,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_vault_root word!("0x42a2bfb8eac4fce9bbf75ea15215b00729faeeaf7fff784692948d3f618a9bb7"), // account_add_asset - word!("0xf0d6be80cdd2a62cab2b4ad5606dde01af1968e3ab3357dcb63fe36aea7f8b80"), + word!("0x9a96c031f9ca6d839acc5ce6aa01b63ddba91fe312ea824ccd224c365cc1327d"), // account_remove_asset - word!("0x7a8f7b23afe3d6264686c4895f78744fbccda957e901247c8f3585427d470039"), + word!("0x8372d56ed394254481026d264b8ea97c2342ba42db21c61b2e96c6bf06d950a9"), // account_get_balance word!("0xb4e92ae0196ca128a451e40dd8a5ff56c13919efa67f63dca488214fbba3ffbc"), // account_has_non_fungible_asset diff --git a/crates/miden-lib/src/transaction/mod.rs b/crates/miden-lib/src/transaction/mod.rs index 1e51532876..29e9d25172 100644 --- a/crates/miden-lib/src/transaction/mod.rs +++ b/crates/miden-lib/src/transaction/mod.rs @@ -484,16 +484,20 @@ impl TransactionKernel { .with_debug_mode(true) } - /// Returns an [`Assembler`] with the mock account and faucet libraries. + /// Returns an [`Assembler`] with the `mock::{account, faucet, util}` libraries. /// /// This assembler is the same as [`TransactionKernel::with_kernel_library`] but additionally - /// includes the [`MockAccountCodeExt::mock_account_library`][account_lib] - /// and [`MockAccountCodeExt::mock_faucet_library`][faucet_lib], which are the standard - /// testing account libraries. + /// includes: + /// - [`MockAccountCodeExt::mock_account_library`][account_lib], + /// - [`MockAccountCodeExt::mock_faucet_library`][faucet_lib], + /// - [`mock_util_library`][util_lib] /// /// [account_lib]: crate::testing::mock_account_code::MockAccountCodeExt::mock_account_library /// [faucet_lib]: crate::testing::mock_account_code::MockAccountCodeExt::mock_faucet_library + /// [util_lib]: crate::testing::mock_util_lib::mock_util_library pub fn with_mock_libraries(source_manager: Arc) -> Assembler { + use crate::testing::mock_util_lib::mock_util_library; + let mut assembler = Self::with_kernel_library(source_manager); for library in Self::mock_libraries() { @@ -502,6 +506,10 @@ impl TransactionKernel { .expect("failed to add mock account libraries"); } + assembler + .link_static_library(mock_util_library()) + .expect("failed to add mock test library"); + assembler } } diff --git a/crates/miden-lib/src/utils/script_builder.rs b/crates/miden-lib/src/utils/script_builder.rs index 6ee8badf83..026f62bbad 100644 --- a/crates/miden-lib/src/utils/script_builder.rs +++ b/crates/miden-lib/src/utils/script_builder.rs @@ -294,23 +294,27 @@ impl ScriptBuilder { // TESTING CONVENIENCE FUNCTIONS // -------------------------------------------------------------------------------------------- - /// Returns a [`ScriptBuilder`] with the mock account and faucet libraries. + /// Returns a [`ScriptBuilder`] with the `mock::{account, faucet, util}` libraries. /// - /// This script builder includes the [`MockAccountCodeExt::mock_account_library`][account_lib] - /// and [`MockAccountCodeExt::mock_faucet_library`][faucet_lib], which are the standard - /// testing account libraries. + /// This script builder includes: + /// - [`MockAccountCodeExt::mock_account_library`][account_lib], + /// - [`MockAccountCodeExt::mock_faucet_library`][faucet_lib], + /// - [`mock_util_library`][util_lib] /// /// [account_lib]: crate::testing::mock_account_code::MockAccountCodeExt::mock_account_library /// [faucet_lib]: crate::testing::mock_account_code::MockAccountCodeExt::mock_faucet_library + /// [util_lib]: crate::testing::mock_util_lib::mock_util_library #[cfg(any(feature = "testing", test))] pub fn with_mock_libraries() -> Result { use miden_objects::account::AccountCode; use crate::testing::mock_account_code::MockAccountCodeExt; + use crate::testing::mock_util_lib::mock_util_library; Self::new(true) .with_dynamically_linked_library(&AccountCode::mock_account_library())? - .with_dynamically_linked_library(&AccountCode::mock_faucet_library()) + .with_dynamically_linked_library(&AccountCode::mock_faucet_library())? + .with_statically_linked_library(&mock_util_library()) } } diff --git a/crates/miden-objects/src/asset/mod.rs b/crates/miden-objects/src/asset/mod.rs index 9078866113..482e2164ab 100644 --- a/crates/miden-objects/src/asset/mod.rs +++ b/crates/miden-objects/src/asset/mod.rs @@ -15,13 +15,14 @@ use alloc::boxed::Box; pub use fungible::FungibleAsset; mod nonfungible; + pub use nonfungible::{NonFungibleAsset, NonFungibleAssetDetails}; mod token_symbol; pub use token_symbol::TokenSymbol; mod vault; -pub use vault::{AssetVault, PartialVault}; +pub use vault::{AssetVault, AssetWitness, PartialVault}; // ASSET // ================================================================================================ diff --git a/crates/miden-objects/src/asset/vault/asset_witness.rs b/crates/miden-objects/src/asset/vault/asset_witness.rs new file mode 100644 index 0000000000..772c0421f6 --- /dev/null +++ b/crates/miden-objects/src/asset/vault/asset_witness.rs @@ -0,0 +1,102 @@ +use miden_crypto::merkle::{InnerNodeInfo, SmtProof}; + +use crate::AssetError; +use crate::asset::Asset; + +/// A witness of an asset in an [`AssetVault`](super::AssetVault). +/// +/// It proves inclusion of a certain asset in the vault. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AssetWitness(SmtProof); + +impl AssetWitness { + /// Creates a new [`AssetWitness`] from an SMT proof. + /// + /// # Errors + /// + /// Returns an error if: + /// - any of the entries in the SMT leaf is not a valid asset. + /// - any of the entries' vault keys does not match the expected vault key of the asset. + pub fn new(smt_proof: SmtProof) -> Result { + for (vault_key, asset) in smt_proof.leaf().entries() { + let asset = Asset::try_from(asset)?; + if asset.vault_key() != *vault_key { + return Err(AssetError::VaultKeyMismatch { + actual: *vault_key, + expected: asset.vault_key(), + }); + } + } + + Ok(Self(smt_proof)) + } + + /// Creates a new [`AssetWitness`] from an SMT proof without checking that the proof contains + /// valid assets. + /// + /// Prefer [`AssetWitness::new`] whenever possible. + pub fn new_unchecked(smt_proof: SmtProof) -> Self { + Self(smt_proof) + } + + /// Returns an iterator over every inner node of this witness' merkle path. + pub fn authenticated_nodes(&self) -> impl Iterator + '_ { + self.0 + .path() + .authenticated_nodes(self.0.leaf().index().value(), self.0.leaf().hash()) + .expect("leaf index is u64 and should be less than 2^SMT_DEPTH") + } +} + +impl From for SmtProof { + fn from(witness: AssetWitness) -> Self { + witness.0 + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + use miden_crypto::merkle::Smt; + + use super::*; + use crate::Word; + use crate::asset::{FungibleAsset, NonFungibleAsset}; + + /// Tests that constructing an asset witness fails if any asset in the smt proof is invalid. + #[test] + fn create_asset_witness_fails_on_invalid_asset() -> anyhow::Result<()> { + let invalid_asset = Word::from([0, 0, 0, 5u32]); + let smt = Smt::with_entries([(invalid_asset, invalid_asset)])?; + let proof = smt.open(&invalid_asset); + + let err = AssetWitness::new(proof).unwrap_err(); + + assert_matches!(err, AssetError::InvalidFaucetAccountId(_)); + + Ok(()) + } + + /// Tests that constructing an asset witness fails if the vault key is from a fungible asset and + /// the asset is a non-fungible one. + #[test] + fn create_asset_witness_fails_on_vault_key_mismatch() -> anyhow::Result<()> { + let fungible_asset = FungibleAsset::mock(500); + let non_fungible_asset = NonFungibleAsset::mock(&[1]); + + let smt = Smt::with_entries([(fungible_asset.vault_key(), non_fungible_asset.into())])?; + let proof = smt.open(&fungible_asset.vault_key()); + + let err = AssetWitness::new(proof).unwrap_err(); + + assert_matches!(err, AssetError::VaultKeyMismatch { actual, expected } => { + assert_eq!(actual, fungible_asset.vault_key()); + assert_eq!(expected, non_fungible_asset.vault_key()); + }); + + Ok(()) + } +} diff --git a/crates/miden-objects/src/asset/vault/mod.rs b/crates/miden-objects/src/asset/vault/mod.rs index d3883a6b9c..f8e1c84c0b 100644 --- a/crates/miden-objects/src/asset/vault/mod.rs +++ b/crates/miden-objects/src/asset/vault/mod.rs @@ -1,5 +1,7 @@ use alloc::string::ToString; +use miden_processor::SMT_DEPTH; + use super::{ AccountType, Asset, @@ -13,11 +15,14 @@ use super::{ }; use crate::account::{AccountId, AccountVaultDelta, NonFungibleDeltaAction}; use crate::crypto::merkle::Smt; -use crate::{AssetVaultError, Word}; +use crate::{AssetVaultError, Felt, Word}; mod partial; pub use partial::PartialVault; +mod asset_witness; +pub use asset_witness::AssetWitness; + // ASSET VAULT // ================================================================================================ @@ -38,8 +43,15 @@ pub struct AssetVault { } impl AssetVault { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The depth of the SMT that represents the asset vault. + pub const DEPTH: u8 = SMT_DEPTH; + // CONSTRUCTOR // -------------------------------------------------------------------------------------------- + /// Returns a new [AssetVault] initialized with the provided assets. pub fn new(assets: &[Asset]) -> Result { Ok(Self { @@ -89,6 +101,15 @@ impl AssetVault { self.asset_tree.entries().map(|x| Asset::new_unchecked(x.1)) } + /// Returns an opening of the leaf associated with `vault_key`. + /// + /// The `vault_key` can be obtained with [`Asset::vault_key`]. + pub fn open(&self, vault_key: Word) -> AssetWitness { + let smt_proof = self.asset_tree.open(&vault_key); + // SAFETY: The asset vault should only contain valid assets. + AssetWitness::new_unchecked(smt_proof) + } + /// Returns a reference to the Sparse Merkle Tree underling this asset vault. pub fn asset_tree(&self) -> &Smt { &self.asset_tree @@ -99,6 +120,12 @@ impl AssetVault { self.asset_tree.is_empty() } + /// Returns the leaf index of a vault key. + pub fn vault_key_to_leaf_index(vault_key: Word) -> Felt { + // The third element in an SMT key is the index. + vault_key[3] + } + // PUBLIC MODIFIERS // -------------------------------------------------------------------------------------------- diff --git a/crates/miden-objects/src/asset/vault/partial.rs b/crates/miden-objects/src/asset/vault/partial.rs index 4f3b7061d0..c1dfa1ac28 100644 --- a/crates/miden-objects/src/asset/vault/partial.rs +++ b/crates/miden-objects/src/asset/vault/partial.rs @@ -4,7 +4,7 @@ use miden_crypto::merkle::{InnerNodeInfo, MerkleError, PartialSmt, SmtLeaf, SmtP use super::AssetVault; use crate::Word; -use crate::asset::Asset; +use crate::asset::{Asset, AssetWitness}; use crate::errors::PartialAssetVaultError; use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; @@ -60,6 +60,23 @@ impl PartialVault { self.partial_smt.leaves().map(|(_, leaf)| leaf) } + /// Returns an opening of the leaf associated with `vault_key`. + /// + /// The `vault_key` can be obtained with [`Asset::vault_key`]. + /// + /// # Errors + /// + /// Returns an error if: + /// - the key is not tracked by this partial vault. + pub fn open(&self, vault_key: Word) -> Result { + let smt_proof = self + .partial_smt + .open(&vault_key) + .map_err(PartialAssetVaultError::UntrackedAsset)?; + // SAFETY: The partial vault should only contain valid assets. + Ok(AssetWitness::new_unchecked(smt_proof)) + } + /// Returns the [`Asset`] associated with the given `vault_key`. /// /// The return value is `None` if the asset does not exist in the vault. diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index bb25ff909c..cd7a9512d2 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -385,6 +385,8 @@ pub enum AssetError { expected_ty = AccountType::NonFungibleFaucet )] NonFungibleFaucetIdTypeMismatch(AccountIdPrefix), + #[error("vault key {actual} does not match expected vault key {expected}")] + VaultKeyMismatch { actual: Word, expected: Word }, } // TOKEN SYMBOL ERROR @@ -434,6 +436,8 @@ pub enum PartialAssetVaultError { VaultKeyMismatch { expected: Word, actual: Word }, #[error("failed to add asset proof")] FailedToAddProof(#[source] MerkleError), + #[error("asset is not tracked in the partial vault")] + UntrackedAsset(#[source] MerkleError), } // NOTE ERROR diff --git a/crates/miden-testing/src/kernel_tests/tx/mod.rs b/crates/miden-testing/src/kernel_tests/tx/mod.rs index 04c2817db9..9fee06a3d2 100644 --- a/crates/miden-testing/src/kernel_tests/tx/mod.rs +++ b/crates/miden-testing/src/kernel_tests/tx/mod.rs @@ -37,6 +37,7 @@ mod test_faucet; mod test_fee; mod test_fpi; mod test_input_note; +mod test_lazy_loading; mod test_link_map; mod test_note; mod test_output_note; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs new file mode 100644 index 0000000000..97c687d233 --- /dev/null +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -0,0 +1,136 @@ +//! This module tests lazy loading. +//! +//! Once lazy loading is enabled generally, it can be removed and/or integrated into other tests. + +use miden_lib::testing::note::NoteBuilder; +use miden_lib::utils::ScriptBuilder; +use miden_objects::account::AccountId; +use miden_objects::asset::{Asset, FungibleAsset}; +use miden_objects::testing::account_id::{ + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, +}; +use miden_objects::testing::constants::FUNGIBLE_ASSET_AMOUNT; + +use super::Word; +use crate::{MockChain, TransactionContextBuilder}; + +/// Tests that adding two different assets to the account vault succeeds when lazy loading is +/// enabled. +#[test] +fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { + let faucet_id1: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); + let faucet_id2: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into().unwrap(); + + let fungible_asset1 = + FungibleAsset::new(faucet_id1, FungibleAsset::MAX_AMOUNT - FUNGIBLE_ASSET_AMOUNT)?; + let fungible_asset2 = FungibleAsset::new(faucet_id2, FUNGIBLE_ASSET_AMOUNT)?; + + // Build a note that adds the assets to the input vault of the transaction. This is necessary + // to adhere to asset preservation rules. + let asset_note = NoteBuilder::new(faucet_id1, rand::rng()) + .add_assets([fungible_asset1, fungible_asset2].map(Asset::from)) + .build()?; + + let code = format!( + " + use.mock::account + + begin + push.{FUNGIBLE_ASSET1} + call.account::add_asset dropw + + push.{FUNGIBLE_ASSET2} + call.account::add_asset dropw + end + ", + FUNGIBLE_ASSET1 = Word::from(fungible_asset1), + FUNGIBLE_ASSET2 = Word::from(fungible_asset2) + ); + + let builder = ScriptBuilder::with_mock_libraries()?; + let source_manager = builder.source_manager(); + let tx_script = builder.compile_tx_script(code)?; + let tx_context = TransactionContextBuilder::with_existing_mock_account() + .tx_script(tx_script) + .extend_input_notes(vec![asset_note]) + .enable_partial_loading() + .with_source_manager(source_manager) + .build()?; + let account = tx_context.account().clone(); + let tx = tx_context.execute_blocking()?; + + let mut account_vault = account.vault().clone(); + account_vault.add_asset(fungible_asset1.into())?; + account_vault.add_asset(fungible_asset2.into())?; + + assert_eq!(tx.final_account().vault_root(), account_vault.root()); + + Ok(()) +} + +/// Tests that removing two different assets from the account vault succeeds when lazy loading is +/// enabled. +#[test] +fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { + let faucet_id1: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); + let faucet_id2: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into().unwrap(); + + let fungible_asset1 = + FungibleAsset::new(faucet_id1, FungibleAsset::MAX_AMOUNT - FUNGIBLE_ASSET_AMOUNT)?; + let fungible_asset2 = FungibleAsset::new(faucet_id2, FUNGIBLE_ASSET_AMOUNT)?; + + let code = format!( + " + use.mock::account + use.mock::util + + begin + push.{FUNGIBLE_ASSET1} + call.account::remove_asset + # => [] + + # move asset to note to adhere to asset preservation rules + exec.util::create_random_note_with_asset drop + # => [] + + push.{FUNGIBLE_ASSET2} + call.account::remove_asset + # => [ASSET] + + # move asset to note to adhere to asset preservation rules + exec.util::create_random_note_with_asset drop + # => [] + end + ", + FUNGIBLE_ASSET1 = Word::from(fungible_asset1), + FUNGIBLE_ASSET2 = Word::from(fungible_asset2) + ); + + let builder = ScriptBuilder::with_mock_libraries()?; + let source_manager = builder.source_manager(); + let tx_script = builder.compile_tx_script(code)?; + + let mut builder = MockChain::builder(); + let account = builder.add_existing_mock_account_with_assets( + crate::Auth::IncrNonce, + [fungible_asset1, fungible_asset2].map(Asset::from), + )?; + let tx_context = builder + .build()? + .build_tx_context(account, &[], &[])? + .tx_script(tx_script) + .enable_partial_loading() + .with_source_manager(source_manager) + .build()?; + let account = tx_context.account().clone(); + let tx = tx_context.execute_blocking()?; + + let mut account_vault = account.vault().clone(); + account_vault.remove_asset(fungible_asset1.into())?; + account_vault.remove_asset(fungible_asset2.into())?; + + assert_eq!(tx.final_account().vault_root(), account_vault.root()); + + Ok(()) +} diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 668a1d5bff..b0b53685e4 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -36,7 +36,7 @@ use miden_objects::account::{ StorageSlot, }; use miden_objects::assembly::DefaultSourceManager; -use miden_objects::assembly::diagnostics::{IntoDiagnostic, NamedSource, miette}; +use miden_objects::assembly::diagnostics::NamedSource; use miden_objects::asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset}; use miden_objects::block::BlockNumber; use miden_objects::note::{ @@ -77,17 +77,8 @@ use miden_objects::transaction::{ }; use miden_objects::{FieldElement, Hasher, Word}; use miden_processor::crypto::RpoRandomCoin; -use miden_processor::fast::FastProcessor; -use miden_processor::{AdviceInputs, StackInputs}; use miden_tx::auth::UnreachableAuth; -use miden_tx::{ - AccountProcedureIndexMap, - ScriptMastForestStore, - TransactionExecutor, - TransactionExecutorError, - TransactionExecutorHost, - TransactionMastStore, -}; +use miden_tx::{TransactionExecutor, TransactionExecutorError}; use super::{Felt, ONE, ZERO}; use crate::kernel_tests::tx::ProcessMemoryExt; @@ -892,104 +883,6 @@ fn test_block_procedures() -> anyhow::Result<()> { Ok(()) } -/// Tests that the transaction witness retrieved from an executed transaction contains all necessary -/// advice input to execute the transaction again. -#[tokio::test] -async fn advice_inputs_from_transaction_witness_are_sufficient_to_reexecute_transaction() --> miette::Result<()> { - // Creates a mockchain with an account and a note that it can consume - let tx_context = { - let mut builder = MockChain::builder(); - let account = builder - .add_existing_wallet(Auth::BasicAuth) - .map_err(|err| miette::miette!(err))?; - let p2id_note = builder - .add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(100)], - NoteType::Public, - ) - .map_err(|err| miette::miette!(err))?; - let mock_chain = builder.build().map_err(|err| miette::miette!(err))?; - - mock_chain - .build_tx_context(account.id(), &[], &[p2id_note]) - .unwrap() - .build() - .unwrap() - }; - - let executed_transaction = tx_context.execute().await.into_diagnostic()?; - - let tx_inputs = executed_transaction.tx_inputs(); - let tx_args = executed_transaction.tx_args(); - - let scripts_mast_store = ScriptMastForestStore::new( - tx_args.tx_script(), - tx_inputs.input_notes().iter().map(|n| n.note().script()), - ); - - // use the witness to execute the transaction again - let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs( - tx_inputs, - tx_args, - Some(executed_transaction.advice_witness().clone()), - ) - .into_diagnostic()?; - - // load account/note/tx_script MAST to the mast_store - let mast_store = Arc::new(TransactionMastStore::new()); - mast_store.load_account_code(tx_inputs.account().code()); - - let mut host = { - let acct_procedure_index_map = - AccountProcedureIndexMap::from_transaction_params(tx_inputs, tx_args, &advice_inputs) - .unwrap(); - - TransactionExecutorHost::<'_, '_, _, UnreachableAuth>::new( - tx_inputs.account(), - tx_inputs.input_notes().clone(), - mast_store.as_ref(), - scripts_mast_store, - acct_procedure_index_map, - None, - tx_inputs.block_header().fee_parameters(), - Arc::new(DefaultSourceManager::default()), - ) - }; - let advice_inputs = advice_inputs.into_advice_inputs(); - // This reverses the stack inputs (even though it doesn't look like it does) because the - // fast processor expects the reverse order. - let stack_inputs = StackInputs::new(stack_inputs.iter().copied().collect()).unwrap(); - - let processor = FastProcessor::new_debug(stack_inputs.as_slice(), advice_inputs); - let (stack_outputs, advice_provider) = processor - .execute(&TransactionKernel::main(), &mut host) - .await - .map_err(TransactionExecutorError::TransactionProgramExecutionFailed) - .into_diagnostic()?; - - // Extract advice map from advice provider. - let advice_inputs = AdviceInputs { - map: advice_provider.into_parts().1, - ..Default::default() - }; - - let (_, _input_notes, output_notes, _signatures, _tx_progress) = host.into_parts(); - let tx_outputs = - TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes) - .unwrap(); - - assert_eq!( - executed_transaction.final_account().commitment(), - tx_outputs.account.commitment() - ); - assert_eq!(executed_transaction.output_notes(), &tx_outputs.output_notes); - - Ok(()) -} - #[test] fn executed_transaction_output_notes() -> anyhow::Result<()> { let executor_account = diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 5c2aacd688..bb194a5712 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -6,7 +6,7 @@ use anyhow::Context; use miden_block_prover::{LocalBlockProver, ProvenBlockError}; use miden_lib::note::{create_p2id_note, create_p2ide_note}; use miden_objects::account::delta::AccountUpdateDetails; -use miden_objects::account::{Account, AccountId, AuthSecretKey, StorageSlot}; +use miden_objects::account::{Account, AccountId, AuthSecretKey, PartialAccount, StorageSlot}; use miden_objects::asset::Asset; use miden_objects::batch::{ProposedBatch, ProvenBatch}; use miden_objects::block::{ @@ -634,7 +634,7 @@ impl MockChain { pub fn get_transaction_inputs_at( &self, reference_block: BlockNumber, - account: Account, + account: impl Into, account_seed: Option, notes: &[NoteId], unauthenticated_notes: &[Note], @@ -703,7 +703,7 @@ impl MockChain { /// Returns a valid [`TransactionInputs`] for the specified entities. pub fn get_transaction_inputs( &self, - account: Account, + account: impl Into, account_seed: Option, notes: &[NoteId], unauthenticated_notes: &[Note], diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 63d641d8cd..0d67cdd5f3 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -9,9 +9,10 @@ use anyhow::Context; use miden_lib::testing::account_component::IncrNonceAuthComponent; use miden_lib::testing::mock_account::MockAccountExt; use miden_objects::EMPTY_WORD; -use miden_objects::account::Account; +use miden_objects::account::{Account, AccountHeader, PartialAccount}; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::debuginfo::SourceManagerSync; +use miden_objects::asset::PartialVault; use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; use miden_objects::note::{Note, NoteId}; use miden_objects::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; @@ -79,6 +80,7 @@ pub struct TransactionContextBuilder { transaction_inputs: Option, auth_args: Word, signatures: Vec<(PublicKey, Word, Vec)>, + load_partial_account: bool, } impl TransactionContextBuilder { @@ -98,6 +100,7 @@ impl TransactionContextBuilder { foreign_account_inputs: vec![], auth_args: EMPTY_WORD, signatures: Vec::new(), + load_partial_account: false, } } @@ -196,10 +199,24 @@ impl TransactionContextBuilder { /// Set the desired transaction inputs pub fn tx_inputs(mut self, tx_inputs: TransactionInputs) -> Self { + assert_eq!( + AccountHeader::from(&self.account), + tx_inputs.account().into(), + "account in context and account provided via tx inputs are not the same account" + ); self.transaction_inputs = Some(tx_inputs); self } + /// Causes the transaction to only construct a minimal partial account as the transaction + /// input, causing lazy loading of assets throughout transaction execution. + /// + /// This exists to test lazy loading selectively and should go away in the future. + pub fn enable_partial_loading(mut self) -> Self { + self.load_partial_account = true; + self + } + /// Extend the note arguments map with the provided one. pub fn extend_note_args(mut self, note_args: BTreeMap) -> Self { self.note_args.extend(note_args); @@ -259,18 +276,48 @@ impl TransactionContextBuilder { let input_note_ids: Vec = mock_chain.committed_notes().values().map(MockChainNote::id).collect(); + let account = PartialAccount::from(&self.account); mock_chain - .get_transaction_inputs( - self.account.clone(), - self.account_seed, - &input_note_ids, - &[], - ) + .get_transaction_inputs(account, self.account_seed, &input_note_ids, &[]) .context("failed to get transaction inputs from mock chain")? }, }; + // If partial loading is enabled, construct an account that doesn't contain all + // merkle paths of assets, in order to test lazy loading. Otherwise, load the full + // account. + let tx_inputs = if self.load_partial_account { + let (account, account_seed, block_header, partial_blockchain, input_notes) = + tx_inputs.into_parts(); + // Construct a partial vault that tracks the empty word, but none of the assets + // that are actually in the asset tree. That way, the partial vault has the same + // root as the full vault, but will not add any relevant merkle paths to the + // merkle store, which will test lazy loading of assets. + // Note that we use self.account instead of account, because we cannot do the same + // operation on a partial vault. + let mut partial_vault = PartialVault::default(); + partial_vault.add(self.account.vault().open(Word::empty()).into())?; + + let account = PartialAccount::new( + account.id(), + account.nonce(), + account.code().clone(), + account.storage().clone(), + partial_vault, + ); + + TransactionInputs::new( + account, + account_seed, + block_header, + partial_blockchain, + input_notes, + )? + } else { + tx_inputs + }; + let tx_args = TransactionArgs::new(AdviceMap::default(), self.foreign_account_inputs) .with_note_args(self.note_args); diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index ac92e91a51..f9e97b5b99 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -5,9 +5,10 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_lib::transaction::TransactionKernel; -use miden_objects::account::{Account, AccountId}; +use miden_objects::account::{Account, AccountId, PartialAccount}; use miden_objects::assembly::debuginfo::{SourceLanguage, Uri}; use miden_objects::assembly::{SourceManager, SourceManagerSync}; +use miden_objects::asset::AssetWitness; use miden_objects::block::{BlockHeader, BlockNumber}; use miden_objects::note::Note; use miden_objects::transaction::{ @@ -194,11 +195,36 @@ impl DataStore for TransactionContext { account_id: AccountId, _ref_blocks: BTreeSet, ) -> impl FutureMaybeSend< - Result<(Account, Option, BlockHeader, PartialBlockchain), DataStoreError>, + Result<(PartialAccount, Option, BlockHeader, PartialBlockchain), DataStoreError>, > { assert_eq!(account_id, self.account().id()); - let (_partial_account, seed, header, mmr, _) = self.tx_inputs.clone().into_parts(); - async move { Ok((self.account.clone(), seed, header, mmr)) } + assert_eq!(account_id, self.tx_inputs.account().id()); + + let (partial_account, seed, header, mmr, _) = self.tx_inputs.clone().into_parts(); + + async move { Ok((partial_account, seed, header, mmr)) } + } + + fn get_vault_asset_witness( + &self, + account_id: AccountId, + vault_root: Word, + vault_key: Word, + ) -> impl FutureMaybeSend> { + assert_eq!( + account_id, + self.account.id(), + "only native account vault witnesses can be requested (for now)" + ); + assert_eq!( + vault_root, + self.account.vault().root(), + "vault root should match the native account's root (for now)" + ); + + let asset_witness = self.account().vault().open(vault_key); + + async { Ok(asset_witness) } } } diff --git a/crates/miden-tx/src/executor/data_store.rs b/crates/miden-tx/src/executor/data_store.rs index b521112c5e..037e074994 100644 --- a/crates/miden-tx/src/executor/data_store.rs +++ b/crates/miden-tx/src/executor/data_store.rs @@ -1,6 +1,7 @@ use alloc::collections::BTreeSet; -use miden_objects::account::{Account, AccountId}; +use miden_objects::account::{AccountId, PartialAccount}; +use miden_objects::asset::AssetWitness; use miden_objects::block::{BlockHeader, BlockNumber}; use miden_objects::transaction::PartialBlockchain; use miden_processor::{FutureMaybeSend, MastForestStore, Word}; @@ -31,6 +32,18 @@ pub trait DataStore: MastForestStore { account_id: AccountId, ref_blocks: BTreeSet, ) -> impl FutureMaybeSend< - Result<(Account, Option, BlockHeader, PartialBlockchain), DataStoreError>, + Result<(PartialAccount, Option, BlockHeader, PartialBlockchain), DataStoreError>, >; + + /// Returns a witness for an asset in the requested account's vault with the requested vault + /// root. + /// + /// This is the witness that needs to be added to the advice provider's merkle store and advice + /// map to make access to the specified asset possible. + fn get_vault_asset_witness( + &self, + account_id: AccountId, + vault_root: Word, + vault_key: Word, + ) -> impl FutureMaybeSend>; } diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 45e3616734..9a8781326a 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -5,12 +5,14 @@ use alloc::vec::Vec; use miden_lib::errors::TransactionKernelError; use miden_lib::transaction::TransactionEvent; -use miden_objects::account::{AccountDelta, PartialAccount}; +use miden_objects::account::{AccountDelta, AccountId, PartialAccount}; use miden_objects::assembly::debuginfo::Location; use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; -use miden_objects::asset::FungibleAsset; +use miden_objects::asset::{Asset, FungibleAsset}; use miden_objects::block::FeeParameters; +use miden_objects::crypto::merkle::SmtProof; use miden_objects::transaction::{InputNote, InputNotes, OutputNote}; +use miden_objects::vm::AdviceMap; use miden_objects::{Felt, Hasher, Word}; use miden_processor::{ AdviceMutation, @@ -19,11 +21,9 @@ use miden_processor::{ EventError, FutureMaybeSend, MastForest, - MastForestStore, ProcessState, }; -use crate::AccountProcedureIndexMap; use crate::auth::{SigningInputs, TransactionAuthenticator}; use crate::host::{ ScriptMastForestStore, @@ -32,6 +32,7 @@ use crate::host::{ TransactionEventHandling, TransactionProgress, }; +use crate::{AccountProcedureIndexMap, DataStore}; // TRANSACTION EXECUTOR HOST // ================================================================================================ @@ -45,7 +46,7 @@ use crate::host::{ /// execution. pub struct TransactionExecutorHost<'store, 'auth, STORE, AUTH> where - STORE: MastForestStore, + STORE: DataStore, AUTH: TransactionAuthenticator, { /// The underlying base transaction host. @@ -72,7 +73,7 @@ where impl<'store, 'auth, STORE, AUTH> TransactionExecutorHost<'store, 'auth, STORE, AUTH> where - STORE: MastForestStore + Sync, + STORE: DataStore + Sync, AUTH: TransactionAuthenticator + Sync, { // CONSTRUCTORS @@ -212,6 +213,69 @@ where Ok(Vec::new()) } + /// Handles a request to an asset witness by querying the data store for a merkle path. + /// + /// ## Native Account + /// + /// For the native account we always request witnesses for the initial vault root, because the + /// data store only has the state of the account vault at the beginning of the transaction. + /// Since the vault root can change as the transaction progresses, this means the witnesses + /// may become _partially_ or fully outdated. To see why they can only be _partially_ outdated, + /// consider the following example: + /// + /// ```text + /// A A' + /// / \ / \ + /// B C -> B' C + /// / \ / \ / \ / \ + /// D E F G D E' F G + /// ``` + /// + /// Leaf E was updated to E', in turn updating nodes B and A. If we now request the merkle path + /// to G against root A (the initial vault root), we'll get nodes F and B. F is a node in the + /// updated tree, while B is not. We insert both into the merkle store anyway. Now, if the + /// transaction attempts to verify the merkle path to G, it can do so because F and B' are in + /// the merkle store. Note that B' is in the store because the transaction inserted it into the + /// merkle store as part of updating E, not because we inserted it. B is present in the store, + /// but is simply ignored for the purpose of verifying G's inclusion. + async fn on_account_vault_asset_witness_requested( + &self, + current_account_id: AccountId, + _vault_root: Word, + asset: Asset, + ) -> Result, TransactionKernelError> { + // For now, we only support getting witnesses for the native account, so return early if the + // requested account is not the native one. + if current_account_id != self.base_host.initial_account_header().id() { + return Ok(Vec::new()); + } + + let vault_root = self.base_host.initial_account_header().vault_root(); + let vault_key = asset.vault_key(); + let asset_witness = self + .base_host + .store() + .get_vault_asset_witness(current_account_id, vault_root, vault_key) + .await + .map_err(|err| TransactionKernelError::GetVaultAssetWitness { + vault_root, + vault_key, + source: Box::new(err), + })?; + + // Get the nodes in the proof and insert them into the merkle store. + let merkle_store_ext = + AdviceMutation::extend_merkle_store(asset_witness.authenticated_nodes()); + + let smt_proof = SmtProof::from(asset_witness); + let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([( + smt_proof.leaf().hash(), + smt_proof.leaf().to_elements(), + )])); + + Ok(vec![merkle_store_ext, map_ext]) + } + /// Consumes `self` and returns the account delta, output notes, generated signatures and /// transaction progress. #[allow(clippy::type_complexity)] @@ -235,7 +299,7 @@ where impl BaseHost for TransactionExecutorHost<'_, '_, STORE, AUTH> where - STORE: MastForestStore, + STORE: DataStore, AUTH: TransactionAuthenticator, { fn get_mast_forest(&self, procedure_root: &Word) -> Option> { @@ -255,7 +319,7 @@ where impl AsyncHost for TransactionExecutorHost<'_, '_, STORE, AUTH> where - STORE: MastForestStore + Sync, + STORE: DataStore + Sync, AUTH: TransactionAuthenticator + Sync, { fn on_event( @@ -286,6 +350,14 @@ where TransactionEventData::TransactionFeeComputed { fee_asset } => { self.on_tx_fee_computed(fee_asset).map_err(EventError::from) }, + TransactionEventData::AccountVaultAssetWitness { + current_account_id, + vault_root, + asset, + } => self + .on_account_vault_asset_witness_requested(current_account_id, vault_root, asset) + .await + .map_err(EventError::from), } } } diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index ffe2e74aa2..826789537e 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -4,7 +4,7 @@ use alloc::vec::Vec; use miden_lib::errors::TransactionKernelError; use miden_lib::transaction::TransactionKernel; -use miden_objects::account::{AccountId, PartialAccount}; +use miden_objects::account::AccountId; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::debuginfo::SourceManagerSync; use miden_objects::asset::Asset; @@ -277,8 +277,7 @@ where validate_account_inputs(tx_args, &ref_block)?; - let partial_account = PartialAccount::from(account); - let tx_inputs = TransactionInputs::new(partial_account, seed, ref_block, mmr, notes) + let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes) .map_err(TransactionExecutorError::InvalidTransactionInputs)?; let (stack_inputs, advice_inputs) = diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index f52d32e817..6660a20a9a 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -22,10 +22,14 @@ use alloc::collections::BTreeMap; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_lib::transaction::memory::{CURRENT_INPUT_NOTE_PTR, NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR}; +use miden_lib::transaction::memory::{ + ACCOUNT_STACK_TOP_PTR, + CURRENT_INPUT_NOTE_PTR, + NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR, +}; use miden_lib::transaction::{TransactionEvent, TransactionEventError, TransactionKernelError}; -use miden_objects::account::{AccountDelta, PartialAccount}; -use miden_objects::asset::{Asset, FungibleAsset}; +use miden_objects::account::{AccountDelta, AccountHeader, AccountId, PartialAccount}; +use miden_objects::asset::{Asset, AssetVault, FungibleAsset}; use miden_objects::note::NoteId; use miden_objects::transaction::{ InputNote, @@ -38,6 +42,7 @@ use miden_objects::transaction::{ use miden_objects::vm::RowIndex; use miden_objects::{Hasher, Word}; use miden_processor::{ + AdviceError, AdviceMutation, ContextId, EventError, @@ -64,6 +69,9 @@ pub struct TransactionBaseHost<'store, STORE> { /// include input note scripts and the transaction script, but not account code. scripts_mast_store: ScriptMastForestStore, + /// The header of the account at the beginning of transaction execution. + initial_account_header: AccountHeader, + /// Account state changes accumulated during transaction execution. /// /// The delta is updated by event handlers. @@ -104,6 +112,7 @@ where Self { mast_store, scripts_mast_store, + initial_account_header: account.into(), account_delta: AccountDeltaTracker::new( account.id(), account.storage().header().clone(), @@ -132,6 +141,12 @@ where &self.tx_progress } + /// Returns a reference to the initial account header of the native account, which represents + /// the state at the beginning of the transaction. + pub fn initial_account_header(&self) -> &AccountHeader { + &self.initial_account_header + } + /// Returns a reference to the account delta tracker of this transaction host. pub fn account_delta_tracker(&self) -> &AccountDeltaTracker { &self.account_delta @@ -143,7 +158,6 @@ where } /// Returns the input notes consumed in this transaction. - #[allow(unused)] pub fn input_notes(&self) -> InputNotes { self.input_notes.clone() } @@ -184,12 +198,16 @@ where } let advice_mutations = match transaction_event { - TransactionEvent::AccountVaultBeforeAddAsset => Ok(TransactionEventHandling::Handled(Vec::new())), + TransactionEvent::AccountVaultBeforeAddAsset => { + Self::on_account_vault_before_add_or_remove_asset(process) + }, TransactionEvent::AccountVaultAfterAddAsset => { self.on_account_vault_after_add_asset(process).map(|_| TransactionEventHandling::Handled(Vec::new())) }, - TransactionEvent::AccountVaultBeforeRemoveAsset => Ok(TransactionEventHandling::Handled(Vec::new())), + TransactionEvent::AccountVaultBeforeRemoveAsset => { + Self::on_account_vault_before_add_or_remove_asset(process) + }, TransactionEvent::AccountVaultAfterRemoveAsset => { self.on_account_vault_after_remove_asset(process).map(|_| TransactionEventHandling::Handled(Vec::new())) }, @@ -590,6 +608,67 @@ where Ok(()) } + /// Checks if the necessary witness for accessing the asset is already in the merkle store, + /// and if not, extracts all necessary data for requesting it. + /// + /// Expected stack state: `[ASSET, account_vault_root_ptr]` + pub fn on_account_vault_before_add_or_remove_asset( + process: &ProcessState, + ) -> Result { + let asset: Asset = process.get_stack_word(0).try_into().map_err(|source| { + TransactionKernelError::MalformedAssetInEventHandler { + handler: "on_account_vault_before_add_or_remove_asset", + source, + } + })?; + + let vault_root_ptr = process.get_stack_item(4); + let vault_root_ptr = u32::try_from(vault_root_ptr).map_err(|_err| { + TransactionKernelError::other(format!( + "vault root ptr should fit into a u32, but was {vault_root_ptr}" + )) + })?; + let vault_root = process + .get_mem_word(process.ctx(), vault_root_ptr) + .map_err(|_err| { + TransactionKernelError::other(format!( + "vault root ptr {vault_root_ptr} is not word-aligned" + )) + })? + .ok_or_else(|| { + TransactionKernelError::other(format!( + "vault root ptr {vault_root_ptr} was not initialized" + )) + })?; + + let current_account_id = Self::get_current_account_id(process)?; + + let leaf_index = AssetVault::vault_key_to_leaf_index(asset.vault_key()); + match process.advice_provider().get_merkle_path( + vault_root, + &Felt::from(AssetVault::DEPTH), + &leaf_index, + ) { + // Merkle path is already in the store; consider the event handled. + Ok(_) => Ok(TransactionEventHandling::Handled(Vec::new())), + // This means the merkle path is missing in the advice provider. + Err(AdviceError::MerkleStoreLookupFailed(_)) => { + Ok(TransactionEventHandling::Unhandled( + TransactionEventData::AccountVaultAssetWitness { + current_account_id, + vault_root, + asset, + }, + )) + }, + // We should never encounter this as long as our inputs to get_merkle_path are correct. + Err(err) => Err(TransactionKernelError::other_with_source( + "unexpected get_merkle_path error", + err, + )), + } + } + /// Extracts the asset that is being removed from the account's vault from the process state /// and updates the appropriate fungible or non-fungible asset map. /// @@ -645,6 +724,40 @@ where } } + /// Returns the ID of the currently executing account. + fn get_current_account_id(process: &ProcessState) -> Result { + let account_stack_top_ptr = + process.get_mem_value(process.ctx(), ACCOUNT_STACK_TOP_PTR).ok_or_else(|| { + TransactionKernelError::other("account stack top ptr should be initialized") + })?; + let account_stack_top_ptr = u32::try_from(account_stack_top_ptr).map_err(|_| { + TransactionKernelError::other("account stack top ptr should fit into a u32") + })?; + + let current_account_ptr = process + .get_mem_value(process.ctx(), account_stack_top_ptr) + .ok_or_else(|| TransactionKernelError::other("account id should be initialized"))?; + let current_account_ptr = u32::try_from(current_account_ptr).map_err(|_| { + TransactionKernelError::other("current account ptr should fit into a u32") + })?; + + let current_account_id_and_nonce = process + .get_mem_word(process.ctx(), current_account_ptr) + .map_err(|_| { + TransactionKernelError::other("current account ptr should be word-aligned") + })? + .ok_or_else(|| { + TransactionKernelError::other("current account id should be initialized") + })?; + + AccountId::try_from([current_account_id_and_nonce[1], current_account_id_and_nonce[0]]) + .map_err(|_| { + TransactionKernelError::other( + "current account id ptr should point to a valid account ID", + ) + }) + } + /// Returns the number of storage slots initialized for the current account. /// /// # Errors @@ -726,6 +839,13 @@ where } } +impl<'store, STORE> TransactionBaseHost<'store, STORE> { + /// Returns the underlying store of the base host. + pub fn store(&self) -> &'store STORE { + self.mast_store + } +} + /// Extracts a word from a slice of field elements. pub(crate) fn extract_word(commitments: &[Felt], start: usize) -> Word { Word::from([ @@ -761,4 +881,13 @@ pub(super) enum TransactionEventData { /// The fee asset extracted from the stack. fee_asset: FungibleAsset, }, + /// The data necessary to request an asset witness from the data store. + AccountVaultAssetWitness { + /// The account ID for whose vault a witness is requested. + current_account_id: AccountId, + /// The vault root identifying the asset vault from which a witness is requested. + vault_root: Word, + /// The asset for which a witness is requested. + asset: Asset, + }, } diff --git a/crates/miden-tx/src/prover/prover_host.rs b/crates/miden-tx/src/prover/prover_host.rs index c3c2accda0..fd6f0eac75 100644 --- a/crates/miden-tx/src/prover/prover_host.rs +++ b/crates/miden-tx/src/prover/prover_host.rs @@ -123,6 +123,9 @@ where TransactionEventData::AuthRequest { .. } => { Err(EventError::from("base host should have handled auth request event")) }, + // Witnesses should be in the advice provider at proving time, so there is + // nothing to do. + TransactionEventData::AccountVaultAssetWitness { .. } => Ok(Vec::new()), // We don't track enough information to handle this event. Since this just // improves error messages for users and the error should not be relevant during // proving, we ignore it. From 102248da37569fb4de0a9d01945c92857728b3a9 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 8 Sep 2025 09:49:41 +0200 Subject: [PATCH 022/133] feat: load the initial fee asset lazily (#1856) * feat: Load fee asset lazily * chore: add changelog * chore: describe test setup in more detail * chore: Rename event to `EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT` * feat: Add `AssetWitness::find` and rename native asset to `fee_asset` * chore: Introduce asset witness -> advice mutation helper fn --- CHANGELOG.md | 1 + .../asm/kernels/transaction/lib/epilogue.masm | 4 +- crates/miden-lib/src/transaction/events.rs | 8 +- .../src/asset/vault/asset_witness.rs | 31 ++++- .../src/kernel_tests/tx/test_lazy_loading.rs | 29 +++- crates/miden-tx/src/executor/exec_host.rs | 129 +++++++++--------- crates/miden-tx/src/executor/mod.rs | 1 - crates/miden-tx/src/host/mod.rs | 7 +- 8 files changed, 137 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a3ff95a5f..e10d7101fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). - Enable lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). - Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). +- Lazy load the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). - Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). ### Changes diff --git a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm index 457206d1c5..fff9ad0b36 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -25,7 +25,7 @@ const.ERR_AUTH_PROCEDURE_CALLED_FROM_WRONG_CONTEXT="auth procedure had been call const.EPILOGUE_AFTER_TX_CYCLES_OBTAINED=131097 # Event emitted to signal that the fee was computed. -const.EPILOGUE_TX_FEE_COMPUTED=131098 +const.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT=131098 # An additional number of cyclces to account for the number of cycles that smt::set will take when # removing the computed fee from the asset vault. @@ -307,7 +307,7 @@ proc.compute_and_remove_fee exec.build_native_fee_asset # => [FEE_ASSET] - emit.EPILOGUE_TX_FEE_COMPUTED + emit.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT # => [FEE_ASSET] # remove the fee from the native account's vault diff --git a/crates/miden-lib/src/transaction/events.rs b/crates/miden-lib/src/transaction/events.rs index 2ba78b2ff7..4909a7bfc8 100644 --- a/crates/miden-lib/src/transaction/events.rs +++ b/crates/miden-lib/src/transaction/events.rs @@ -47,7 +47,7 @@ const TX_SCRIPT_PROCESSING_END: u32 = 0x2_0017; // 131095 const EPILOGUE_START: u32 = 0x2_0018; // 131096 const EPILOGUE_TX_CYCLES_OBTAINED: u32 = 0x2_0019; // 131097 -const EPILOGUE_TX_FEE_COMPUTED: u32 = 0x2_001a; // 131098 +const EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT: u32 = 0x2_001a; // 131098 const EPILOGUE_END: u32 = 0x2_001b; // 131099 const LINK_MAP_SET_EVENT: u32 = 0x2_001c; // 131100 @@ -104,7 +104,7 @@ pub enum TransactionEvent { EpilogueStart = EPILOGUE_START, EpilogueTxCyclesObtained = EPILOGUE_TX_CYCLES_OBTAINED, - EpilogueTxFeeComputed = EPILOGUE_TX_FEE_COMPUTED, + EpilogueBeforeTxFeeRemovedFromAccount = EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT, EpilogueEnd = EPILOGUE_END, LinkMapSetEvent = LINK_MAP_SET_EVENT, @@ -185,7 +185,9 @@ impl TryFrom for TransactionEvent { EPILOGUE_START => Ok(TransactionEvent::EpilogueStart), EPILOGUE_TX_CYCLES_OBTAINED => Ok(TransactionEvent::EpilogueTxCyclesObtained), - EPILOGUE_TX_FEE_COMPUTED => Ok(TransactionEvent::EpilogueTxFeeComputed), + EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT => { + Ok(TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount) + }, EPILOGUE_END => Ok(TransactionEvent::EpilogueEnd), LINK_MAP_SET_EVENT => Ok(TransactionEvent::LinkMapSetEvent), diff --git a/crates/miden-objects/src/asset/vault/asset_witness.rs b/crates/miden-objects/src/asset/vault/asset_witness.rs index 772c0421f6..7193d8bb4f 100644 --- a/crates/miden-objects/src/asset/vault/asset_witness.rs +++ b/crates/miden-objects/src/asset/vault/asset_witness.rs @@ -1,7 +1,7 @@ -use miden_crypto::merkle::{InnerNodeInfo, SmtProof}; +use miden_crypto::merkle::{InnerNodeInfo, SmtLeaf, SmtProof}; -use crate::AssetError; use crate::asset::Asset; +use crate::{AssetError, Word}; /// A witness of an asset in an [`AssetVault`](super::AssetVault). /// @@ -10,6 +10,9 @@ use crate::asset::Asset; pub struct AssetWitness(SmtProof); impl AssetWitness { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + /// Creates a new [`AssetWitness`] from an SMT proof. /// /// # Errors @@ -39,6 +42,30 @@ impl AssetWitness { Self(smt_proof) } + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Searches for an [`Asset`] in the witness with the given `vault_key`. + pub fn find(&self, vault_key: Word) -> Option { + self.assets().find(|asset| asset.vault_key() == vault_key) + } + + /// Returns an iterator over the [`Asset`]s in this witness. + pub fn assets(&self) -> impl Iterator { + // TODO: Avoid cloning the vector by not calling SmtLeaf::entries. + // Once SmtLeaf::entries returns a slice (i.e. once + // https://github.com/0xMiden/crypto/pull/521 is available), replace this match statement. + let entries = match self.0.leaf() { + SmtLeaf::Empty(_) => &[], + SmtLeaf::Single(kv_pair) => core::slice::from_ref(kv_pair), + SmtLeaf::Multiple(kv_pairs) => kv_pairs, + }; + + entries.iter().map(|(_key, value)| { + Asset::try_from(value).expect("asset witness should track valid assets") + }) + } + /// Returns an iterator over every inner node of this witness' merkle path. pub fn authenticated_nodes(&self) -> impl Iterator + '_ { self.0 diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index 97c687d233..c83d4ce302 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -7,13 +7,14 @@ use miden_lib::utils::ScriptBuilder; use miden_objects::account::AccountId; use miden_objects::asset::{Asset, FungibleAsset}; use miden_objects::testing::account_id::{ + ACCOUNT_ID_NATIVE_ASSET_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, }; use miden_objects::testing::constants::FUNGIBLE_ASSET_AMOUNT; use super::Word; -use crate::{MockChain, TransactionContextBuilder}; +use crate::{Auth, MockChain, TransactionContextBuilder}; /// Tests that adding two different assets to the account vault succeeds when lazy loading is /// enabled. @@ -134,3 +135,29 @@ fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { Ok(()) } + +/// Tests that a transaction against an account with a non-empty vault successfully loads the fee +/// asset during the epilogue. +/// +/// The non-empty vault is important for the test because the advice provider's merkle store has all +/// merkle paths for an empty vault by default, and so there would be nothing to load. +#[test] +fn loading_fee_asset_succeeds() -> anyhow::Result<()> { + let mut builder = + MockChain::builder().native_asset_id(ACCOUNT_ID_NATIVE_ASSET_FAUCET.try_into()?); + let account = builder.add_existing_mock_account_with_assets( + Auth::IncrNonce, + [ + FungibleAsset::mock(23), + FungibleAsset::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into()?, 50)?.into(), + ], + )?; + builder + .build()? + .build_tx_context(account, &[], &[])? + .enable_partial_loading() + .build()? + .execute_blocking()?; + + Ok(()) +} diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 9a8781326a..2fd6ecaf79 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -8,8 +8,7 @@ use miden_lib::transaction::TransactionEvent; use miden_objects::account::{AccountDelta, AccountId, PartialAccount}; use miden_objects::assembly::debuginfo::Location; use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; -use miden_objects::asset::{Asset, FungibleAsset}; -use miden_objects::block::FeeParameters; +use miden_objects::asset::{Asset, AssetWitness, FungibleAsset}; use miden_objects::crypto::merkle::SmtProof; use miden_objects::transaction::{InputNote, InputNotes, OutputNote}; use miden_objects::vm::AdviceMap; @@ -63,9 +62,6 @@ where /// authenticator that produced it. generated_signatures: BTreeMap>, - /// The balance of the native asset in the account at the beginning of transaction execution. - initial_native_asset: FungibleAsset, - /// The source manager to track source code file span information, improving any MASM related /// error messages. source_manager: Arc, @@ -87,32 +83,8 @@ where scripts_mast_store: ScriptMastForestStore, acct_procedure_index_map: AccountProcedureIndexMap, authenticator: Option<&'auth AUTH>, - fee_parameters: &FeeParameters, source_manager: Arc, ) -> Self { - // TODO: Once we have lazy account loading, this should be loaded in on_tx_fee_computed to - // avoid the use of PartialVault entirely, which in the future, may or may not track - // all assets in the account at this point. Here we assume it does track _all_ assets of the - // account. - let initial_native_asset = { - let native_asset = FungibleAsset::new(fee_parameters.native_asset_id(), 0) - .expect("native asset ID should be a valid fungible faucet ID"); - - // Map Asset to FungibleAsset. - // SAFETY: We requested a fungible vault key, so if Some is returned, it should be a - // fungible asset. - // A returned error means the vault does not track or does not contain the asset. - // However, since in practice, the partial vault represents the entire account vault, - // we can assume the second case. A returned None means the asset's amount is - // zero. - // So in both Err and None cases, use the default native_asset with amount 0. - account - .vault() - .get(native_asset.vault_key()) - .map(|asset| asset.map(|asset| asset.unwrap_fungible()).unwrap_or(native_asset)) - .unwrap_or(native_asset) - }; - let base_host = TransactionBaseHost::new( account, input_notes, @@ -125,7 +97,6 @@ where base_host, authenticator, generated_signatures: BTreeMap::new(), - initial_native_asset, source_manager, } } @@ -164,53 +135,80 @@ where Ok(vec![AdviceMutation::extend_stack(signature)]) } - /// Handles the [`TransactionEvent::EpilogueTxFeeComputed`] and returns an error if the account - /// cannot pay the fee. - fn on_tx_fee_computed( + /// Handles the [`TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount`] and returns an error + /// if the account cannot pay the fee. + async fn on_before_tx_fee_removed_from_account( &self, fee_asset: FungibleAsset, ) -> Result, TransactionKernelError> { + let asset_witness = self + .base_host + .store() + .get_vault_asset_witness( + self.base_host.initial_account_header().id(), + self.base_host.initial_account_header().vault_root(), + fee_asset.vault_key(), + ) + .await + .map_err(|err| TransactionKernelError::GetVaultAssetWitness { + vault_root: self.base_host.initial_account_header().vault_root(), + vault_key: fee_asset.vault_key(), + source: Box::new(err), + })?; + + // Find fee asset in the witness or default to 0 if it isn't present. + let initial_fee_asset = asset_witness + .find(fee_asset.vault_key()) + .and_then(|asset| match asset { + Asset::Fungible(fungible_asset) => Some(fungible_asset), + _ => None, + }) + .unwrap_or( + FungibleAsset::new(fee_asset.faucet_id(), 0) + .expect("fungible asset created from fee asset should be valid"), + ); + // Compute the current balance of the native asset in the account based on the initial value // and the delta. - let current_native_asset = { - let native_asset_amount_delta = self + let current_fee_asset = { + let fee_asset_amount_delta = self .base_host .account_delta_tracker() .vault_delta() .fungible() - .amount(&self.initial_native_asset.faucet_id()) + .amount(&initial_fee_asset.faucet_id()) .unwrap_or(0); // SAFETY: Initial native asset faucet ID should be a fungible faucet and amount should // be less than MAX_AMOUNT as checked by the account delta. - let native_asset_delta = FungibleAsset::new( - self.initial_native_asset.faucet_id(), - native_asset_amount_delta.unsigned_abs(), + let fee_asset_delta = FungibleAsset::new( + initial_fee_asset.faucet_id(), + fee_asset_amount_delta.unsigned_abs(), ) .expect("faucet ID and amount should be valid"); // SAFETY: These computations are essentially the same as the ones executed by the // transaction kernel, which should have aborted if they weren't valid. - if native_asset_amount_delta > 0 { - self.initial_native_asset - .add(native_asset_delta) + if fee_asset_amount_delta > 0 { + initial_fee_asset + .add(fee_asset_delta) .expect("transaction kernel should ensure amounts do not exceed MAX_AMOUNT") } else { - self.initial_native_asset - .sub(native_asset_delta) + initial_fee_asset + .sub(fee_asset_delta) .expect("transaction kernel should ensure amount is not negative") } }; // Return an error if the balance in the account does not cover the fee. - if current_native_asset.amount() < fee_asset.amount() { + if current_fee_asset.amount() < fee_asset.amount() { return Err(TransactionKernelError::InsufficientFee { - account_balance: current_native_asset.amount(), + account_balance: current_fee_asset.amount(), tx_fee: fee_asset.amount(), }); } - Ok(Vec::new()) + Ok(asset_witness_to_advice_mutation(asset_witness)) } /// Handles a request to an asset witness by querying the data store for a merkle path. @@ -263,17 +261,7 @@ where source: Box::new(err), })?; - // Get the nodes in the proof and insert them into the merkle store. - let merkle_store_ext = - AdviceMutation::extend_merkle_store(asset_witness.authenticated_nodes()); - - let smt_proof = SmtProof::from(asset_witness); - let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([( - smt_proof.leaf().hash(), - smt_proof.leaf().to_elements(), - )])); - - Ok(vec![merkle_store_ext, map_ext]) + Ok(asset_witness_to_advice_mutation(asset_witness)) } /// Consumes `self` and returns the account delta, output notes, generated signatures and @@ -347,9 +335,10 @@ where .on_auth_requested(pub_key_hash, signing_inputs) .await .map_err(EventError::from), - TransactionEventData::TransactionFeeComputed { fee_asset } => { - self.on_tx_fee_computed(fee_asset).map_err(EventError::from) - }, + TransactionEventData::TransactionFeeComputed { fee_asset } => self + .on_before_tx_fee_removed_from_account(fee_asset) + .await + .map_err(EventError::from), TransactionEventData::AccountVaultAssetWitness { current_account_id, vault_root, @@ -362,3 +351,21 @@ where } } } + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Converts an [`AssetWitness`] into the set of advice mutations that need to be inserted in order +/// to access the asset. +fn asset_witness_to_advice_mutation(asset_witness: AssetWitness) -> Vec { + // Get the nodes in the proof and insert them into the merkle store. + let merkle_store_ext = AdviceMutation::extend_merkle_store(asset_witness.authenticated_nodes()); + + let smt_proof = SmtProof::from(asset_witness); + let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([( + smt_proof.leaf().hash(), + smt_proof.leaf().to_elements(), + )])); + + vec![merkle_store_ext, map_ext] +} diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index 826789537e..bdb8af60c7 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -309,7 +309,6 @@ where script_mast_store, acct_procedure_index_map, self.authenticator, - tx_inputs.block_header().fee_parameters(), self.source_manager.clone(), ); diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 6660a20a9a..aad1af6108 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -288,7 +288,7 @@ where self.tx_progress.epilogue_after_tx_cycles_obtained(process.clk()); Ok(TransactionEventHandling::Handled(vec![])) } - TransactionEvent::EpilogueTxFeeComputed => self.on_tx_fee_computed(process), + TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount => self.on_before_tx_fee_removed_from_account(process), TransactionEvent::EpilogueEnd => { self.tx_progress.end_epilogue(process.clk()); Ok(TransactionEventHandling::Handled(Vec::new())) @@ -385,14 +385,15 @@ where TransactionKernelError::Unauthorized(Box::new(tx_summary)) } - /// Extracts all necessary data to handle [`TransactionEvent::EpilogueTxFeeComputed`]. + /// Extracts all necessary data to handle + /// [`TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount`]. /// /// Expected stack state: /// /// ```text /// [FEE_ASSET] /// ``` - fn on_tx_fee_computed( + fn on_before_tx_fee_removed_from_account( &self, process: &ProcessState, ) -> Result { From f67f4c74076fdd0af90349495717bfbf848f3d17 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Mon, 8 Sep 2025 15:29:22 -0700 Subject: [PATCH 023/133] chore: fix build --- crates/miden-testing/src/kernel_tests/tx/test_fpi.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index c1d25bf7d8..e6e6e8dd46 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -843,7 +843,7 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { .with_dynamically_linked_library(first_foreign_account_component.library())? .compile_tx_script(code)?; - mock_chain + let executed_transaction = mock_chain .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(foreign_account_inputs) @@ -852,8 +852,6 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { .build()? .execute_blocking()?; - let executed_transaction = tx_context.execute_blocking()?; - // TODO: Remove later and add a integration test using FPI. LocalTransactionProver::default().prove(executed_transaction.into())?; From 46021b4c17bbc21866dd755323e6bd25bd9d60bd Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Tue, 9 Sep 2025 07:31:40 +0300 Subject: [PATCH 024/133] refactor: optimize kernel `note` procedures (#1834) --- CHANGELOG.md | 1 + .../asm/kernels/transaction/api.masm | 420 +++++++++++------- .../kernels/transaction/lib/input_note.masm | 36 +- .../asm/kernels/transaction/lib/memory.masm | 9 +- .../asm/kernels/transaction/lib/note.masm | 122 ----- crates/miden-lib/asm/miden/account.masm | 62 +-- crates/miden-lib/asm/miden/input_note.masm | 155 ++++++- .../asm/miden/kernel_proc_offsets.masm | 115 ++--- crates/miden-lib/asm/miden/note.masm | 266 +++++++---- crates/miden-lib/asm/miden/tx.masm | 2 +- .../miden-lib/src/errors/tx_kernel_errors.rs | 22 +- .../src/transaction/kernel_procedures.rs | 36 +- .../kernel_tests/tx/test_account_interface.rs | 2 +- .../src/kernel_tests/tx/test_input_note.rs | 138 +++++- .../src/kernel_tests/tx/test_note.rs | 162 +++---- docs/src/protocol_library.md | 12 +- 16 files changed, 922 insertions(+), 638 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8506f12482..909e5858fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Changes - [BREAKING] Incremented MSRV to 1.89. +- [BREAKING] Remove some of the `note` kernel procedures and use `input_note` procedures instead ([#1834](https://github.com/0xMiden/miden-base/pull/1834)). - [BREAKING] Remove versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). - [BREAKING] Move `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Replaced `Account` with `PartialAccount` in `TransactionInputs` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 3ff973e341..9f7b08478a 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -1,10 +1,7 @@ -use.std::sys - use.$kernel::account use.$kernel::account_delta use.$kernel::account_id use.$kernel::asset_vault -use.$kernel::constants use.$kernel::faucet use.$kernel::input_note use.$kernel::memory @@ -32,6 +29,18 @@ const.ERR_FAUCET_IS_NF_ASSET_ISSUED_PROC_CAN_ONLY_BE_CALLED_ON_NON_FUNGIBLE_FAUC const.ERR_KERNEL_PROCEDURE_OFFSET_OUT_OF_BOUNDS="provided kernel procedure offset is out of bounds" +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note assets of current note because no note is currently being processed" + +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_RECIPIENT_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note recipient of current note because no note is currently being processed" + +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note metadata of current note because no note is currently being processed" + +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note inputs of current note because no note is currently being processed" + +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SCRIPT_ROOT_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note script root of current note because no note is currently being processed" + +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SERIAL_NUMBER_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note serial number of current note because no note is currently being processed" + # AUTHENTICATION # ================================================================================================= @@ -602,7 +611,7 @@ export.account_has_non_fungible_asset # => [has_asset, pad(15)] end -### FAUCET ##################################### +### FAUCET ###################################### #! Mint an asset from the faucet the transaction is being executed against. #! @@ -731,149 +740,274 @@ export.faucet_is_non_fungible_asset_issued # => [is_issued, pad(15)] end -### NOTE ######################################## +### INPUT NOTE ################################## -#! Returns the number of assets and the assets commitment of the note currently being processed. +#! Returns the assets information of the specified input note. #! -#! Inputs: [pad(16)] +#! Inputs: [is_current_note, note_index, pad(14)] #! Outputs: [ASSETS_COMMITMENT, num_assets, pad(11)] #! #! Where: -#! - num_assets is the number of assets in the note currently being processed. -#! - ASSETS_COMMITMENT is a sequential hash of the assets in the note currently being processed. +#! - is_current_note is the boolean flag indicating whether we should return the assets data from +#! the currently processing note or from the note with the specified index. +#! - note_index is the index of the input note whose assets info should be returned. Notice that if +#! is_current_note is 1, note_index is ignored. +#! - ASSETS_COMMITMENT is a sequential hash of the assets in the specified input note. +#! - num_assets is the number of assets in the specified input note. #! #! Panics if: -#! - a note is not being processed. +#! - the note index is greater or equal to the total number of input notes. +#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! from incorrect context). #! #! Invocation: dynexec -export.note_get_assets_info +export.input_note_get_assets_info + # get the input note pointer depending on whether the requested note is current or it was + # requested by index. + exec.get_requested_note_ptr + # => [input_note_ptr, pad(15)] + + # assert the pointer is not zero - this would suggest the procedure has been called from an + # incorrect context + dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_WHILE_NO_NOTE_BEING_PROCESSED + # => [input_note_ptr, pad(15)] + # get the assets info - exec.note::get_assets_info - # => [ASSETS_COMMITMENT, num_assets, pad(16)] + exec.input_note::get_assets_info + # => [ASSETS_COMMITMENT, num_assets, pad(15)] # truncate the stack - repeat.5 - movup.5 drop - end + movupw.2 dropw # => [ASSETS_COMMITMENT, num_assets, pad(11)] end -#! Adds the ASSET to the note specified by the index. +#! Returns the recipient of the specified input note. #! -#! Inputs: [note_idx, ASSET, pad(11)] -#! Outputs: [note_idx, ASSET, pad(11)] +#! Inputs: [is_current_note, note_index, pad(15)] +#! Outputs: [RECIPIENT, pad(12)] #! #! Where: -#! - note_idx is the index of the note to which the asset is added. -#! - ASSET can be a fungible or non-fungible asset. +#! - is_current_note is the boolean flag indicating whether we should return the assets data from +#! the currently processing note or from the note with the specified index. +#! - note_index is the index of the input note whose assets info should be returned. Notice that if +#! is_current_note is 1, note_index is ignored. +#! - RECIPIENT is the commitment to the input note's script, inputs, the serial number. #! #! Panics if: -#! - the procedure is called when the current account is not the native one. +#! - the note index is greater or equal to the total number of input notes. +#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! from incorrect context). #! #! Invocation: dynexec -export.note_add_asset - # check that this procedure was executed against the native account - exec.memory::assert_native_account - # => [note_idx, ASSET, pad(11)] +export.input_note_get_recipient + # get the input note pointer depending on whether the requested note is current or it was + # requested by index. + exec.get_requested_note_ptr + # => [input_note_ptr, pad(15)] - # duplicate the asset word to be able to return it - movdn.4 dupw movup.8 - # => [note_idx, ASSET, ASSET, pad(11)] + # assert the pointer is not zero - this would suggest the procedure has been called from an + # incorrect context + dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_RECIPIENT_WHILE_NO_NOTE_BEING_PROCESSED + # => [input_note_ptr, pad(15)] - exec.tx::add_asset_to_note - # => [note_idx, ASSET, pad(11)] + # get the recipient + exec.memory::get_input_note_recipient + # => [RECIPIENT, pad(15)] + + # truncate the stack + swapw drop drop drop movdn.4 + # => [RECIPIENT, pad(12)] end -#! Returns the information about assets in the input note with the specified index. +#! Returns the metadata of the specified input note. #! -#! Inputs: [note_index, pad(15)] -#! Outputs: [ASSETS_COMMITMENT, num_assets, pad(11)] +#! Inputs: [is_current_note, note_index, pad(14)] +#! Outputs: [METADATA, pad(12)] #! #! Where: -#! - note_index is the index of the input note whose assets info should be returned. -#! - num_assets is the number of assets in the specified note. -#! - ASSETS_COMMITMENT is a sequential hash of the assets in the specified note. +#! - is_current_note is the boolean flag indicating whether we should return the metadata from +#! the currently processing note or from the note with the specified index. +#! - note_index is the index of the input note whose metadata should be returned. Notice that if +#! is_current_note is 1, note_index is ignored. +#! - METADATA is the metadata of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. +#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! from incorrect context). #! #! Invocation: dynexec -export.input_note_get_assets_info - # assert that the provided note index is less than the total number of input notes - exec.input_note::assert_note_index_in_bounds - # => [note_index, pad(15)] +export.input_note_get_metadata + # get the input note pointer depending on whether the requested note is current or it was + # requested by index. + exec.get_requested_note_ptr + # => [input_note_ptr, pad(15)] - # get the assets info - exec.input_note::get_assets_info - # => [ASSETS_COMMITMENT, num_assets, pad(16)] + # assert the pointer is not zero - this would suggest the procedure has been called from an + # incorrect context + dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED + # => [input_note_ptr, pad(15)] + + # get the metadata + exec.memory::get_input_note_metadata + # => [METADATA, pad(15)] # truncate the stack - repeat.5 - movup.5 drop - end - # => [ASSETS_COMMITMENT, num_assets, pad(11)] + swapw drop drop drop movdn.4 + # => [METADATA, pad(12)] end -#! Returns the recipient of the input note with the specified index. +#! Returns the serial number of the specified input note. #! -#! Inputs: [note_index, pad(15)] -#! Outputs: [RECIPIENT, pad(12)] +#! Inputs: [is_current_note, note_index, pad(14)] +#! Outputs: [SERIAL_NUMBER, pad(12)] #! #! Where: -#! - note_index is the index of the input note whose recipient should be returned. -#! - RECIPIENT is the commitment to the input note's script, inputs, the serial number. +#! - is_current_note is the boolean flag indicating whether we should return the serial number +#! of the currently processing note or of the note with the specified index. +#! - note_index is the index of the input note whose serial number should be returned. Notice that +#! if is_current_note is 1, note_index is ignored. +#! - SERIAL_NUMBER is the serial number of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. +#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! from incorrect context). #! #! Invocation: dynexec -export.input_note_get_recipient - # assert that the provided note index is less than the total number of input notes - exec.input_note::assert_note_index_in_bounds - # => [note_index, pad(15)] +export.input_note_get_serial_number + # get the input note pointer depending on whether the requested note is current or it was + # requested by index. + exec.get_requested_note_ptr + # => [input_note_ptr, pad(15)] - # get the note data pointer by the provided index - exec.memory::get_input_note_ptr - # => [note_ptr, pad(15)] + # assert the pointer is not zero - this would suggest the procedure has been called from an + # incorrect context + dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SERIAL_NUMBER_WHILE_NO_NOTE_BEING_PROCESSED + # => [input_note_ptr, pad(15)] - # get the recipient - exec.memory::get_input_note_recipient - # => [RECIPIENT, pad(15)] + # get the serial number using the note pointer + exec.memory::get_input_note_serial_num + # => [SERIAL_NUMBER, pad(15)] # truncate the stack - swapw drop drop drop movdn.4 - # => [RECIPIENT, pad(12)] + repeat.3 + movup.4 drop + end + # => [SERIAL_NUMBER, pad(12)] end -#! Returns the metadata of the input note with the specified index. +#! Returns the inputs commitment and length of the specified input note. #! -#! Inputs: [note_index, pad(15)] -#! Outputs: [METADATA, pad(12)] +#! Inputs: [is_current_note, note_index, pad(14)] +#! Outputs: [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11)] #! #! Where: -#! - note_index is the index of the input note whose metadata should be returned. -#! - METADATA is the metadata of the input note. +#! - is_current_note is the boolean flag indicating whether we should return the inputs commitment +#! and length from the currently processing note or from the note with the specified index. +#! - note_index is the index of the input note whose data should be returned. Notice that if +#! is_current_note is 1, note_index is ignored. +#! - NOTE_INPUTS_COMMITMENT is the inputs commitment of the specified input note. +#! - num_inputs is the number of inputs of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. +#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! from incorrect context). #! #! Invocation: dynexec -export.input_note_get_metadata - # assert that the provided note index is less than the total number of input notes - exec.input_note::assert_note_index_in_bounds - # => [note_index, pad(15)] +export.input_note_get_inputs_info + # get the input note pointer depending on whether the requested note is current or it was + # requested by index. + exec.get_requested_note_ptr + # => [input_note_ptr, pad(15)] + + # assert the pointer is not zero - this would suggest the procedure has been called from an + # incorrect context + dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_WHILE_NO_NOTE_BEING_PROCESSED + # => [input_note_ptr, pad(15)] + + # get the note inputs length + dup exec.memory::get_input_note_num_inputs swap + # => [input_note_ptr, num_inputs, pad(16)] + + # get the inputs commitment + exec.memory::get_input_note_inputs_commitment + # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(16)] - # get the note data pointer by the provided index - exec.memory::get_input_note_ptr - # => [note_ptr, pad(15)] + # truncate the stack + repeat.5 + movup.5 drop + end + # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11)] +end - # get the metadata - exec.memory::get_input_note_metadata - # => [METADATA, pad(15)] +#! Returns the script root of the specified input note. +#! +#! Inputs: [is_current_note, note_index, pad(14)] +#! Outputs: [SCRIPT_ROOT, pad(12)] +#! +#! Where: +#! - is_current_note is the boolean flag indicating whether we should return the inputs commitment +#! and length from the currently processing note or from the note with the specified index. +#! - note_index is the index of the input note whose data should be returned. Notice that if +#! is_current_note is 1, note_index is ignored. +#! - SCRIPT_ROOT is the script root of the specified input note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! from incorrect context). +#! +#! Invocation: dynexec +export.input_note_get_script_root + # get the input note pointer depending on whether the requested note is current or it was + # requested by index. + exec.get_requested_note_ptr + # => [input_note_ptr, pad(15)] + + # assert the pointer is not zero - this would suggest the procedure has been called from an + # incorrect context + dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SCRIPT_ROOT_WHILE_NO_NOTE_BEING_PROCESSED + # => [input_note_ptr, pad(15)] + + # get the script root using the note pointer + exec.memory::get_input_note_script_root + # => [SCRIPT_ROOT, pad(15)] # truncate the stack - swapw drop drop drop movdn.4 - # => [METADATA, pad(12)] + repeat.3 + movup.4 drop + end + # => [SCRIPT_ROOT, pad(12)] +end + +### OUTPUT NOTE ################################# + +#! Adds the ASSET to the note specified by the index. +#! +#! Inputs: [note_idx, ASSET, pad(11)] +#! Outputs: [note_idx, ASSET, pad(11)] +#! +#! Where: +#! - note_idx is the index of the note to which the asset is added. +#! - ASSET can be a fungible or non-fungible asset. +#! +#! Panics if: +#! - the procedure is called when the current account is not the native one. +#! +#! Invocation: dynexec +export.output_note_add_asset + # check that this procedure was executed against the native account + exec.memory::assert_native_account + # => [note_idx, ASSET, pad(11)] + + # duplicate the asset word to be able to return it + movdn.4 dupw movup.8 + # => [note_idx, ASSET, ASSET, pad(11)] + + exec.tx::add_asset_to_note + # => [note_idx, ASSET, pad(11)] end #! Returns the information about assets in the output note with the specified index. @@ -968,92 +1102,6 @@ export.output_note_get_metadata # => [METADATA, pad(12)] end -#! Returns the serial number of the note currently being processed. -#! -#! Inputs: [pad(16)] -#! Outputs: [SERIAL_NUMBER, pad(12)] -#! -#! Where: -#! - SERIAL_NUMBER is the serial number of the note currently being processed. -#! -#! Panics if: -#! - no note is not being processed. -#! -#! Invocation: dynexec -export.note_get_serial_number - exec.note::get_serial_number - # => [SERIAL_NUMBER, pad(16)] - - # truncate the stack - swapw dropw - # => [SERIAL_NUMBER, pad(12)] -end - -#! Returns the current note's inputs commitment and length. -#! -#! Inputs: [pad(16)] -#! Outputs: [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11)] -#! -#! Where: -#! - NOTE_INPUTS_COMMITMENT is the current note's inputs commitment. -#! - num_inputs is the number of input values of the current note. -#! -#! Invocation: dynexec -export.note_get_inputs_commitment_and_len - exec.memory::get_input_note_num_inputs - # => [num_inputs, pad(16)] - - exec.note::get_note_inputs_commitment - # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(16)] - - # truncate the stack - swapdw dropw dropw - # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11)] -end - -#! Returns the sender of the note currently being processed. -#! -#! Inputs: [pad(16)] -#! Outputs: [sender_id_prefix, sender_id_suffix, pad(14)] -#! -#! Where: -#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender account ID of the note -#! currently being processed. -#! -#! Panics if: -#! - a note is not being processed. -#! -#! Invocation: dynexec -export.note_get_sender - exec.note::get_sender - # => [sender_id_prefix, sender_id_suffix, pad(16)] - - # truncate the stack - movup.2 drop movup.2 drop - # => [sender_id_prefix, sender_id_suffix, pad(14)] -end - -#! Returns the script root of the note currently being processed. -#! -#! Inputs: [pad(16)] -#! Outputs: [script_root, pad(12)] -#! -#! Where: -#! - SCRIPT_ROOT is the script root of the note currently being processed. -#! -#! Panics if: -#! - no note is not being processed. -#! -#! Invocation: dynexec -export.note_get_script_root - exec.note::get_script_root - # => [SCRIPT_ROOT, pad(16)] - - # truncate the stack - swapw dropw - # => [SCRIPT_ROOT, pad(12)] -end - ### TRANSACTION ################################# #! Creates a new note and returns the index of the note. @@ -1416,3 +1464,39 @@ export.exec_kernel_proc dynexec # => [, ] end + +# HELPER PROCEDURES +# ================================================================================================= + +#! Returns the memory pointer to the input note, depending on whether the requested note is current +#! or it was requested by index. +#! +#! If the pointer to the currently processing note was requested, but no note is being executed, 0 +#! is returned. +#! +#! Inputs: [is_current_note, note_index] +#! Outputs: [input_note_ptr] +#! +#! Where: +#! - is_current_note is the boolean flag indicating whether we should return requested data from +#! the currently processing note or from the note with the specified index. +#! - note_index is the index of the input note whose data should be returned. Notice that if +#! is_current_note is 1, note_index is ignored. +#! - input_note_ptr is the pointer to the correct input note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +proc.get_requested_note_ptr + # get the memory pointer to the note with the specified index and verify it is valid + swap exec.input_note::get_input_note_ptr swap + # => [is_current_note, indexed_input_note_ptr] + + # get the memory pointer to the currently processed input note + exec.memory::get_current_input_note_ptr swap + # => [is_current_note, current_input_note_ptr, indexed_input_note_ptr] + + # If is_current_note flag is true (current note processing case), current_input_note_ptr remains + # on the stack. If it is false, indexed_input_note_ptr remains instead. + cdrop + # => [input_note_ptr] +end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/input_note.masm b/crates/miden-lib/asm/kernels/transaction/lib/input_note.masm index 43a1d73f9b..717bc67ee0 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/input_note.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/input_note.masm @@ -8,25 +8,19 @@ const.ERR_INPUT_NOTE_INDEX_OUT_OF_BOUNDS="requested input note index should be l # INPUT NOTE PROCEDURES # ================================================================================================= -#! Returns the information about assets in the input note with the specified index. +#! Returns the information about assets in the input note with the specified memory pointer. #! -#! The provided input note index is expected to be less than the total number of input notes. -#! -#! Inputs: [note_index] +#! Inputs: [input_note_ptr] #! Outputs: [ASSETS_COMMITMENT, num_assets] #! #! Where: -#! - note_index is the index of the input note whose assets info should be returned. +#! - input_note_ptr is the memory address of the data segment for the requested input note. #! - num_assets is the number of assets in the specified note. #! - ASSETS_COMMITMENT is a sequential hash of the assets in the specified note. export.get_assets_info - # get the memory pointer to the requested note - exec.memory::get_input_note_ptr - # => [ptr] - # get the number of assets in the note dup exec.memory::get_input_note_num_assets - # => [num_assets, ptr] + # => [num_assets, input_note_ptr] # get the assets commitment from the note pointer swap exec.memory::get_input_note_assets_commitment @@ -46,3 +40,25 @@ export.assert_note_index_in_bounds u32lt assert.err=ERR_INPUT_NOTE_INDEX_OUT_OF_BOUNDS # => [note_index] end + +#! Computes a pointer to the memory address at which the data associated with an input note with +#! index `idx` is stored. +#! +#! Inputs: [idx] +#! Outputs: [note_ptr] +#! +#! Where: +#! - idx is the index of the input note. +#! - note_ptr is the memory address of the data segment for the input note with `idx`. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +export.get_input_note_ptr + # asset that the provided input note index is valid + exec.assert_note_index_in_bounds + # => [idx] + + # get the memory pointer to the specified input note + exec.memory::get_input_note_ptr + # => [note_ptr] +end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index efd4a4cee9..39c67f916a 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -1591,18 +1591,18 @@ end #! Returns the number of inputs of the note currently being processed. #! -#! Inputs: [] +#! Inputs: [note_ptr] #! Outputs: [num_inputs] #! #! Where: -#! - num_inputs is the number of inputs in the input note. +#! - note_ptr is the memory address at which the input note data begins. +#! - num_inputs is the number of inputs in in the input note. export.get_input_note_num_inputs - exec.get_current_input_note_ptr add.INPUT_NOTE_NUM_INPUTS_OFFSET mem_load end -#! Sets the number of input values for an input note located at the specified memory address. +#! Sets the number of inputs for an input note located at the specified memory address. #! #! Inputs: [note_ptr, num_inputs] #! Outputs: [] @@ -1737,7 +1737,6 @@ export.get_input_note_sender # reassemble the suffix by multiplying the high part with 2^32 and adding the lo part mul.0x0100000000 add swap # => [sender_id_prefix, sender_id_suffix] - end # OUTPUT NOTES diff --git a/crates/miden-lib/asm/kernels/transaction/lib/note.masm b/crates/miden-lib/asm/kernels/transaction/lib/note.masm index 4b07767651..7ef13aa9a9 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/note.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/note.masm @@ -6,12 +6,6 @@ use.$kernel::memory # ERRORS # ================================================================================================= -const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SENDER_FROM_INCORRECT_CONTEXT="attempted to access note sender from incorrect context" - -const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_FROM_INCORRECT_CONTEXT="attempted to access note assets from incorrect context" - -const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_FROM_INCORRECT_CONTEXT="attempted to access note inputs from incorrect context" - const.ERR_NOTE_NUM_OF_ASSETS_EXCEED_LIMIT="number of assets in a note exceed 255" # CONSTANTS @@ -24,86 +18,6 @@ const.OUTPUT_NOTE_HASHING_MEM_DIFF=2040 # CURRENTLY EXECUTING NOTE PROCEDURES # ================================================================================================= -#! Returns the sender of the note currently being processed. -#! -#! Inputs: [] -#! Outputs: [sender_id_prefix, sender_id_suffix] -#! -#! Where: -#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender of the note currently -#! being processed. -#! -#! Panics if: -#! - the note is not being processed. -export.get_sender - # get the current input note pointer - exec.memory::get_current_input_note_ptr - # => [ptr] - - # assert the pointer is not zero - this would suggest the procedure has been called from an - # incorrect context - dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SENDER_FROM_INCORRECT_CONTEXT - # => [ptr] - - # get the sender from the note pointer - exec.memory::get_input_note_sender - # => [sender_id_prefix, sender_id_suffix] -end - -#! Returns the number of assets and the assets commitment of the note currently being processed. -#! -#! Inputs: [] -#! Outputs: [ASSETS_COMMITMENT, num_assets] -#! -#! Where: -#! - num_assets is the number of assets in the note currently being processed. -#! - ASSETS_COMMITMENT is a sequential hash of the assets in the note currently being processed. -#! -#! Panics if: -#! - the note is not being processed. -export.get_assets_info - # get the current input note pointer - exec.memory::get_current_input_note_ptr - # => [ptr] - - # assert the pointer is not zero - this would suggest the procedure has been called from an - # incorrect context - dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_FROM_INCORRECT_CONTEXT - # => [ptr] - - # get the number of assets in the note - dup exec.memory::get_input_note_num_assets - # => [num_assets, ptr] - - # get the assets commitment from the note pointer - swap exec.memory::get_input_note_assets_commitment - # => [ASSETS_COMMITMENT, num_assets] -end - -#! Returns the commitment to the note's inputs. -#! -#! Inputs: [] -#! Outputs: [NOTE_INPUTS_COMMITMENT] -#! -#! Where: -#! - NOTE_INPUTS_COMMITMENT is the note inputs commitment of the note currently being processed. -#! -#! Panics if: -#! - the note is not being processed. -export.get_note_inputs_commitment - exec.memory::get_current_input_note_ptr - # => [ptr] - - # The kernel memory is initialized by prologue::process_input_notes_data, and reset by - # note_processing_teardown before running the tx_script. If the value is `0` it is likely this - # procedure is being called outside of the kernel context. - dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_FROM_INCORRECT_CONTEXT - # => [ptr] - - exec.memory::get_input_note_inputs_commitment - # => [NOTE_INPUTS_COMMITMENT] -end - #! Move the current input note pointer to the next note and returns the pointer value. #! #! Inputs: [] @@ -345,39 +259,3 @@ export.compute_output_notes_commitment movup.4 drop # => [OUTPUT_NOTES_COMMITMENT, ...] end - -#! Returns the serial number of the note currently being processed. -#! -#! Inputs: [] -#! Outputs: [SERIAL_NUMBER] -#! -#! Where: -#! - SERIAL_NUMBER is the serial number of the note currently being processed. -#! -#! Panics if: -#! - no note is being processed. -export.get_serial_number - exec.memory::get_current_input_note_ptr - # => [note_ptr, ...] - - exec.memory::get_input_note_serial_num - # => [SERIAL_NUMBER, ...] -end - -#! Returns the script root of the note currently being processed. -#! -#! Inputs: [] -#! Outputs: [SCRIPT_ROOT] -#! -#! Where: -#! - SCRIPT_ROOT is the serial number of the note currently being processed. -#! -#! Panics if: -#! - no note is being processed. -export.get_script_root - exec.memory::get_current_input_note_ptr - # => [note_ptr] - - exec.memory::get_input_note_script_root - # => [SCRIPT_ROOT] -end diff --git a/crates/miden-lib/asm/miden/account.masm b/crates/miden-lib/asm/miden/account.masm index 06cbe70748..278b859d4f 100644 --- a/crates/miden-lib/asm/miden/account.masm +++ b/crates/miden-lib/asm/miden/account.masm @@ -121,6 +121,37 @@ export.get_native_nonce # => [nonce] end +#! Increments the account nonce by one and returns the new nonce. +#! +#! Inputs: [] +#! Outputs: [final_nonce] +#! +#! Where: +#! - final_nonce is the new nonce of the account. Since it cannot be incremented again, this will +#! also be the final nonce of the account after transaction execution. +#! +#! Panics if: +#! - the invocation of this procedure does not originate from the native account. +#! - the invocation of this procedure does not originate from the authentication procedure +#! of the account. +#! - the nonce has already been incremented. +#! +#! Invocation: exec +export.incr_nonce + # pad the stack + padw padw padw push.0.0.0 + # => [pad(15)] + + exec.kernel_proc_offsets::account_incr_nonce_offset + # => [offset, pad(15)] + + syscall.exec_kernel_proc + # => [final_nonce, pad(15)] + + swap.15 dropw dropw dropw drop drop drop + # => [final_nonce] +end + #! Returns the native account commitment at the beginning of the transaction. #! #! Inputs: [] @@ -201,37 +232,6 @@ export.compute_delta_commitment # => [DELTA_COMMITMENT] end -#! Increments the account nonce by one and returns the new nonce. -#! -#! Inputs: [] -#! Outputs: [final_nonce] -#! -#! Where: -#! - final_nonce is the new nonce of the account. Since it cannot be incremented again, this will -#! also be the final nonce of the account after transaction execution. -#! -#! Panics if: -#! - the invocation of this procedure does not originate from the native account. -#! - the invocation of this procedure does not originate from the authentication procedure -#! of the account. -#! - the nonce has already been incremented. -#! -#! Invocation: exec -export.incr_nonce - # pad the stack - padw padw padw push.0.0.0 - # => [pad(15)] - - exec.kernel_proc_offsets::account_incr_nonce_offset - # => [offset, pad(15)] - - syscall.exec_kernel_proc - # => [final_nonce, pad(15)] - - swap.15 dropw dropw dropw drop drop drop - # => [final_nonce] -end - #! Gets an item from the account storage. Panics if the index is out of bounds. #! #! Inputs: [index] diff --git a/crates/miden-lib/asm/miden/input_note.masm b/crates/miden-lib/asm/miden/input_note.masm index a2bb17b58f..a3a07ecead 100644 --- a/crates/miden-lib/asm/miden/input_note.masm +++ b/crates/miden-lib/asm/miden/input_note.masm @@ -22,15 +22,20 @@ use.miden::note #! Invocation: exec export.get_assets_info # start padding the stack - push.0.0 movup.2 - # => [note_index, 0, 0] + push.0 swap + # => [note_index, 0] + + # push the flag indicating that we want to request assets info from the note with the specified + # index + push.0 + # => [is_current_note = 0, note_index, 0] exec.kernel_proc_offsets::input_note_get_assets_info_offset - # => [offset, note_index, 0, 0] + # => [offset, is_current_note = 0, note_index, 0] # pad the stack padw swapw padw padw swapdw - # => [offset, note_index, pad(14)] + # => [offset, 0, note_index, pad(13)] syscall.exec_kernel_proc # => [ASSETS_COMMITMENT, num_assets, pad(11)] @@ -91,11 +96,16 @@ end #! Invocation: exec export.get_recipient # start padding the stack - push.0.0 movup.2 - # => [note_index, 0, 0] + push.0 swap + # => [note_index, 0] + + # push the flag indicating that we want to request assets info from the note with the specified + # index + push.0 + # => [is_current_note = 0, note_index, 0] exec.kernel_proc_offsets::input_note_get_recipient_offset - # => [offset, note_index, 0, 0] + # => [offset, is_current_note = 0, note_index, 0] # pad the stack padw swapw padw padw swapdw @@ -124,15 +134,20 @@ end #! Invocation: exec export.get_metadata # start padding the stack - push.0.0 movup.2 - # => [note_index, 0, 0] + push.0 swap + # => [note_index, 0] + + # push the flag indicating that we want to request metadata from the note with the specified + # index + push.0 + # => [is_current_note = 0, note_index, 0] exec.kernel_proc_offsets::input_note_get_metadata_offset - # => [offset, note_index, 0, 0] + # => [offset, is_current_note = 0, note_index, 0] # pad the stack padw swapw padw padw swapdw - # => [offset, note_index, pad(14)] + # => [offset, is_current_note = 0, note_index, pad(13)] syscall.exec_kernel_proc # => [METADATA, pad(12)] @@ -141,3 +156,121 @@ export.get_metadata swapdw dropw dropw swapw dropw # => [METADATA] end + +#! Returns the inputs commitment and length of the input note with the specified index. +#! +#! Inputs: [note_index] +#! Outputs: [NOTE_INPUTS_COMMITMENT, num_inputs] +#! +#! Where: +#! - note_index is the index of the input note whose data should be returned. +#! - NOTE_INPUTS_COMMITMENT is the inputs commitment of the specified input note. +#! - num_inputs is the number of input values of the specified input note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! +#! Invocation: exec +export.get_inputs_info + # start padding the stack + push.0 swap + # => [note_index, 0] + + # push the flag indicating that we want to request inputs info from the note with the specified + # index + push.0 + # => [is_current_note = 0, note_index, 0] + + exec.kernel_proc_offsets::input_note_get_inputs_info_offset + # => [offset, is_current_note = 0, note_index, 0] + + # pad the stack + padw swapw padw padw swapdw + # => [offset, is_current_note = 0, note_index, pad(13)] + + syscall.exec_kernel_proc + # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11)] + + # clean the stack + swapdw dropw dropw + repeat.3 + movup.5 drop + end + # => [NOTE_INPUTS_COMMITMENT, num_inputs] +end + +#! Returns the script root of the input note with the specified index. +#! +#! Inputs: [note_index] +#! Outputs: [SCRIPT_ROOT] +#! +#! Where: +#! - note_index is the index of the input note whose script root should be returned. +#! - SCRIPT_ROOT is the script root of the specified input note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! +#! Invocation: exec +export.get_script_root + # start padding the stack + push.0 swap + # => [note_index, 0] + + # push the flag indicating that we want to request script root from the note with the specified + # index + push.0 + # => [is_current_note = 0, note_index, 0] + + exec.kernel_proc_offsets::input_note_get_script_root_offset + # => [offset, is_current_note = 0, note_index, 0] + + # pad the stack + padw swapw padw padw swapdw + # => [offset, is_current_note = 0, note_index, pad(13)] + + syscall.exec_kernel_proc + # => [SCRIPT_ROOT, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [SCRIPT_ROOT] +end + +#! Returns the serial number of the input note with the specified index. +#! +#! Inputs: [note_index] +#! Outputs: [SERIAL_NUMBER] +#! +#! Where: +#! - note_index is the index of the input note whose serial number should be returned. +#! - SERIAL_NUMBER is the serial number of the specified input note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! +#! Invocation: exec +export.get_serial_number + # start padding the stack + push.0 swap + # => [note_index, 0] + + # push the flag indicating that we want to request serial number from the note with the + # specified index + push.0 + # => [is_current_note = 0, note_index, 0] + + exec.kernel_proc_offsets::input_note_get_serial_number_offset + # => [offset, is_current_note = 0, note_index, 0] + + # pad the stack + padw swapw padw padw swapdw + # => [offset, is_current_note = 0, note_index, pad(13)] + + syscall.exec_kernel_proc + # => [SERIAL_NUMBER, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [SERIAL_NUMBER] +end diff --git a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm index efa5dea66f..bd00393ace 100644 --- a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm +++ b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm @@ -47,50 +47,45 @@ const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=24 const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=25 ### Note ######################################## -# assets -const.NOTE_GET_ASSETS_INFO_OFFSET=26 # accessor -const.NOTE_ADD_ASSET_OFFSET=27 # mutator - -# note parameters -const.NOTE_GET_SERIAL_NUMBER_OFFSET=28 -const.NOTE_GET_INPUTS_COMMITMENT_AND_LEN_OFFSET=29 -const.NOTE_GET_SENDER_OFFSET=30 -const.NOTE_GET_SCRIPT_ROOT_OFFSET=31 - -# note introspection -const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=32 -const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=33 -const.INPUT_NOTE_GET_RECIPIENT_OFFSET=34 -const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=35 +# input notes +const.INPUT_NOTE_GET_METADATA_OFFSET=26 +const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=27 +const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=28 +const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=29 +const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=30 +const.INPUT_NOTE_GET_RECIPIENT_OFFSET=31 -const.INPUT_NOTE_GET_METADATA_OFFSET=36 -const.OUTPUT_NOTE_GET_METADATA_OFFSET=37 +# output notes +const.OUTPUT_NOTE_GET_METADATA_OFFSET=32 +const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=33 +const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=34 +const.OUTPUT_NOTE_ADD_ASSET_OFFSET=35 ### Tx ########################################## # creation -const.TX_CREATE_NOTE_OFFSET=38 +const.TX_CREATE_NOTE_OFFSET=36 # input/output notes -const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=39 -const.TX_GET_NUM_INPUT_NOTES_OFFSET=40 +const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=37 +const.TX_GET_NUM_INPUT_NOTES_OFFSET=38 -const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=41 -const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=42 +const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=39 +const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=40 # block info -const.TX_GET_BLOCK_COMMITMENT_OFFSET=43 -const.TX_GET_BLOCK_NUMBER_OFFSET=44 -const.TX_GET_BLOCK_TIMESTAMP_OFFSET=45 +const.TX_GET_BLOCK_COMMITMENT_OFFSET=41 +const.TX_GET_BLOCK_NUMBER_OFFSET=42 +const.TX_GET_BLOCK_TIMESTAMP_OFFSET=43 # foreign context -const.TX_START_FOREIGN_CONTEXT_OFFSET=46 -const.TX_END_FOREIGN_CONTEXT_OFFSET=47 +const.TX_START_FOREIGN_CONTEXT_OFFSET=44 +const.TX_END_FOREIGN_CONTEXT_OFFSET=45 # expiration data -const.TX_GET_EXPIRATION_DELTA_OFFSET=48 # accessor -const.TX_UPDATE_EXPIRATION_BLOCK_NUM_OFFSET=49 # mutator +const.TX_GET_EXPIRATION_DELTA_OFFSET=46 # accessor +const.TX_UPDATE_EXPIRATION_BLOCK_NUM_OFFSET=47 # mutator # ACCESSORS # ------------------------------------------------------------------------------------------------- @@ -413,28 +408,16 @@ end ### NOTE ######################################## -#! Returns the offset of the `note_get_assets_info` kernel procedure. -#! -#! Inputs: [] -#! Outputs: [proc_offset] -#! -#! Where: -#! - proc_offset is the offset of the `note_get_assets_info` kernel procedure required to get the -#! address where this procedure is stored. -export.note_get_assets_info_offset - push.NOTE_GET_ASSETS_INFO_OFFSET -end - -#! Returns the offset of the `note_add_asset` kernel procedure. +#! Returns the offset of the `output_note_add_asset` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `note_add_asset` kernel procedure required to get the +#! - proc_offset is the offset of the `output_note_add_asset` kernel procedure required to get the #! address where this procedure is stored. -export.note_add_asset_offset - push.NOTE_ADD_ASSET_OFFSET +export.output_note_add_asset_offset + push.OUTPUT_NOTE_ADD_ASSET_OFFSET end #! Returns the offset of the `input_note_get_assets_info` kernel procedure. @@ -509,52 +492,40 @@ export.output_note_get_metadata_offset push.OUTPUT_NOTE_GET_METADATA_OFFSET end -#! Returns the offset of the `note_get_serial_number` kernel procedure. -#! -#! Inputs: [] -#! Outputs: [proc_offset] -#! -#! Where: -#! - proc_offset is the offset of the `note_get_serial_number` kernel procedure required to get the -#! address where this procedure is stored. -export.note_get_serial_number_offset - push.NOTE_GET_SERIAL_NUMBER_OFFSET -end - -#! Returns the offset of the `note_get_inputs_commitment_and_len` kernel procedure. +#! Returns the offset of the `input_note_get_serial_number` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `note_get_inputs_commitment_and_len` kernel procedure required -#! to get the address where this procedure is stored. -export.note_get_inputs_commitment_and_len_offset - push.NOTE_GET_INPUTS_COMMITMENT_AND_LEN_OFFSET +#! - proc_offset is the offset of the `input_note_get_serial_number` kernel procedure required to +#! get the address where this procedure is stored. +export.input_note_get_serial_number_offset + push.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET end -#! Returns the offset of the `note_get_sender` kernel procedure. +#! Returns the offset of the `input_note_get_inputs_info` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `note_get_sender` kernel procedure required to get the address -#! where this procedure is stored. -export.note_get_sender_offset - push.NOTE_GET_SENDER_OFFSET +#! - proc_offset is the offset of the `input_note_get_inputs_info` kernel procedure required to get +#! the address where this procedure is stored. +export.input_note_get_inputs_info_offset + push.INPUT_NOTE_GET_INPUTS_INFO_OFFSET end -#! Returns the offset of the `note_get_script_root` kernel procedure. +#! Returns the offset of the `input_note_get_script_root` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `note_get_script_root` kernel procedure required to get the -#! address where this procedure is stored. -export.note_get_script_root_offset - push.NOTE_GET_SCRIPT_ROOT_OFFSET +#! - proc_offset is the offset of the `input_note_get_script_root` kernel procedure required to get +#! the address where this procedure is stored. +export.input_note_get_script_root_offset + push.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET end ### TRANSACTION ################################# diff --git a/crates/miden-lib/asm/miden/note.masm b/crates/miden-lib/asm/miden/note.masm index 712a6929f8..63b96bef07 100644 --- a/crates/miden-lib/asm/miden/note.masm +++ b/crates/miden-lib/asm/miden/note.masm @@ -2,15 +2,16 @@ use.miden::kernel_proc_offsets use.std::crypto::hashes::rpo use.std::mem use.miden::contracts::wallets::basic->wallet +use.miden::account_id # ERRORS # ================================================================================================= const.ERR_NOTE_DATA_DOES_NOT_MATCH_COMMITMENT="note data does not match the commitment" -const.ERR_PROLOGUE_NUMBER_OF_NOTE_INPUTS_EXCEEDED_LIMIT="number of note inputs exceeded the maximum limit of 128" +const.ERR_PROLOGUE_NOTE_INPUTS_LEN_EXCEEDED_LIMIT="number of note inputs exceeded the maximum limit of 128" -const.ERR_NOTE_INVALID_NUMBER_OF_NOTE_INPUTS="the specified number of note inputs does not match the actual number" +const.ERR_NOTE_INVALID_NUMBER_OF_INPUTS="the specified number of note inputs does not match the actual number" # PROCEDURES # ================================================================================================= @@ -24,14 +25,22 @@ const.ERR_NOTE_INVALID_NUMBER_OF_NOTE_INPUTS="the specified number of note input #! - dest_ptr is the memory address to write the assets. #! - num_assets is the number of assets in the currently executing note. #! +#! Panics if: +#! - no note is being processed. +#! #! Invocation: exec export.get_assets # pad the stack - padw padw padw push.0.0.0 - # => [pad(15), dest_ptr] + padw padw padw push.0.0 + # => [pad(14), dest_ptr] + + # push the flag indicating that we want to request assets info from the currently processing + # note + push.1 + # => [is_current_note = 1, pad(14), dest_ptr] - exec.kernel_proc_offsets::note_get_assets_info_offset - # => [offset, pad(15), dest_ptr] + exec.kernel_proc_offsets::input_note_get_assets_info_offset + # => [offset, is_current_note = 1, pad(14), dest_ptr] syscall.exec_kernel_proc # => [ASSETS_COMMITMENT, num_assets, pad(11), dest_ptr] @@ -45,75 +54,80 @@ export.get_assets # => [num_assets, dest_ptr] end -#! Loads the note's inputs to `dest_ptr`. +#! Returns the recipient of the note currently being processed. +#! +#! Inputs: [] +#! Outputs: [RECIPIENT] +#! +#! Where: +#! - RECIPIENT is the commitment to the input note's script, inputs, the serial number. +#! +#! Panics if: +#! - no note is being processed. +#! +#! Invocation: exec +export.get_recipient + # pad the stack + padw padw padw push.0.0 + # => [pad(14)] + + # push the flag indicating that we want to request recipient from the currently processing + # note + push.1 + # => [is_current_note = 1, pad(14)] + + exec.kernel_proc_offsets::input_note_get_recipient_offset + # => [offset, is_current_note = 1, pad(14)] + + syscall.exec_kernel_proc + # => [RECIPIENT, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [RECIPIENT] +end + +#! Writes the note's inputs to `dest_ptr`. #! #! Inputs: #! Stack: [dest_ptr] -#! Advice Map: { INPUTS_COMMITMENT: [INPUTS] } +#! Advice Map: { NOTE_INPUTS_COMMITMENT: [INPUTS] } #! Outputs: #! Stack: [num_inputs, dest_ptr] #! #! Where: -#! - dest_ptr is the memory address to write the inputs. -#! - INPUTS_COMMITMENT is the sequential hash of the padded note's inputs. +#! - dest_ptr is the memory address to write the note inputs. +#! - NOTE_INPUTS_COMMITMENT is the sequential hash of the padded note's inputs. #! - INPUTS is the data corresponding to the note's inputs. #! +#! Panics if: +#! - no note is being processed. +#! #! Invocation: exec export.get_inputs # pad the stack - padw padw padw push.0.0.0 - # OS => [pad(15), dest_ptr] + padw padw padw push.0.0 + # => [pad(14), dest_ptr] + + # push the flag indicating that we want to request inputs info from the currently processing + # note + push.1 + # => [is_current_note = 1, pad(14), dest_ptr] - exec.kernel_proc_offsets::note_get_inputs_commitment_and_len_offset - # OS => [offset, pad(15), dest_ptr] + exec.kernel_proc_offsets::input_note_get_inputs_info_offset + # => [offset, is_current_note = 1, pad(14), dest_ptr] syscall.exec_kernel_proc - # OS => [INPUTS_COMMITMENT, num_inputs, pad(11), dest_ptr] + # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11), dest_ptr] # clean the stack swapdw dropw dropw movup.5 drop movup.5 drop movup.5 drop - # OS => [INPUTS_COMMITMENT, num_inputs, dest_ptr] - - # load the inputs from the advice map to the advice stack - adv.push_mapvaln - # OS => [INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [advice_num_inputs, [INPUT_VALUES]] - - # move the number of inputs obtained from advice map to the operand stack - adv_push.1 dup.5 - # OS => [num_inputs, advice_num_inputs, INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - # Validate the note inputs length. Round up the number of inputs to the next multiple of 8: that - # value should be equal to the length obtained from the `adv.push_mapvaln` procedure. - u32divmod.8 neq.0 add mul.8 - # OS => [rounded_up_num_inputs, advice_num_inputs, INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - assert_eq.err=ERR_NOTE_INVALID_NUMBER_OF_NOTE_INPUTS - # OS => [INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - # calculate the number of words required to store the inputs - dup.4 u32divmod.4 neq.0 add - # OS => [num_words, INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - # round up the number of words to the next multiple of 2 - dup is_odd add - # OS => [even_num_words, INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - # prepare the stack for the `pipe_preimage_to_memory` procedure - dup.6 swap - # OS => [even_num_words, dest_ptr, INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] + # => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] - # write the input values from the advice stack into memory - exec.mem::pipe_preimage_to_memory drop - # OS => [num_inputs, dest_ptr] - # AS => [] + # write the inputs to the memory using the provided destination pointer + exec.write_inputs_to_memory + # => [num_inputs, dest_ptr] end #! Returns the sender of the note currently being processed. @@ -131,17 +145,25 @@ end #! Invocation: exec export.get_sender # pad the stack - padw padw padw push.0.0.0 - # => [pad(15)] + padw padw padw push.0.0 + # => [pad(14)] + + # push the flag indicating that we want to request metadata from the currently processing note + push.1 + # => [is_current_note = 1, pad(14)] - exec.kernel_proc_offsets::note_get_sender_offset - # => [offset, pad(15)] + exec.kernel_proc_offsets::input_note_get_metadata_offset + # => [offset, is_current_note = 1, pad(14)] syscall.exec_kernel_proc - # => [sender, pad(15)] + # => [METADATA, pad(12)] + + # extract the sender ID from the metadata word + exec.extract_sender_from_metadata + # => [sender_id_prefix, sender_id_suffix, pad(12)] # clean the stack - swapdw dropw dropw swapw dropw movdn.3 movdn.3 drop drop + swapw dropw swapw dropw movdn.5 movdn.5 dropw # => [sender_id_prefix, sender_id_suffix] end @@ -159,11 +181,16 @@ end #! Invocation: exec export.get_serial_number # pad the stack - padw padw padw push.0.0.0 - # => [pad(15)] + padw padw padw push.0.0 + # => [pad(14)] + + # push the flag indicating that we want to request serial number from the currently processing + # note + push.1 + # => [is_current_note = 1, pad(14)] - exec.kernel_proc_offsets::note_get_serial_number_offset - # => [offset, pad(15)] + exec.kernel_proc_offsets::input_note_get_serial_number_offset + # => [offset, is_current_note = 1, pad(14)] syscall.exec_kernel_proc # => [SERIAL_NUMBER, pad(12)] @@ -173,17 +200,17 @@ export.get_serial_number # => [SERIAL_NUMBER] end -#! Computes the commitment to the note inputs starting at the specified memory address. +#! Computes the commitment to the output note inputs starting at the specified memory address. #! -#! This procedure checks that the provided number of inputs is within limits and then computes the -#! commitment. +#! This procedure checks that the provided number of note inputs is within limits and then computes +#! the commitment. #! #! Notice that the note inputs are padded with zeros in case their number is not a multiple of 8. #! -#! If the number if inputs is 0, procedure returns the empty word: [0, 0, 0, 0]. +#! If the number of note inputs is 0, procedure returns the empty word: [0, 0, 0, 0]. #! #! Inputs: [inputs_ptr, num_inputs] -#! Outputs: [COMMITMENT] +#! Outputs: [INPUTS_COMMITMENT] #! #! Cycles: #! - If number of elements divides by 8: 56 cycles + 3 * words @@ -196,8 +223,8 @@ end #! Invocation: exec export.compute_inputs_commitment # check that number of inputs is less than 128 - dup.1 push.128 u32assert2.err=ERR_PROLOGUE_NUMBER_OF_NOTE_INPUTS_EXCEEDED_LIMIT - u32lte assert.err=ERR_PROLOGUE_NUMBER_OF_NOTE_INPUTS_EXCEEDED_LIMIT + dup.1 push.128 u32assert2.err=ERR_PROLOGUE_NOTE_INPUTS_LEN_EXCEEDED_LIMIT + u32lte assert.err=ERR_PROLOGUE_NOTE_INPUTS_LEN_EXCEEDED_LIMIT # => [inputs_ptr, num_inputs] # push 1 as the pad_inputs flag: we should pad the stack while computing the note inputs @@ -208,7 +235,7 @@ export.compute_inputs_commitment exec.rpo::prepare_hasher_state exec.rpo::hash_memory_with_state - # => [COMMITMENT] + # => [INPUTS_COMMITMENT] end #! Returns the script root of the note currently being processed. @@ -225,11 +252,16 @@ end #! Invocation: exec export.get_script_root # pad the stack - padw padw padw push.0.0.0 - # => [pad(15)] + padw padw padw push.0.0 + # => [pad(14)] + + # push the flag indicating that we want to request script root from the currently processing + # note + push.1 + # => [is_current_note = 1, pad(14)] - exec.kernel_proc_offsets::note_get_script_root_offset - # => [offset, pad(15)] + exec.kernel_proc_offsets::input_note_get_script_root_offset + # => [offset, is_current_note = 1, pad(14)] syscall.exec_kernel_proc # => [SCRIPT_ROOT, pad(12)] @@ -335,3 +367,83 @@ export.write_assets_to_memory # OS => [num_assets, dest_ptr] # AS => [] end + +# HELPER PROCEDURES +# ================================================================================================= + +#! Writes the note inputs stored in the advice map to the memory specified by the provided +#! destination pointer. +#! +#! Inputs: +#! Operand stack: [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] +#! Advice map: { +#! NOTE_INPUTS_COMMITMENT: [[INPUT_VALUES]] +#! } +#! Outputs: +#! Operand stack: [num_inputs, dest_ptr] +proc.write_inputs_to_memory + # load the inputs from the advice map to the advice stack + adv.push_mapvaln + # OS => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [advice_num_inputs, [INPUT_VALUES]] + + # move the number of inputs obtained from advice map to the operand stack + adv_push.1 dup.5 + # OS => [num_inputs, advice_num_inputs, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + # Validate the note inputs length. Round up the number of inputs to the next multiple of 8: that + # value should be equal to the length obtained from the `adv.push_mapvaln` procedure. + u32divmod.8 neq.0 add mul.8 + # OS => [rounded_up_num_inputs, advice_num_inputs, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + assert_eq.err=ERR_NOTE_INVALID_NUMBER_OF_INPUTS + # OS => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + # calculate the number of words required to store the inputs + dup.4 u32divmod.4 neq.0 add + # OS => [num_words, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + # round up the number of words to the next multiple of 2 + dup is_odd add + # OS => [even_num_words, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + # prepare the stack for the `pipe_preimage_to_memory` procedure + dup.6 swap + # OS => [even_num_words, dest_ptr, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + # write the inputs from the advice stack into memory + exec.mem::pipe_preimage_to_memory drop + # OS => [num_inputs, dest_ptr] + # AS => [] +end + +#! Extracts the sender ID from the provided metadata word. +#! +#! Inputs: [METADATA] +#! Outputs: [sender_id_prefix, sender_id_suffix] +#! +#! Where: +#! - METADATA is the metadata of some note. +#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender ID of the note which +#! metadata was provided. +proc.extract_sender_from_metadata + # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_id_prefix] + + # drop aux felt and the felt containing tag, execution hint and payload + drop drop + # => [merged_sender_id_type_hint_tag, sender_id_prefix] + + # extract suffix of sender from merged layout, which means clearing the least significant byte + exec.account_id::shape_suffix + # => [sender_id_suffix, sender_id_prefix] + + # rearrange suffix and prefix + swap + # => [sender_id_prefix, sender_id_suffix] +end diff --git a/crates/miden-lib/asm/miden/tx.masm b/crates/miden-lib/asm/miden/tx.masm index 2948913dce..54439d7318 100644 --- a/crates/miden-lib/asm/miden/tx.masm +++ b/crates/miden-lib/asm/miden/tx.masm @@ -241,7 +241,7 @@ end #! #! Invocation: exec export.add_asset_to_note - movup.4 exec.kernel_proc_offsets::note_add_asset_offset + movup.4 exec.kernel_proc_offsets::output_note_add_asset_offset # => [offset, note_idx, ASSET] # pad the stack before the syscall to prevent accidental modification of the deeper stack diff --git a/crates/miden-lib/src/errors/tx_kernel_errors.rs b/crates/miden-lib/src/errors/tx_kernel_errors.rs index 3e07fa48f4..89a79a523f 100644 --- a/crates/miden-lib/src/errors/tx_kernel_errors.rs +++ b/crates/miden-lib/src/errors/tx_kernel_errors.rs @@ -143,12 +143,18 @@ pub const ERR_NON_FUNGIBLE_ASSET_FORMAT_MOST_SIGNIFICANT_BIT_MUST_BE_ZERO: MasmE /// Error Message: "failed to build the non-fungible asset because the provided faucet id is not from a non-fungible faucet" pub const ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID: MasmError = MasmError::from_static_str("failed to build the non-fungible asset because the provided faucet id is not from a non-fungible faucet"); -/// Error Message: "attempted to access note assets from incorrect context" -pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_FROM_INCORRECT_CONTEXT: MasmError = MasmError::from_static_str("attempted to access note assets from incorrect context"); -/// Error Message: "attempted to access note inputs from incorrect context" -pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_FROM_INCORRECT_CONTEXT: MasmError = MasmError::from_static_str("attempted to access note inputs from incorrect context"); -/// Error Message: "attempted to access note sender from incorrect context" -pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SENDER_FROM_INCORRECT_CONTEXT: MasmError = MasmError::from_static_str("attempted to access note sender from incorrect context"); +/// Error Message: "failed to access note assets of current note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note assets of current note because no note is currently being processed"); +/// Error Message: "failed to access note inputs of current note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note inputs of current note because no note is currently being processed"); +/// Error Message: "failed to access note metadata of current note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note metadata of current note because no note is currently being processed"); +/// Error Message: "failed to access note recipient of current note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_RECIPIENT_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note recipient of current note because no note is currently being processed"); +/// Error Message: "failed to access note script root of current note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SCRIPT_ROOT_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note script root of current note because no note is currently being processed"); +/// Error Message: "failed to access note serial number of current note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SERIAL_NUMBER_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note serial number of current note because no note is currently being processed"); /// Error Message: "note data does not match the commitment" pub const ERR_NOTE_DATA_DOES_NOT_MATCH_COMMITMENT: MasmError = MasmError::from_static_str("note data does not match the commitment"); /// Error Message: "adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807" @@ -158,7 +164,7 @@ pub const ERR_NOTE_INVALID_INDEX: MasmError = MasmError::from_static_str("failed /// Error Message: "invalid note type for the given note tag prefix" pub const ERR_NOTE_INVALID_NOTE_TYPE_FOR_NOTE_TAG_PREFIX: MasmError = MasmError::from_static_str("invalid note type for the given note tag prefix"); /// Error Message: "the specified number of note inputs does not match the actual number" -pub const ERR_NOTE_INVALID_NUMBER_OF_NOTE_INPUTS: MasmError = MasmError::from_static_str("the specified number of note inputs does not match the actual number"); +pub const ERR_NOTE_INVALID_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("the specified number of note inputs does not match the actual number"); /// Error Message: "invalid note type" pub const ERR_NOTE_INVALID_TYPE: MasmError = MasmError::from_static_str("invalid note type"); /// Error Message: "network execution mode with a specific target can only target network accounts" @@ -201,6 +207,8 @@ pub const ERR_PROLOGUE_NEW_NON_FUNGIBLE_FAUCET_RESERVED_SLOT_INVALID_TYPE: MasmE pub const ERR_PROLOGUE_NEW_NON_FUNGIBLE_FAUCET_RESERVED_SLOT_MUST_BE_VALID_EMPTY_SMT: MasmError = MasmError::from_static_str("reserved slot for non-fungible faucet is not a valid empty SMT"); /// Error Message: "failed to authenticate note inclusion in block" pub const ERR_PROLOGUE_NOTE_AUTHENTICATION_FAILED: MasmError = MasmError::from_static_str("failed to authenticate note inclusion in block"); +/// Error Message: "number of note inputs exceeded the maximum limit of 128" +pub const ERR_PROLOGUE_NOTE_INPUTS_LEN_EXCEEDED_LIMIT: MasmError = MasmError::from_static_str("number of note inputs exceeded the maximum limit of 128"); /// Error Message: "number of input notes exceeds the kernel's maximum limit of 1024" pub const ERR_PROLOGUE_NUMBER_OF_INPUT_NOTES_EXCEEDS_LIMIT: MasmError = MasmError::from_static_str("number of input notes exceeds the kernel's maximum limit of 1024"); /// Error Message: "number of note assets exceeds the maximum limit of 256" diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index 474f680ec5..a1d483ecc7 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -6,7 +6,7 @@ use miden_objects::{Word, word}; // ================================================================================================ /// Hashes of all dynamically executed kernel procedures. -pub const KERNEL_PROCEDURES: [Word; 50] = [ +pub const KERNEL_PROCEDURES: [Word; 48] = [ // account_get_initial_commitment word!("0x920898348bacd6d98a399301eb308478fd32b32eab019a5a6ef7a6b44abb61f6"), // account_compute_current_commitment @@ -59,30 +59,26 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ word!("0x7d32952d4dc0edd0311e3424b8128df2d48cf949f800c28218fbc851a8db42b5"), // faucet_is_non_fungible_asset_issued word!("0xc827d2430763c70880216804d3523c14e70e5cf926d5d8a72844c6476add5ef7"), - // note_get_assets_info - word!("0x13a1496a239fa95b3b376c53d899ce2d495e956cdae0f121666408462784673a"), - // note_add_asset - word!("0x47673b932aac8c186cb0979bbc3c4c2afa00fa1b80c0afb5e5efb4924bba48d9"), - // note_get_serial_number - word!("0x6989241a99d9aa1630daae03fc55ecac269e184d6b455c7c0bc996d15ef7f9a8"), - // note_get_inputs_commitment_and_len - word!("0x6ae9f25739a4368330c40e9bd21e5beed4583656443877f3f59cad5040decee1"), - // note_get_sender - word!("0x125832eac0e511f2d86c508b5fa79b7ea6a302c4200222c7f58e1832260bde8f"), - // note_get_script_root - word!("0x317c3f724d57093c98927f7820dc00f5cf3509d2de4306dccaef4e2266fcd5b6"), + // input_note_get_metadata + word!("0x7ad3e94585e7a397ee27443c98b376ed8d4ba762122af6413fde9314c00a6219"), // input_note_get_assets_info - word!("0x0816c7215676487f3ce03d372bf5512afb528ab62466074921829d7d2974147c"), + word!("0x159439fe48dbc11e674c5d05830d0408dcfa033c26e85e01256002c6cbc07e9a"), + // input_note_get_script_root + word!("0x527036257e58c3a84cf0aa170fb3f219a4553db17d269279355ad164a2b90ac5"), + // input_note_get_inputs_info + word!("0xdd8bbf4cdb48051da346bc89760b77fdf4c948904276a99d96409922a00bd322"), + // input_note_get_serial_number + word!("0x25815e02b7976d8e5c297dde60d372cc142c81f702f424ac0920190528c547ee"), + // input_note_get_recipient + word!("0xd3c255177f9243bb1a523a87615bbe76dd5a3605fcae87eb9d3a626d4ecce33c"), + // output_note_get_metadata + word!("0xde4a5b57f9d53692459383e6cf6302ef3602a348896ed6ab6fdf67e07fa483ff"), // output_note_get_assets_info word!("0x71d0e246d52c4d896e6508564207e049d4d68da187a143fe95bf5e7f5602f967"), - // input_note_get_recipient - word!("0x1e612cf8aa3cca674363d9e1fb15c666a0cf2febc80bf9d7800920941133a3f4"), // output_note_get_recipient word!("0xc824115ed79a2e1670daed8c18fba1bc15f54c5ec0ec6699de69a00b21d9df92"), - // input_note_get_metadata - word!("0x4b0c2a8560a007abadd55013c3b3c620b2de2189c08109500ba46a4222b37d89"), - // output_note_get_metadata - word!("0xde4a5b57f9d53692459383e6cf6302ef3602a348896ed6ab6fdf67e07fa483ff"), + // output_note_add_asset + word!("0x47673b932aac8c186cb0979bbc3c4c2afa00fa1b80c0afb5e5efb4924bba48d9"), // tx_create_note word!("0x52b37f8b25e26517f22f1f600acae7fbfffa84094595ba961af2af807a484736"), // tx_get_input_notes_commitment diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index a3c3b6dfc5..5447e03e46 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -53,7 +53,7 @@ async fn check_note_consumability_well_known_notes_success() -> anyhow::Result<( &mut RpoRandomCoin::new(Word::from([2u32; 4])), )?; - let notes = vec![p2ide_note, p2id_note]; + let notes = vec![p2id_note, p2ide_note]; let tx_context = TransactionContextBuilder::with_existing_mock_account() .extend_input_notes(notes.clone()) .build()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index cf23d83387..f515358291 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -94,7 +94,7 @@ fn test_get_asset_info() -> anyhow::Result<()> { } /// Check that recipient and metadata of a note with one asset obtained from the -/// `input_note::get_recipient` procedure is correct. +/// `input_note::get_recipient` and `input_note::get_metadata` procedures are correct. #[test] fn test_get_recipient_and_metadata() -> anyhow::Result<()> { let TestSetup { @@ -241,3 +241,139 @@ fn test_get_assets() -> anyhow::Result<()> { Ok(()) } + +/// Check that the number of the inputs and their commitment of a note with one asset +/// obtained from the `input_note::get_inputs_info` procedure is correct. +#[test] +fn test_get_inputs_info() -> anyhow::Result<()> { + let TestSetup { + mock_chain, + account, + p2id_note_0_assets: _, + p2id_note_1_asset, + p2id_note_2_assets: _, + } = setup_test()?; + + let code = format!( + r#" + use.miden::input_note + + begin + # get the inputs commitment and length from the input note with index 0 (the only one + # we have) + push.0 + exec.input_note::get_inputs_info + # => [NOTE_INPUTS_COMMITMENT, inputs_num] + + # assert the correctness of the inputs commitment + push.{INPUTS_COMMITMENT} + assert_eqw.err="note 0 has incorrect inputs commitment" + # => [inputs_num] + + # assert the inputs have correct length + push.{inputs_num} + assert_eq.err="note 0 has incorrect inputs length" + # => [] + end + "#, + INPUTS_COMMITMENT = p2id_note_1_asset.inputs().commitment(), + inputs_num = p2id_note_1_asset.inputs().num_values(), + ); + + let tx_script = ScriptBuilder::default().compile_tx_script(code)?; + + let tx_context = mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note_1_asset])? + .tx_script(tx_script) + .build()?; + + tx_context.execute_blocking()?; + + Ok(()) +} + +/// Check that the script root of a note with one asset obtained from the +/// `input_note::get_script_root` procedure is correct. +#[test] +fn test_get_script_root() -> anyhow::Result<()> { + let TestSetup { + mock_chain, + account, + p2id_note_0_assets: _, + p2id_note_1_asset, + p2id_note_2_assets: _, + } = setup_test()?; + + let code = format!( + r#" + use.miden::input_note + + begin + # get the script root from the input note with index 0 (the only one we have) + push.0 + exec.input_note::get_script_root + # => [SCRIPT_ROOT] + + # assert the correctness of the script root + push.{SCRIPT_ROOT} + assert_eqw.err="note 0 has incorrect script root" + # => [] + end + "#, + SCRIPT_ROOT = p2id_note_1_asset.script().root(), + ); + + let tx_script = ScriptBuilder::default().compile_tx_script(code)?; + + let tx_context = mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note_1_asset])? + .tx_script(tx_script) + .build()?; + + tx_context.execute_blocking()?; + + Ok(()) +} + +/// Check that the serial number of a note with one asset obtained from the +/// `input_note::get_serial_number` procedure is correct. +#[test] +fn test_get_serial_number() -> anyhow::Result<()> { + let TestSetup { + mock_chain, + account, + p2id_note_0_assets: _, + p2id_note_1_asset, + p2id_note_2_assets: _, + } = setup_test()?; + + let code = format!( + r#" + use.miden::input_note + + begin + # get the serial number from the input note with index 0 (the only one we have) + push.0 + exec.input_note::get_serial_number + # => [SERIAL_NUMBER] + + # assert the correctness of the serial number + push.{SERIAL_NUMBER} + assert_eqw.err="note 0 has incorrect serial number" + # => [] + end + "#, + SERIAL_NUMBER = p2id_note_1_asset.serial_num(), + ); + + let tx_script = ScriptBuilder::default().compile_tx_script(code)?; + + let tx_context = mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note_1_asset])? + .tx_script(tx_script) + .build()?; + + tx_context.execute_blocking()?; + + Ok(()) +} diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 4f940f34ed..ef3356a48a 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use anyhow::Context; use miden_lib::account::wallets::BasicWallet; use miden_lib::errors::MasmError; -use miden_lib::errors::tx_kernel_errors::ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SENDER_FROM_INCORRECT_CONTEXT; +use miden_lib::errors::tx_kernel_errors::ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED; use miden_lib::testing::mock_account::MockAccountExt; use miden_lib::testing::note::NoteBuilder; use miden_lib::transaction::TransactionKernel; @@ -48,33 +48,47 @@ use crate::{ TransactionContext, TransactionContextBuilder, TxContextInput, - assert_execution_error, assert_transaction_executor_error, }; #[test] -fn test_get_sender_no_sender() -> anyhow::Result<()> { - let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; +fn test_get_sender_fails_from_tx_script() -> anyhow::Result<()> { + // Creates a mockchain with an account and a note + let mut builder = MockChain::builder(); + let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let p2id_note = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(150)], + NoteType::Public, + )?; + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + // calling get_sender should return sender let code = " - use.$kernel::memory - use.$kernel::prologue use.miden::note begin - exec.prologue::prepare_transaction - - # force the current input note pointer to 0 - push.0 exec.memory::set_current_input_note_ptr - - # get the sender + # try to get the sender from transaction script exec.note::get_sender end "; + let tx_script = ScriptBuilder::default() + .compile_tx_script(code) + .context("failed to compile tx script")?; + + let tx_context = mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[p2id_note.id()], &[])? + .tx_script(tx_script) + .build()?; - let process = tx_context.execute_code(code); + let result = tx_context.execute_blocking(); + assert_transaction_executor_error!( + result, + ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED + ); - assert_execution_error!(process, ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SENDER_FROM_INCORRECT_CONTEXT); Ok(()) } @@ -115,79 +129,6 @@ fn test_get_sender() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_get_vault_data() -> anyhow::Result<()> { - let tx_context = { - let mut builder = MockChain::builder(); - let account = builder.add_existing_wallet(crate::Auth::BasicAuth)?; - let p2id_note_1 = builder.add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(150)], - NoteType::Public, - )?; - let p2id_note_2 = builder.add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(300)], - NoteType::Public, - )?; - let mut mock_chain = builder.build()?; - mock_chain.prove_next_block()?; - - mock_chain - .build_tx_context( - TxContextInput::AccountId(account.id()), - &[], - &[p2id_note_1, p2id_note_2], - )? - .build()? - }; - - let notes = tx_context.input_notes(); - - // calling get_assets_info should return assets info - let code = format!( - " - use.std::sys - - use.$kernel::prologue - use.$kernel::note - - begin - exec.prologue::prepare_transaction - - # get the assets info about note 0 - exec.note::get_assets_info - - # assert the assets data is correct - push.{note_0_asset_commitment} assert_eqw - push.{note_0_num_assets} assert_eq - - # increment current input note pointer - exec.note::increment_current_input_note_ptr - - # get the assets info about note 1 - exec.note::get_assets_info - - # assert the assets data is correct - push.{note_1_asset_commitment} assert_eqw - push.{note_1_num_assets} assert_eq - - # truncate the stack - exec.sys::truncate_stack - end - ", - note_0_asset_commitment = notes.get_note(0).note().assets().commitment(), - note_0_num_assets = notes.get_note(0).note().assets().num_assets(), - note_1_asset_commitment = notes.get_note(1).note().assets().commitment(), - note_1_num_assets = notes.get_note(1).note().assets().num_assets(), - ); - - tx_context.execute_code(&code)?; - Ok(()) -} - #[test] fn test_get_assets() -> anyhow::Result<()> { // Creates a mockchain with an account and a note that it can consume @@ -345,18 +286,23 @@ fn test_get_inputs() -> anyhow::Result<()> { .build()? }; - fn construct_input_assertions(note: &Note) -> String { + fn construct_inputs_assertions(note: &Note) -> String { let mut code = String::new(); - for input_chunk in note.inputs().values().chunks(WORD_SIZE) { - let mut input_word = EMPTY_WORD; - input_word.as_mut_slice()[..input_chunk.len()].copy_from_slice(input_chunk); + for inputs_chunk in note.inputs().values().chunks(WORD_SIZE) { + let mut inputs_word = EMPTY_WORD; + inputs_word.as_mut_slice()[..inputs_chunk.len()].copy_from_slice(inputs_chunk); code += &format!( - " - # assert the input is correct - dup padw movup.4 mem_loadw push.{input_word} assert_eqw push.4 add - ", - input_word = input_word + r#" + # assert the inputs are correct + # => [dest_ptr] + dup padw movup.4 mem_loadw push.{inputs_word} assert_eqw.err="inputs are incorrect" + # => [dest_ptr] + + push.4 add + # => [dest_ptr+4] + "#, + inputs_word = inputs_word ); } code @@ -378,7 +324,7 @@ fn test_get_inputs() -> anyhow::Result<()> { exec.note_internal::prepare_note # => [note_script_root_ptr, NOTE_ARGS, pad(11)] - # drop the note inputs + # clean the stack dropw dropw dropw dropw # => [] @@ -391,17 +337,17 @@ fn test_get_inputs() -> anyhow::Result<()> { dup eq.{NOTE_0_PTR} assert # => [dest_ptr] - # apply note 1 input assertions - {input_assertions} + # apply note 1 inputs assertions + {inputs_assertions} # => [dest_ptr] - # clean the pointer + # clear the stack drop # => [] end ", num_inputs = note0.inputs().num_values(), - input_assertions = construct_input_assertions(note0), + inputs_assertions = construct_inputs_assertions(note0), NOTE_0_PTR = 100000000, ); @@ -409,12 +355,12 @@ fn test_get_inputs() -> anyhow::Result<()> { Ok(()) } -/// This test checks the scenario when an input note has exactly 8 input values, and the transaction +/// This test checks the scenario when an input note has exactly 8 inputs, and the transaction /// script attempts to load the inputs to memory using the `miden::note::get_inputs` procedure. /// -/// Previously this setup was leading to the incorrect number of note input values computed during -/// the `get_inputs` procedure, see the -/// [issue #1363](https://github.com/0xMiden/miden-base/issues/1363) for more details. +/// Previously this setup was leading to the incorrect number of note inputs computed during the +/// `get_inputs` procedure, see the [issue #1363](https://github.com/0xMiden/miden-base/issues/1363) +/// for more details. #[test] fn test_get_exactly_8_inputs() -> anyhow::Result<()> { let sender_id = ACCOUNT_ID_SENDER @@ -471,12 +417,12 @@ fn test_get_exactly_8_inputs() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - # execute the `get_inputs` procedure to trigger note inputs number assertion + # execute the `get_inputs` procedure to trigger note inputs length assertion push.0 exec.note::get_inputs # => [num_inputs, 0] - # assert that the number if inputs is 8 - push.8 assert_eq.err=\"number of inputs should be equal to 8\" + # assert that the inputs length is 8 + push.8 assert_eq.err=\"number of inputs values should be equal to 8\" # clean the stack drop diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index 4f0c82cf5a..359688e581 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -57,11 +57,12 @@ Note procedures can be used to fetch data from the note that is currently being | Procedure | Description | Context | | --- | --- | --- | | `get_assets` | Writes the assets of the currently executing note into memory starting at the specified address.

Inputs: `[dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Note | -| `get_inputs` | Loads the note's inputs to the specified memory address.

Inputs: `[dest_ptr]`
Outputs: `[num_inputs, dest_ptr]` | Note | +| `get_recipient` | Returns the recipient of the note currently being processed.

Inputs: `[]`
Outputs: `[RECIPIENT]` | Note | +| `get_inputs` | Writes the note's inputs to the specified memory address.

Inputs: `[dest_ptr]`
Outputs: `[num_inputs, dest_ptr]` | Note | | `get_sender` | Returns the sender of the note currently being processed.

Inputs: `[]`
Outputs: `[sender_id_prefix, sender_id_suffix]` | Note | | `get_serial_number` | Returns the serial number of the note currently being processed.

Inputs: `[]`
Outputs: `[SERIAL_NUMBER]` | Note | | `get_script_root` | Returns the script root of the note currently being processed.

Inputs: `[]`
Outputs: `[SCRIPT_ROOT]` | Note | -| `compute_inputs_commitment` | Computes the commitment to the note inputs starting at the specified memory address.

Inputs: `[inputs_ptr, num_inputs]`
Outputs: `[COMMITMENT]` | Any | +| `compute_inputs_commitment` | Computes the commitment to the output note inputs starting at the specified memory address.

Inputs: `[inputs_ptr, num_inputs]`
Outputs: `[INPUTS_COMMITMENT]` | Any | | `add_assets_to_account` | Adds all assets from the currently executing note to the account vault.

Inputs: `[]`
Outputs: `[]` | Note | ## Input Note Procedures (`miden::input_note`) @@ -70,10 +71,13 @@ Input note procedures can be used to fetch data on input notes consumed by the t | Procedure | Description | Context | | --- | --- | --- | -| `get_assets_info` | Returns the information about assets in the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[ASSETS_COMMITMENT, num_assets]` | Any | -| `get_assets` | Writes the assets of the input note with the specified index into memory starting at the specified address.

Inputs: `[dest_ptr, note_index]`
Outputs: `[num_assets, dest_ptr, note_index]` | Any | +| `get_assets_info` | Returns the information about [assets](note.md#assets) in the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[ASSETS_COMMITMENT, num_assets]` | Any | +| `get_assets` | Writes the [assets](note.md#assets) of the input note with the specified index into memory starting at the specified address.

Inputs: `[dest_ptr, note_index]`
Outputs: `[num_assets, dest_ptr, note_index]` | Any | | `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[RECIPIENT]` | Any | | `get_metadata` | Returns the [metadata](note.md#metadata) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[METADATA]` | Any | +| `get_inputs_info` | Returns the [inputs](note.md#inputs) commitment and length of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[NOTE_INPUTS_COMMITMENT, num_inputs]` | Any | +| `get_script_root` | Returns the [script root](note.md#script) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[SCRIPT_ROOT]` | Any | +| `get_serial_number` | Returns the [serial number](note.md#serial-number) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[SERIAL_NUMBER]` | Any | ## Output Note Procedures (`miden::output_note`) From c8ed513dcc142d64c10b25742e93a16314637e5a Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Mon, 8 Sep 2025 22:02:19 -0700 Subject: [PATCH 025/133] chore: use workspace version for crate versions --- Cargo.toml | 1 + crates/miden-block-prover/Cargo.toml | 2 +- crates/miden-lib/Cargo.toml | 2 +- crates/miden-objects/Cargo.toml | 2 +- crates/miden-testing/Cargo.toml | 2 +- crates/miden-tx-batch-prover/Cargo.toml | 2 +- crates/miden-tx/Cargo.toml | 2 +- 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c519b6e23..f841afd8a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ homepage = "https://miden.xyz" license = "MIT" repository = "https://github.com/0xMiden/miden-base" rust-version = "1.89" +version = "0.12.0" [profile.release] codegen-units = 1 diff --git a/crates/miden-block-prover/Cargo.toml b/crates/miden-block-prover/Cargo.toml index cc3ff8d396..d774870d03 100644 --- a/crates/miden-block-prover/Cargo.toml +++ b/crates/miden-block-prover/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-block-prover" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.12.0" +version.workspace = true [lib] bench = false diff --git a/crates/miden-lib/Cargo.toml b/crates/miden-lib/Cargo.toml index 24e2dfe608..a7aefcfcdf 100644 --- a/crates/miden-lib/Cargo.toml +++ b/crates/miden-lib/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-lib" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.12.0" +version.workspace = true [lib] diff --git a/crates/miden-objects/Cargo.toml b/crates/miden-objects/Cargo.toml index b95a1c8795..c6e0e24c3e 100644 --- a/crates/miden-objects/Cargo.toml +++ b/crates/miden-objects/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-objects" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.12.0" +version.workspace = true [[bench]] harness = false diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index 065885ef0f..647776a5cc 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-testing" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.12.0" +version.workspace = true [features] std = ["miden-lib/std"] diff --git a/crates/miden-tx-batch-prover/Cargo.toml b/crates/miden-tx-batch-prover/Cargo.toml index 921d73533e..6b214a120b 100644 --- a/crates/miden-tx-batch-prover/Cargo.toml +++ b/crates/miden-tx-batch-prover/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-tx-batch-prover" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.12.0" +version.workspace = true [lib] bench = false diff --git a/crates/miden-tx/Cargo.toml b/crates/miden-tx/Cargo.toml index 460d69b844..86c3c5006b 100644 --- a/crates/miden-tx/Cargo.toml +++ b/crates/miden-tx/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-tx" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.12.0" +version.workspace = true [features] concurrent = ["miden-prover/concurrent", "std"] From 7e6668e372cecba798ca7bee71f57e2954c49698 Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Tue, 9 Sep 2025 08:27:40 +0300 Subject: [PATCH 026/133] feat: add `AccountComponent::from_package()` method (#1802) --- CHANGELOG.md | 15 +- Cargo.lock | 1 + Cargo.toml | 17 +- crates/miden-objects/Cargo.toml | 15 +- .../src/account/component/mod.rs | 173 ++++++++++++++++++ crates/miden-objects/src/lib.rs | 1 + 6 files changed, 200 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 909e5858fb..70161395a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,21 +6,22 @@ - Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). - Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). -- Enable lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). +- Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). - Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). +- Enable lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). - Lazy load the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). -- Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). ### Changes - [BREAKING] Incremented MSRV to 1.89. -- [BREAKING] Remove some of the `note` kernel procedures and use `input_note` procedures instead ([#1834](https://github.com/0xMiden/miden-base/pull/1834)). -- [BREAKING] Remove versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). -- [BREAKING] Move `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). +- [BREAKING] Removed versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). +- Added `AccountComponent::from_package()` method to create components from `miden-mast-package::Package` ([#1802](https://github.com/0xMiden/miden-base/pull/1802)). +- [BREAKING] Removed some of the `note` kernel procedures and use `input_note` procedures instead ([#1834](https://github.com/0xMiden/miden-base/pull/1834)). - [BREAKING] Replaced `Account` with `PartialAccount` in `TransactionInputs` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). - [BREAKING] Renamed `Account::init_commitment` to `Account::initial_commitment` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). -- [BREAKING] Rename the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). -- [BREAKING] Move `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). +- [BREAKING] Renamed the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). +- [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). +- [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). ## 0.11.2 (2025-09-08) diff --git a/Cargo.lock b/Cargo.lock index 947b11688c..afe304ec51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1275,6 +1275,7 @@ dependencies = [ "miden-assembly", "miden-core", "miden-crypto", + "miden-mast-package", "miden-objects", "miden-processor", "miden-utils-sync", diff --git a/Cargo.toml b/Cargo.toml index f841afd8a1..c6ed189a96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,14 +49,15 @@ miden-tx = { default-features = false, path = "crates/miden-tx", ve miden-tx-batch-prover = { default-features = false, path = "crates/miden-tx-batch-prover", version = "0.12" } # Miden dependencies -miden-assembly = { default-features = false, version = "0.17" } -miden-core = { default-features = false, version = "0.17" } -miden-crypto = { default-features = false, version = "0.15.6" } -miden-processor = { default-features = false, version = "0.17" } -miden-prover = { default-features = false, version = "0.17" } -miden-stdlib = { default-features = false, version = "0.17" } -miden-utils-sync = { default-features = false, version = "0.17" } -miden-verifier = { default-features = false, version = "0.17" } +miden-assembly = { default-features = false, version = "0.17" } +miden-core = { default-features = false, version = "0.17" } +miden-crypto = { default-features = false, version = "0.15.6" } +miden-mast-package = { default-features = false, version = "0.17" } +miden-processor = { default-features = false, version = "0.17" } +miden-prover = { default-features = false, version = "0.17" } +miden-stdlib = { default-features = false, version = "0.17" } +miden-utils-sync = { default-features = false, version = "0.17" } +miden-verifier = { default-features = false, version = "0.17" } # External dependencies anyhow = { default-features = false, version = "1.0" } diff --git a/crates/miden-objects/Cargo.toml b/crates/miden-objects/Cargo.toml index c6e0e24c3e..1a35b97bc5 100644 --- a/crates/miden-objects/Cargo.toml +++ b/crates/miden-objects/Cargo.toml @@ -34,13 +34,14 @@ testing = ["dep:rand", "dep:rand_xoshiro", "dep:winter-rand-utils"] [dependencies] # Miden dependencies -miden-assembly = { workspace = true } -miden-core = { workspace = true } -miden-crypto = { workspace = true } -miden-processor = { workspace = true } -miden-utils-sync = { workspace = true } -miden-verifier = { workspace = true } -winter-rand-utils = { optional = true, version = "0.13" } +miden-assembly = { workspace = true } +miden-core = { workspace = true } +miden-crypto = { workspace = true } +miden-mast-package = { workspace = true } +miden-processor = { workspace = true } +miden-utils-sync = { workspace = true } +miden-verifier = { workspace = true } +winter-rand-utils = { optional = true, version = "0.13" } # External dependencies bech32 = { default-features = false, features = ["alloc"], version = "0.11" } diff --git a/crates/miden-objects/src/account/component/mod.rs b/crates/miden-objects/src/account/component/mod.rs index dd6fb4efcc..a982eb5cc4 100644 --- a/crates/miden-objects/src/account/component/mod.rs +++ b/crates/miden-objects/src/account/component/mod.rs @@ -3,6 +3,8 @@ use alloc::vec::Vec; use miden_assembly::ast::QualifiedProcedureName; use miden_assembly::{Assembler, Library, Parse}; +use miden_core::utils::Deserializable; +use miden_mast_package::Package; use miden_processor::MastForest; mod template; @@ -11,6 +13,35 @@ pub use template::*; use crate::account::{AccountType, StorageSlot}; use crate::{AccountError, Word}; +// IMPLEMENTATIONS +// ================================================================================================ + +impl TryFrom for AccountComponentTemplate { + type Error = AccountError; + + fn try_from(package: Package) -> Result { + let library = package.unwrap_library().as_ref().clone(); + + // Extract metadata - require explicit account component metadata + let metadata = match package.account_component_metadata_bytes.as_deref() { + Some(metadata_bytes) => AccountComponentMetadata::read_from_bytes(metadata_bytes) + .map_err(|err| { + AccountError::other_with_source( + "failed to deserialize account component metadata", + err, + ) + })?, + None => { + return Err(AccountError::other( + "package does not contain account component metadata - packages without explicit metadata may be intended for other purposes (e.g., note scripts, transaction scripts)", + )); + }, + }; + + Ok(AccountComponentTemplate::new(metadata, library)) + } +} + /// An [`AccountComponent`] defines a [`Library`] of code and the initial value and types of /// the [`StorageSlot`]s it accesses. /// @@ -112,6 +143,31 @@ impl AccountComponent { .with_supported_types(template.metadata().supported_types().clone())) } + /// Creates an [`AccountComponent`] from a [`Package`] using [`InitStorageData`]. + /// + /// This method provides type safety by leveraging the component's metadata to validate + /// storage initialization data. The package must contain explicit account component metadata. + /// + /// # Arguments + /// + /// * `package` - The package containing the library and account component metadata + /// * `init_storage_data` - The initialization data for storage slots + /// + /// # Errors + /// + /// Returns an error if: + /// - The package does not contain account component metadata + /// - The package cannot be converted to an [`AccountComponentTemplate`] + /// - The storage initialization fails due to invalid or missing data + /// - The component creation fails + pub fn from_package_with_init_data( + package: &Package, + init_storage_data: &InitStorageData, + ) -> Result { + let template = AccountComponentTemplate::try_from(package.clone())?; + Self::from_template(&template, init_storage_data) + } + // ACCESSORS // -------------------------------------------------------------------------------------------- @@ -205,3 +261,120 @@ impl From for Library { component.library } } + +#[cfg(test)] +mod tests { + use alloc::collections::BTreeSet; + use alloc::string::ToString; + use alloc::sync::Arc; + + use miden_assembly::Assembler; + use miden_core::utils::Serializable; + use miden_mast_package::{MastArtifact, Package, PackageManifest}; + use semver::Version; + + use super::*; + use crate::testing::account_code::CODE; + + #[test] + fn test_try_from_package_for_template() { + // Create a simple library for testing + let library = Assembler::default().assemble_library([CODE]).unwrap(); + + // Test with metadata + let metadata = AccountComponentMetadata::new( + "test_component".to_string(), + "A test component".to_string(), + Version::new(1, 0, 0), + BTreeSet::from_iter([AccountType::RegularAccountImmutableCode]), + vec![], + ) + .unwrap(); + + let metadata_bytes = metadata.to_bytes(); + let package_with_metadata = Package { + name: "test_package".to_string(), + mast: MastArtifact::Library(Arc::new(library.clone())), + manifest: PackageManifest::new(None), + account_component_metadata_bytes: Some(metadata_bytes), + }; + + let template = AccountComponentTemplate::try_from(package_with_metadata).unwrap(); + assert_eq!(template.metadata().name(), "test_component"); + assert!( + template + .metadata() + .supported_types() + .contains(&AccountType::RegularAccountImmutableCode) + ); + + // Test without metadata - should fail + let package_without_metadata = Package { + name: "test_package_no_metadata".to_string(), + mast: MastArtifact::Library(Arc::new(library)), + manifest: PackageManifest::new(None), + account_component_metadata_bytes: None, + }; + + let result = AccountComponentTemplate::try_from(package_without_metadata); + assert!(result.is_err()); + let error_msg = result.unwrap_err().to_string(); + assert!(error_msg.contains("package does not contain account component metadata")); + } + + #[test] + fn test_from_package_with_init_data() { + // Create a simple library for testing + let library = Assembler::default().assemble_library([CODE]).unwrap(); + + // Create metadata for the component + let metadata = AccountComponentMetadata::new( + "test_component".to_string(), + "A test component".to_string(), + Version::new(1, 0, 0), + BTreeSet::from_iter([ + AccountType::RegularAccountImmutableCode, + AccountType::RegularAccountUpdatableCode, + ]), + vec![], + ) + .unwrap(); + + // Serialize the metadata + let metadata_bytes = metadata.to_bytes(); + + // Create a package with metadata + let package = Package { + name: "test_package_init_data".to_string(), + mast: MastArtifact::Library(Arc::new(library.clone())), + manifest: PackageManifest::new(None), + account_component_metadata_bytes: Some(metadata_bytes), + }; + + // Test with empty init data - this tests the complete workflow: + // Package -> AccountComponentTemplate -> AccountComponent + let init_data = InitStorageData::default(); + let component = + AccountComponent::from_package_with_init_data(&package, &init_data).unwrap(); + + // Verify the component was created correctly + assert_eq!(component.storage_size(), 0); + assert!(component.supports_type(AccountType::RegularAccountImmutableCode)); + assert!(component.supports_type(AccountType::RegularAccountUpdatableCode)); + assert!(!component.supports_type(AccountType::FungibleFaucet)); + + // Test without metadata - should fail + let package_without_metadata = Package { + name: "test_package_no_metadata".to_string(), + mast: MastArtifact::Library(Arc::new(library)), + manifest: PackageManifest::new(None), + account_component_metadata_bytes: None, + }; + + let result = + AccountComponent::from_package_with_init_data(&package_without_metadata, &init_data); + assert!(result.is_err()); + let error_msg = result.unwrap_err().to_string(); + assert!(error_msg.contains("package does not contain account component metadata")); + } +} diff --git a/crates/miden-objects/src/lib.rs b/crates/miden-objects/src/lib.rs index 3602518434..4676db41ef 100644 --- a/crates/miden-objects/src/lib.rs +++ b/crates/miden-objects/src/lib.rs @@ -100,6 +100,7 @@ pub mod utils { pub mod vm { pub use miden_core::sys_events::SystemEvent; pub use miden_core::{AdviceMap, Program, ProgramInfo}; + pub use miden_mast_package::Package; pub use miden_processor::{AdviceInputs, FutureMaybeSend, RowIndex, StackInputs, StackOutputs}; pub use miden_verifier::ExecutionProof; } From 3b00b37a23c14dca712e71b064880e468dde9d91 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 9 Sep 2025 09:19:26 +0200 Subject: [PATCH 027/133] chore: apply changes from 1866 to next (#1869) --- crates/miden-tx/src/prover/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index ed355d34f4..7d5aae4bd3 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -127,6 +127,9 @@ impl LocalTransactionProver { .map_err(TransactionProverError::ConflictingAdviceMapEntry)?; self.mast_store.load_account_code(tx_inputs.account().code()); + for account_inputs in tx_args.foreign_account_inputs() { + self.mast_store.load_account_code(account_inputs.code()); + } let script_mast_store = ScriptMastForestStore::new( tx_args.tx_script(), From b39ec470b2f0e34eb745f6395a90f4c66de88f85 Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Tue, 9 Sep 2025 14:58:09 +0530 Subject: [PATCH 028/133] feat: remove ProvenTransactionExt (#1867) * feat: rm ProvenTransactionExt * fix: add changelog * Update CHANGELOG.md --------- Co-authored-by: Philipp Gackstatter --- CHANGELOG.md | 3 +++ .../kernel_tests/block/proposed_block_errors.rs | 8 ++++---- .../kernel_tests/block/proposed_block_success.rs | 11 ++++++----- .../src/kernel_tests/block/proven_block_error.rs | 7 ++++--- .../miden-testing/src/kernel_tests/block/utils.rs | 8 ++++---- crates/miden-testing/src/lib.rs | 1 - crates/miden-testing/src/mock_chain/mod.rs | 2 -- .../miden-testing/src/mock_chain/proven_tx_ext.rs | 15 --------------- 8 files changed, 21 insertions(+), 34 deletions(-) delete mode 100644 crates/miden-testing/src/mock_chain/proven_tx_ext.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 70161395a1..7f52a060c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ - [BREAKING] Removed some of the `note` kernel procedures and use `input_note` procedures instead ([#1834](https://github.com/0xMiden/miden-base/pull/1834)). - [BREAKING] Replaced `Account` with `PartialAccount` in `TransactionInputs` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). - [BREAKING] Renamed `Account::init_commitment` to `Account::initial_commitment` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). +- [BREAKING] Rename the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). +- [BREAKING] Move `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). +- Remove `ProvenTransactionExt`([#1867](https://github.com/0xMiden/miden-base/pull/1867)). - [BREAKING] Renamed the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). - [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs index 1a5c1df88d..98ac48c8e6 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs @@ -8,9 +8,10 @@ use miden_objects::block::{BlockInputs, BlockNumber, ProposedBlock}; use miden_objects::crypto::merkle::SparseMerklePath; use miden_objects::note::NoteInclusionProof; use miden_objects::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET; -use miden_objects::transaction::{OutputNote, ProvenTransaction}; +use miden_objects::transaction::OutputNote; use miden_objects::{MAX_BATCHES_PER_BLOCK, ProposedBlockError}; use miden_processor::crypto::MerklePath; +use miden_tx::LocalTransactionProver; use super::utils::{ TestSetup, @@ -26,7 +27,6 @@ use super::utils::{ generate_untracked_note, setup_chain, }; -use crate::ProvenTransactionExt; use crate::utils::create_spawn_note; /// Tests that too many batches produce an error. @@ -607,8 +607,8 @@ fn proposed_block_fails_on_inconsistent_account_state_transition() -> anyhow::Re // We will only include tx0 and tx2 and leave out tx1, which will trigger the error condition // that there is no transition from tx0 -> tx2. - let tx0 = ProvenTransaction::from_executed_transaction_mocked(executed_tx0.clone()); - let tx2 = ProvenTransaction::from_executed_transaction_mocked(executed_tx2.clone()); + let tx0 = LocalTransactionProver::default().prove_dummy(executed_tx0.clone())?; + let tx2 = LocalTransactionProver::default().prove_dummy(executed_tx2.clone())?; let batch0 = generate_batch(&mut chain, vec![tx0]); let batch1 = generate_batch(&mut chain, vec![tx2]); diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs index 6eafa032ae..ef01f1a293 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs @@ -9,7 +9,8 @@ use miden_objects::account::delta::AccountUpdateDetails; use miden_objects::account::{Account, AccountId, AccountStorageMode}; use miden_objects::block::{BlockInputs, ProposedBlock}; use miden_objects::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET; -use miden_objects::transaction::{OutputNote, ProvenTransaction, TransactionHeader}; +use miden_objects::transaction::{OutputNote, TransactionHeader}; +use miden_tx::LocalTransactionProver; use rand::Rng; use super::utils::{ @@ -24,7 +25,7 @@ use super::utils::{ setup_chain, }; use crate::kernel_tests::block::utils::generate_conditional_tx; -use crate::{AccountState, Auth, MockChain, ProvenTransactionExt}; +use crate::{AccountState, Auth, MockChain}; /// Tests that we can build empty blocks. #[test] @@ -142,7 +143,7 @@ fn proposed_block_aggregates_account_state_transition() -> anyhow::Result<()> { let [tx0, tx1, tx2] = [executed_tx0, executed_tx1, executed_tx2] .into_iter() - .map(ProvenTransaction::from_executed_transaction_mocked) + .map(|tx| LocalTransactionProver::default().prove_dummy(tx).unwrap()) .collect::>() .try_into() .expect("we should have provided three executed txs"); @@ -293,8 +294,8 @@ fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow: state_updating_tx.final_account().commitment() ); - let tx0 = ProvenTransaction::from_executed_transaction_mocked(noop_tx); - let tx1 = ProvenTransaction::from_executed_transaction_mocked(state_updating_tx); + let tx0 = LocalTransactionProver::default().prove_dummy(noop_tx)?; + let tx1 = LocalTransactionProver::default().prove_dummy(state_updating_tx)?; let batch0 = generate_batch(&mut chain, vec![tx0]); let batch1 = generate_batch(&mut chain, vec![tx1.clone()]); diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs index 88b8a1352c..723bf6b330 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs @@ -10,9 +10,10 @@ use miden_objects::account::{Account, AccountBuilder, AccountComponent, AccountI use miden_objects::asset::FungibleAsset; use miden_objects::batch::ProvenBatch; use miden_objects::block::{BlockInputs, BlockNumber, ProposedBlock}; -use miden_objects::transaction::{ProvenTransaction, ProvenTransactionBuilder}; +use miden_objects::transaction::ProvenTransactionBuilder; use miden_objects::vm::ExecutionProof; use miden_objects::{AccountTreeError, NullifierTreeError, Word}; +use miden_tx::LocalTransactionProver; use winterfell::Proof; use super::utils::{ @@ -22,7 +23,7 @@ use super::utils::{ generate_tracked_note, setup_chain, }; -use crate::{Auth, MockChain, ProvenTransactionExt, TransactionContextBuilder}; +use crate::{Auth, MockChain, TransactionContextBuilder}; struct WitnessTestSetup { stale_block_inputs: BlockInputs, @@ -293,7 +294,7 @@ fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> a .tx_inputs(tx_inputs) .build()?; let tx = tx_context.execute_blocking().context("failed to execute account creating tx")?; - let tx = ProvenTransaction::from_executed_transaction_mocked(tx); + let tx = LocalTransactionProver::default().prove_dummy(tx)?; let batch = generate_batch(&mut mock_chain, vec![tx]); let batches = [batch]; diff --git a/crates/miden-testing/src/kernel_tests/block/utils.rs b/crates/miden-testing/src/kernel_tests/block/utils.rs index 115f353160..cd1662a412 100644 --- a/crates/miden-testing/src/kernel_tests/block/utils.rs +++ b/crates/miden-testing/src/kernel_tests/block/utils.rs @@ -19,10 +19,10 @@ use miden_objects::transaction::{ TransactionScript, }; use miden_objects::{Felt, ONE, Word, ZERO}; +use miden_tx::LocalTransactionProver; use rand::rngs::SmallRng; use rand::{Rng, SeedableRng}; -use crate::mock_chain::ProvenTransactionExt; use crate::{Auth, MockChain, TxContextInput}; pub struct TestSetup { @@ -105,7 +105,7 @@ pub fn generate_tx_with_authenticated_notes( notes: &[NoteId], ) -> ProvenTransaction { let executed_tx = generate_executed_tx_with_authenticated_notes(chain, account_id, notes); - ProvenTransaction::from_executed_transaction_mocked(executed_tx) + LocalTransactionProver::default().prove_dummy(executed_tx).unwrap() } /// Generates a transaction, which depending on the `modify_storage` flag, does the following: @@ -158,7 +158,7 @@ pub fn generate_tx_with_expiration( .build() .unwrap(); let executed_tx = tx_context.execute_blocking().unwrap(); - ProvenTransaction::from_executed_transaction_mocked(executed_tx) + LocalTransactionProver::default().prove_dummy(executed_tx).unwrap() } pub fn generate_tx_with_unauthenticated_notes( @@ -172,7 +172,7 @@ pub fn generate_tx_with_unauthenticated_notes( .build() .unwrap(); let executed_tx = tx_context.execute_blocking().unwrap(); - ProvenTransaction::from_executed_transaction_mocked(executed_tx) + LocalTransactionProver::default().prove_dummy(executed_tx).unwrap() } fn update_expiration_tx_script(expiration_delta: u16) -> TransactionScript { diff --git a/crates/miden-testing/src/lib.rs b/crates/miden-testing/src/lib.rs index 914e374235..1675950099 100644 --- a/crates/miden-testing/src/lib.rs +++ b/crates/miden-testing/src/lib.rs @@ -13,7 +13,6 @@ pub use mock_chain::{ MockChain, MockChainBuilder, MockChainNote, - ProvenTransactionExt, TxContextInput, }; diff --git a/crates/miden-testing/src/mock_chain/mod.rs b/crates/miden-testing/src/mock_chain/mod.rs index 335ccf1eb8..263c572975 100644 --- a/crates/miden-testing/src/mock_chain/mod.rs +++ b/crates/miden-testing/src/mock_chain/mod.rs @@ -2,10 +2,8 @@ mod auth; mod chain; mod chain_builder; mod note; -mod proven_tx_ext; pub use auth::Auth; pub use chain::{AccountState, MockChain, TxContextInput}; pub use chain_builder::MockChainBuilder; pub use note::MockChainNote; -pub use proven_tx_ext::ProvenTransactionExt; diff --git a/crates/miden-testing/src/mock_chain/proven_tx_ext.rs b/crates/miden-testing/src/mock_chain/proven_tx_ext.rs deleted file mode 100644 index c0517014a0..0000000000 --- a/crates/miden-testing/src/mock_chain/proven_tx_ext.rs +++ /dev/null @@ -1,15 +0,0 @@ -use miden_objects::transaction::{ExecutedTransaction, ProvenTransaction}; -use miden_tx::LocalTransactionProver; - -/// Extension trait to convert an [`ExecutedTransaction`] into a [`ProvenTransaction`] with a dummy -/// proof for testing purposes. -pub trait ProvenTransactionExt { - /// Converts the transaction into a proven transaction with a dummy proof. - fn from_executed_transaction_mocked(executed_tx: ExecutedTransaction) -> ProvenTransaction; -} - -impl ProvenTransactionExt for ProvenTransaction { - fn from_executed_transaction_mocked(executed_tx: ExecutedTransaction) -> ProvenTransaction { - LocalTransactionProver::default().prove_dummy(executed_tx).unwrap() - } -} From 074d91390d0d4e16a319805736caad27c1a2852f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:48:25 +0900 Subject: [PATCH 029/133] Adds tests for new faucet (#1757) * Adds tests for new faucet Closes #912 * chore: clippy * fix: review comments - Delete redundant create_basic_fungible_faucet_works, move bits to faucet_contract_creation, - remove prove_and_verify_transaction in creation test, - use create_new_faucet throughout, * refactor new faucet tests to add creation + Minting) * fix: create_new_faucet should use AccountState::New instead of AccountState::Exists This ensures that new faucets are properly created as new accounts rather than existing ones. * fix: execute_mint_transaction should pass full Account instead of AccountID This allows the function to work with new accounts that haven't been registered in the chain state yet. + renamed tests for clarity + Deleted prove_new_faucet_execution_burn_fungible_asset_succeeds as it was testing the same scenario as test_burn_fungible_asset_insufficient_input_amount * fix: test names --------- Co-authored-by: Philipp Gackstatter --- crates/miden-lib/src/account/faucets/mod.rs | 8 + .../src/kernel_tests/tx/test_account_delta.rs | 61 +++---- .../src/kernel_tests/tx/test_asset.rs | 5 +- .../src/kernel_tests/tx/test_input_note.rs | 7 +- .../src/kernel_tests/tx/test_link_map.rs | 10 +- .../src/kernel_tests/tx/test_note.rs | 5 +- .../src/kernel_tests/tx/test_tx.rs | 8 +- crates/miden-testing/tests/scripts/faucet.rs | 151 +++++++++++++----- 8 files changed, 148 insertions(+), 107 deletions(-) diff --git a/crates/miden-lib/src/account/faucets/mod.rs b/crates/miden-lib/src/account/faucets/mod.rs index ede914c554..3c22447412 100644 --- a/crates/miden-lib/src/account/faucets/mod.rs +++ b/crates/miden-lib/src/account/faucets/mod.rs @@ -432,6 +432,14 @@ mod tests { ); assert!(faucet_account.is_faucet()); + + assert_eq!(faucet_account.account_type(), AccountType::FungibleFaucet); + + // Verify the faucet can be extracted and has correct metadata + let faucet_component = BasicFungibleFaucet::try_from(faucet_account.clone()).unwrap(); + assert_eq!(faucet_component.symbol(), token_symbol); + assert_eq!(faucet_component.decimals(), decimals); + assert_eq!(faucet_component.max_supply(), max_supply); } #[test] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index 27ea2878ea..357b5e0c05 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -126,50 +126,43 @@ fn storage_delta_for_value_slots() -> anyhow::Result<()> { let tx_script = compile_tx_script(format!( " begin - push.{tmp_slot_0_value} + push.{slot_0_tmp_value} push.0 # => [index, VALUE] exec.set_item # => [] - push.{final_slot_0_value} + push.{slot_0_final_value} push.0 # => [index, VALUE] exec.set_item # => [] - push.{final_slot_1_value} + push.{slot_1_final_value} push.1 # => [index, VALUE] exec.set_item # => [] - push.{final_slot_2_value} + push.{slot_2_final_value} push.2 # => [index, VALUE] exec.set_item # => [] - push.{tmp_slot_3_value} + push.{slot_3_tmp_value} push.3 # => [index, VALUE] exec.set_item # => [] - push.{final_slot_3_value} + push.{slot_3_final_value} push.3 # => [index, VALUE] exec.set_item # => [] end - ", - // Set slot 0 to some other value initially. - tmp_slot_0_value = slot_0_tmp_value, - final_slot_0_value = slot_0_final_value, - final_slot_1_value = slot_1_final_value, - final_slot_2_value = slot_2_final_value, - tmp_slot_3_value = slot_3_tmp_value, - final_slot_3_value = slot_3_final_value, + " ))?; let executed_tx = mock_chain @@ -260,7 +253,7 @@ fn storage_delta_for_map_slots() -> anyhow::Result<()> { let tx_script = compile_tx_script(format!( " begin - push.{key0_value}.{key0}.0 + push.{key0_final_value}.{key0}.0 # => [index, KEY, VALUE] exec.set_map_item # => [] @@ -270,17 +263,17 @@ fn storage_delta_for_map_slots() -> anyhow::Result<()> { exec.set_map_item # => [] - push.{key1_value}.{key1}.0 + push.{key1_final_value}.{key1}.0 # => [index, KEY, VALUE] exec.set_map_item # => [] - push.{key2_value}.{key2}.1 + push.{key2_final_value}.{key2}.1 # => [index, KEY, VALUE] exec.set_map_item # => [] - push.{key3_value}.{key3}.1 + push.{key3_final_value}.{key3}.1 # => [index, KEY, VALUE] exec.set_map_item # => [] @@ -290,7 +283,7 @@ fn storage_delta_for_map_slots() -> anyhow::Result<()> { exec.set_map_item # => [] - push.{key4_value}.{key4}.1 + push.{key4_final_value}.{key4}.1 # => [index, KEY, VALUE] exec.set_map_item # => [] @@ -300,27 +293,12 @@ fn storage_delta_for_map_slots() -> anyhow::Result<()> { exec.set_map_item # => [] - push.{key5_value}.{key5}.2 + push.{key5_final_value}.{key5}.2 # => [index, KEY, VALUE] exec.set_map_item # => [] end - ", - key0 = key0, - key1 = key1, - key2 = key2, - key3 = key3, - key4 = key4, - key5 = key5, - key0_value = key0_final_value, - key1_tmp_value = key1_tmp_value, - key1_value = key1_final_value, - key2_value = key2_final_value, - key3_value = key3_final_value, - key4_tmp_value = key4_tmp_value, - key4_value = key4_final_value, - key5_tmp_value = key5_tmp_value, - key5_value = key5_final_value, + " ))?; let executed_tx = mock_chain @@ -667,7 +645,7 @@ fn asset_and_storage_delta() -> anyhow::Result<()> { ## Update account storage item ## ------------------------------------------------------------------------------------ # push a new value for the storage slot onto the stack - push.{UPDATED_SLOT_VALUE} + push.{updated_slot_value} # => [13, 11, 9, 7] # get the index of account storage slot @@ -680,11 +658,11 @@ fn asset_and_storage_delta() -> anyhow::Result<()> { ## Update account storage map ## ------------------------------------------------------------------------------------ # push a new VALUE for the storage map onto the stack - push.{UPDATED_MAP_VALUE} + push.{updated_map_value} # => [18, 19, 20, 21] # push a new KEY for the storage map onto the stack - push.{UPDATED_MAP_KEY} + push.{updated_map_key} # => [14, 15, 16, 17, 18, 19, 20, 21] # get the index of account storage slot @@ -701,10 +679,7 @@ fn asset_and_storage_delta() -> anyhow::Result<()> { dropw dropw dropw dropw end - ", - UPDATED_SLOT_VALUE = updated_slot_value, - UPDATED_MAP_VALUE = updated_map_value, - UPDATED_MAP_KEY = updated_map_key, + " ); let tx_script = ScriptBuilder::with_mock_libraries()?.compile_tx_script(tx_script_src)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs index 92324c7845..532c53119d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs @@ -97,14 +97,13 @@ fn test_validate_non_fungible_asset() -> anyhow::Result<()> { use.$kernel::asset begin - push.{asset} + push.{non_fungible_asset} exec.asset::validate_non_fungible_asset # truncate the stack swapw dropw end - ", - asset = non_fungible_asset + " ); let process = &tx_context.execute_code(&code)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index f515358291..91dc8a85e6 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -33,7 +33,7 @@ fn test_get_asset_info() -> anyhow::Result<()> { # => [ASSETS_COMMITMENT, num_assets] # assert the correctness of the assets hash - push.{COMPUTED_ASSETS_COMMITMENT} + push.{assets_commitment} assert_eqw.err="note {note_index} has incorrect assets hash" # => [num_assets] @@ -41,10 +41,7 @@ fn test_get_asset_info() -> anyhow::Result<()> { push.{assets_number} assert_eq.err="note {note_index} has incorrect assets number" # => [] - "#, - note_index = note_index, - COMPUTED_ASSETS_COMMITMENT = assets_commitment, - assets_number = assets_number, + "# ) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs b/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs index 48ebe76580..086ccf348b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs @@ -171,15 +171,7 @@ fn insertion() -> anyhow::Result<()> { assert_eqw.err="retrieved value1 for key {entry3_key} should be an empty word" # => [] end - "#, - entry0_key = entry0_key, - entry0_value = entry0_value, - entry1_key = entry1_key, - entry1_value = entry1_value, - entry2_key = entry2_key, - entry2_value = entry2_value, - entry3_key = entry3_key, - entry3_value = entry3_value, + "# ); let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index ef3356a48a..6da6bf639c 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -298,11 +298,10 @@ fn test_get_inputs() -> anyhow::Result<()> { # => [dest_ptr] dup padw movup.4 mem_loadw push.{inputs_word} assert_eqw.err="inputs are incorrect" # => [dest_ptr] - + push.4 add # => [dest_ptr+4] - "#, - inputs_word = inputs_word + "# ); } code diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index b0b53685e4..d21b1558bd 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -1327,17 +1327,15 @@ fn test_tx_script_inputs() -> anyhow::Result<()> { begin # push the tx script input key onto the stack - push.{key} + push.{tx_script_input_key} # load the tx script input value from the map and read it onto the stack adv.push_mapval adv_loadw # assert that the value is correct - push.{value} assert_eqw + push.{tx_script_input_value} assert_eqw end - ", - key = tx_script_input_key, - value = tx_script_input_value + " ); let tx_script = ScriptBuilder::default().compile_tx_script(tx_script_src)?; diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 78c29c3d31..73d7a99591 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -3,35 +3,31 @@ extern crate alloc; use miden_lib::account::faucets::FungibleFaucetExt; use miden_lib::errors::tx_kernel_errors::ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED; use miden_lib::utils::ScriptBuilder; +use miden_objects::account::Account; use miden_objects::asset::{Asset, FungibleAsset}; use miden_objects::note::{NoteAssets, NoteExecutionHint, NoteId, NoteMetadata, NoteTag, NoteType}; -use miden_objects::transaction::OutputNote; +use miden_objects::transaction::{ExecutedTransaction, OutputNote}; use miden_objects::{Felt, Word}; use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; use crate::{get_note_with_fungible_asset_and_script, prove_and_verify_transaction}; -// TESTS MINT FUNGIBLE ASSET +// Shared test utilities for faucet tests // ================================================================================================ -#[test] -fn prove_faucet_contract_mint_fungible_asset_succeeds() -> anyhow::Result<()> { - // CONSTRUCT AND EXECUTE TX (Success) - // -------------------------------------------------------------------------------------------- - let mut builder = MockChain::builder(); - let faucet = builder.add_existing_faucet(Auth::BasicAuth, "TST", 200, None)?; - let mock_chain = builder.build()?; - - let recipient = Word::from([0, 1, 2, 3u32]); - let tag = NoteTag::for_local_use_case(0, 0).unwrap(); - let aux = Felt::new(27); - let note_execution_hint = NoteExecutionHint::on_block_slot(5, 6, 7); - let note_type = NoteType::Private; - let amount = Felt::new(100); - - tag.validate(note_type).expect("note tag should support private notes"); +/// Common test parameters for faucet tests +pub struct FaucetTestParams { + pub recipient: Word, + pub tag: NoteTag, + pub aux: Felt, + pub note_execution_hint: NoteExecutionHint, + pub note_type: NoteType, + pub amount: Felt, +} - let tx_script_code = format!( +/// Creates minting script code for fungible asset distribution +pub fn create_mint_script_code(params: &FaucetTestParams) -> String { + format!( " begin # pad the stack before call @@ -52,39 +48,85 @@ fn prove_faucet_contract_mint_fungible_asset_succeeds() -> anyhow::Result<()> { dropw dropw dropw dropw end ", - note_type = note_type as u8, - recipient = recipient, - aux = aux, - tag = u32::from(tag), - note_execution_hint = Felt::from(note_execution_hint) - ); + note_type = params.note_type as u8, + recipient = params.recipient, + aux = params.aux, + tag = u32::from(params.tag), + note_execution_hint = Felt::from(params.note_execution_hint), + amount = params.amount, + ) +} +/// Executes a minting transaction with the given faucet and parameters +pub fn execute_mint_transaction( + mock_chain: &mut MockChain, + faucet: Account, + params: &FaucetTestParams, +) -> anyhow::Result { + let tx_script_code = create_mint_script_code(params); let tx_script = ScriptBuilder::default().compile_tx_script(tx_script_code)?; - let tx_context = mock_chain - .build_tx_context(faucet.id(), &[], &[])? - .tx_script(tx_script) - .build()?; + let tx_context = mock_chain.build_tx_context(faucet, &[], &[])?.tx_script(tx_script).build()?; - let executed_transaction = tx_context.execute_blocking()?; - - prove_and_verify_transaction(executed_transaction.clone())?; + Ok(tx_context.execute_blocking()?) +} - let fungible_asset: Asset = FungibleAsset::new(faucet.id(), amount.into())?.into(); +/// Verifies minted output note matches expectations +pub fn verify_minted_output_note( + executed_transaction: &ExecutedTransaction, + faucet: &Account, + params: &FaucetTestParams, +) -> anyhow::Result<()> { + let fungible_asset: Asset = FungibleAsset::new(faucet.id(), params.amount.into())?.into(); let output_note = executed_transaction.output_notes().get_note(0).clone(); - let assets = NoteAssets::new(vec![fungible_asset])?; - let id = NoteId::new(recipient, assets.commitment()); + let id = NoteId::new(params.recipient, assets.commitment()); assert_eq!(output_note.id(), id); assert_eq!( output_note.metadata(), - &NoteMetadata::new(faucet.id(), NoteType::Private, tag, note_execution_hint, aux)? + &NoteMetadata::new( + faucet.id(), + params.note_type, + params.tag, + params.note_execution_hint, + params.aux + )? ); Ok(()) } +// TESTS MINT FUNGIBLE ASSET +// ================================================================================================ + +/// Tests that minting assets on an existing faucet succeeds. +#[test] +fn minting_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let faucet = builder.add_existing_faucet(Auth::BasicAuth, "TST", 200, None)?; + let mut mock_chain = builder.build()?; + + let params = FaucetTestParams { + recipient: Word::from([0, 1, 2, 3u32]), + tag: NoteTag::for_local_use_case(0, 0).unwrap(), + aux: Felt::new(27), + note_execution_hint: NoteExecutionHint::on_block_slot(5, 6, 7), + note_type: NoteType::Private, + amount: Felt::new(100), + }; + + params + .tag + .validate(params.note_type) + .expect("note tag should support private notes"); + + let executed_transaction = execute_mint_transaction(&mut mock_chain, faucet.clone(), ¶ms)?; + verify_minted_output_note(&executed_transaction, &faucet, ¶ms)?; + + Ok(()) +} + #[test] fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() -> anyhow::Result<()> { // CONSTRUCT AND EXECUTE TX (Failure) @@ -138,11 +180,42 @@ fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() -> anyhow::Res Ok(()) } +// TESTS FOR NEW FAUCET EXECUTION ENVIRONMENT +// ================================================================================================ + +/// Tests that minting assets on a new faucet succeeds. +#[test] +fn minting_fungible_asset_on_new_faucet_succeeds() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let faucet = builder.create_new_faucet(Auth::BasicAuth, "TST", 200)?; + let mut mock_chain = builder.build()?; + + let params = FaucetTestParams { + recipient: Word::from([0, 1, 2, 3u32]), + tag: NoteTag::for_local_use_case(0, 0).unwrap(), + aux: Felt::new(27), + note_execution_hint: NoteExecutionHint::on_block_slot(5, 6, 7), + note_type: NoteType::Private, + amount: Felt::new(100), + }; + + params + .tag + .validate(params.note_type) + .expect("note tag should support private notes"); + + let executed_transaction = execute_mint_transaction(&mut mock_chain, faucet.clone(), ¶ms)?; + verify_minted_output_note(&executed_transaction, &faucet, ¶ms)?; + + Ok(()) +} + // TESTS BURN FUNGIBLE ASSET // ================================================================================================ +/// Tests that burning a fungible asset on an existing faucet succeeds and proves the transaction. #[test] -fn prove_faucet_contract_burn_fungible_asset_succeeds() -> anyhow::Result<()> { +fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let faucet = builder.add_existing_faucet(Auth::BasicAuth, "TST", 200, Some(100))?; let mut mock_chain = builder.build()?; @@ -159,7 +232,7 @@ fn prove_faucet_contract_burn_fungible_asset_succeeds() -> anyhow::Result<()> { assert_eq!(faucet.get_token_issuance().unwrap(), Felt::new(100)); // need to create a note with the fungible asset to be burned - let note_script = " + let burn_note_script_code = " # burn the asset begin dropw @@ -179,7 +252,7 @@ fn prove_faucet_contract_burn_fungible_asset_succeeds() -> anyhow::Result<()> { end "; - let note = get_note_with_fungible_asset_and_script(fungible_asset, note_script); + let note = get_note_with_fungible_asset_and_script(fungible_asset, burn_note_script_code); mock_chain.add_pending_note(OutputNote::Full(note.clone())); mock_chain.prove_next_block()?; From 7d7c27fdcff6e99bfed3e0ed7255aba3367f1edd Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:53:06 +0300 Subject: [PATCH 030/133] Add `build_recipient` procedure (#1807) --- CHANGELOG.md | 1 + crates/miden-lib/asm/miden/note.masm | 56 +++++- crates/miden-lib/asm/miden/tx.masm | 23 --- .../src/kernel_tests/tx/test_note.rs | 180 +++++++++++++----- .../src/kernel_tests/tx/test_tx.rs | 7 +- 5 files changed, 190 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f52a060c3..3bc8508aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). - Enable lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). - Lazy load the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). +- Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). ### Changes diff --git a/crates/miden-lib/asm/miden/note.masm b/crates/miden-lib/asm/miden/note.masm index 63b96bef07..78499f7077 100644 --- a/crates/miden-lib/asm/miden/note.masm +++ b/crates/miden-lib/asm/miden/note.masm @@ -233,7 +233,6 @@ export.compute_inputs_commitment # => [inputs_ptr, num_inputs, pad_inputs_flag] exec.rpo::prepare_hasher_state - exec.rpo::hash_memory_with_state # => [INPUTS_COMMITMENT] end @@ -368,6 +367,61 @@ export.write_assets_to_memory # AS => [] end + +#! Builds the recipient hash from note inputs, script root, and serial number. +#! +#! This procedure computes the commitment of the note inputs and then uses it to calculate the note +#! recipient by hashing this commit, the provided script root, and the serial number. +#! +#! Inputs: [inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] +#! Outputs: [RECIPIENT] +#! +#! Where: +#! - inputs_ptr is the memory address where the note inputs are stored. +#! - num_inputs is the number of input values. +#! - SCRIPT_ROOT is the script root of the note. +#! - SERIAL_NUM is the serial number of the note. +#! - RECIPIENT is the commitment to the input note's script, inputs, the serial number. +#! +#! Panics if: +#! - inputs_ptr is not word-aligned (i.e., is not a multiple of 4). +#! - num_inputs is greater than 128. +#! +#! Invocation: exec +export.build_recipient + exec.compute_inputs_commitment + # => [INPUTS_HASH, SERIAL_NUM, SCRIPT_ROOT] + + movdnw.2 + # => [SERIAL_NUM, SCRIPT_ROOT, INPUTS_HASH] + + exec.build_recipient_hash + # => [RECIPIENT] +end + +#! Returns the RECIPIENT for a specified SERIAL_NUM, SCRIPT_ROOT, and inputs commitment. +#! +#! Inputs: [SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT] +#! Outputs: [RECIPIENT] +#! +#! Where: +#! - SERIAL_NUM is the serial number of the recipient. +#! - SCRIPT_ROOT is the commitment of the note script. +#! - INPUT_COMMITMENT is the commitment of the note inputs. +#! - RECIPIENT is the recipient of the note. +#! +#! Invocation: exec +export.build_recipient_hash + padw hmerge + # => [SERIAL_NUM_HASH, SCRIPT_ROOT, INPUT_COMMITMENT] + + swapw hmerge + # => [MERGE_SCRIPT, INPUT_COMMITMENT] + + swapw hmerge + # [RECIPIENT] +end + # HELPER PROCEDURES # ================================================================================================= diff --git a/crates/miden-lib/asm/miden/tx.masm b/crates/miden-lib/asm/miden/tx.masm index 54439d7318..91a826a386 100644 --- a/crates/miden-lib/asm/miden/tx.masm +++ b/crates/miden-lib/asm/miden/tx.masm @@ -257,29 +257,6 @@ export.add_asset_to_note # => [ASSET, note_idx] end -#! Returns the RECIPIENT for a specified SERIAL_NUM, SCRIPT_ROOT, and inputs commitment. -#! -#! Inputs: [SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT] -#! Outputs: [RECIPIENT] -#! -#! Where: -#! - SERIAL_NUM is the serial number of the recipient. -#! - SCRIPT_ROOT is the commitment of the note script. -#! - INPUT_COMMITMENT is the commitment of the note inputs. -#! - RECIPIENT is the recipient of the note. -#! -#! Invocation: exec -export.build_recipient_hash - padw hmerge - # => [SERIAL_NUM_HASH, SCRIPT_ROOT, INPUT_COMMITMENT] - - swapw hmerge - # => [MERGE_SCRIPT, INPUT_COMMITMENT] - - swapw hmerge - # [RECIPIENT] -end - #! Executes the provided procedure against the foreign account. #! #! WARNING: the procedure to be invoked can not have more than 15 inputs and it can not return more diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 6da6bf639c..d3737ede3d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -617,20 +617,121 @@ fn test_get_note_serial_number() -> anyhow::Result<()> { } #[test] -fn test_get_inputs_hash() -> anyhow::Result<()> { +fn test_build_recipient() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - let code = " + // Create test script and serial number + let note_script = ScriptBuilder::default().compile_note_script("begin nop end")?; + let serial_num = Word::default(); + + // Define test values as Words + let word_1 = Word::from([1, 2, 3, 4u32]); + let word_2 = Word::from([5, 6, 7, 8u32]); + let word_3 = Word::from([9, 10, 11, 12u32]); + let word_4 = Word::from([13, 14, 15, 16u32]); + const BASE_ADDR: u32 = 4000; + + let code = format!( + " use.std::sys use.miden::note begin # put the values that will be hashed into the memory - push.1.2.3.4.4000 mem_storew dropw - push.5.6.7.8.4004 mem_storew dropw - push.9.10.11.12.4008 mem_storew dropw - push.13.14.15.16.4012 mem_storew dropw + push.{word_1}.{base_addr} mem_storew dropw + push.{word_2}.{addr_1} mem_storew dropw + push.{word_3}.{addr_2} mem_storew dropw + push.{word_4}.{addr_3} mem_storew dropw + + # Test with 4 values + push.{script_root} # SCRIPT_ROOT + push.{serial_num} # SERIAL_NUM + push.4.4000 # num_inputs, inputs_ptr + exec.note::build_recipient + # => [RECIPIENT_4] + + # Test with 5 values + push.{script_root} # SCRIPT_ROOT + push.{serial_num} # SERIAL_NUM + push.5.4000 # num_inputs, inputs_ptr + exec.note::build_recipient + # => [RECIPIENT_5, RECIPIENT_4] + + # Test with 13 values + push.{script_root} # SCRIPT_ROOT + push.{serial_num} # SERIAL_NUM + push.13.4000 # num_inputs, inputs_ptr + exec.note::build_recipient + # => [RECIPIENT_13, RECIPIENT_5, RECIPIENT_4] + + # truncate the stack + exec.sys::truncate_stack + end + ", + word_1 = word_1, + word_2 = word_2, + word_3 = word_3, + word_4 = word_4, + base_addr = BASE_ADDR, + addr_1 = BASE_ADDR + 4, + addr_2 = BASE_ADDR + 8, + addr_3 = BASE_ADDR + 12, + script_root = note_script.root(), + serial_num = serial_num, + ); + + let process = &tx_context.execute_code(&code)?; + + // Create expected recipients and get their digests + let note_inputs_4 = NoteInputs::new(word_1.to_vec())?; + let recipient_4 = NoteRecipient::new(serial_num, note_script.clone(), note_inputs_4); + + let mut inputs_5 = word_1.to_vec(); + inputs_5.push(word_2[0]); + let note_inputs_5 = NoteInputs::new(inputs_5)?; + let recipient_5 = NoteRecipient::new(serial_num, note_script.clone(), note_inputs_5); + + let mut inputs_13 = word_1.to_vec(); + inputs_13.extend_from_slice(&word_2.to_vec()); + inputs_13.extend_from_slice(&word_3.to_vec()); + inputs_13.push(word_4[0]); + let note_inputs_13 = NoteInputs::new(inputs_13)?; + let recipient_13 = NoteRecipient::new(serial_num, note_script, note_inputs_13); + + let mut expected_stack = alloc::vec::Vec::new(); + expected_stack.extend_from_slice(recipient_4.digest().as_elements()); + expected_stack.extend_from_slice(recipient_5.digest().as_elements()); + expected_stack.extend_from_slice(recipient_13.digest().as_elements()); + expected_stack.reverse(); + + assert_eq!(process.stack.get_state_at(process.system.clk())[0..12], expected_stack); + Ok(()) +} + +#[test] +fn test_compute_inputs_commitment() -> anyhow::Result<()> { + let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; + + // Define test values as Words + let word_1 = Word::from([1, 2, 3, 4u32]); + let word_2 = Word::from([5, 6, 7, 8u32]); + let word_3 = Word::from([9, 10, 11, 12u32]); + let word_4 = Word::from([13, 14, 15, 16u32]); + const BASE_ADDR: u32 = 4000; + + let code = format!( + " + use.std::sys + + use.miden::note + + begin + # put the values that will be hashed into the memory + push.{word_1}.{base_addr} mem_storew dropw + push.{word_2}.{addr_1} mem_storew dropw + push.{word_3}.{addr_2} mem_storew dropw + push.{word_4}.{addr_3} mem_storew dropw # push the number of values and pointer to the inputs on the stack push.5.4000 @@ -657,49 +758,32 @@ fn test_get_inputs_hash() -> anyhow::Result<()> { # truncate the stack exec.sys::truncate_stack end - "; + ", + word_1 = word_1, + word_2 = word_2, + word_3 = word_3, + word_4 = word_4, + base_addr = BASE_ADDR, + addr_1 = BASE_ADDR + 4, + addr_2 = BASE_ADDR + 8, + addr_3 = BASE_ADDR + 12, + ); + + let process = &tx_context.execute_code(&code)?; + + let mut inputs_5 = word_1.to_vec(); + inputs_5.push(word_2[0]); + let note_inputs_5_hash = NoteInputs::new(inputs_5)?.commitment(); + + let mut inputs_8 = word_1.to_vec(); + inputs_8.extend_from_slice(&word_2.to_vec()); + let note_inputs_8_hash = NoteInputs::new(inputs_8)?.commitment(); - let process = &tx_context.execute_code(code)?; - - let note_inputs_5_hash = NoteInputs::new(vec![ - Felt::new(1), - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - ])? - .commitment(); - - let note_inputs_8_hash = NoteInputs::new(vec![ - Felt::new(1), - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - Felt::new(6), - Felt::new(7), - Felt::new(8), - ])? - .commitment(); - - let note_inputs_15_hash = NoteInputs::new(vec![ - Felt::new(1), - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - Felt::new(6), - Felt::new(7), - Felt::new(8), - Felt::new(9), - Felt::new(10), - Felt::new(11), - Felt::new(12), - Felt::new(13), - Felt::new(14), - Felt::new(15), - ])? - .commitment(); + let mut inputs_15 = word_1.to_vec(); + inputs_15.extend_from_slice(&word_2.to_vec()); + inputs_15.extend_from_slice(&word_3.to_vec()); + inputs_15.extend_from_slice(&word_4[0..3]); + let note_inputs_15_hash = NoteInputs::new(inputs_15)?.commitment(); let mut expected_stack = alloc::vec::Vec::new(); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index d21b1558bd..42f4d028dd 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -774,12 +774,9 @@ fn test_build_recipient_hash() -> anyhow::Result<()> { let code = format!( " use.miden::tx + use.miden::note use.$kernel::prologue - proc.build_recipient_hash - exec.tx::build_recipient_hash - end - begin exec.prologue::prepare_transaction @@ -794,7 +791,7 @@ fn test_build_recipient_hash() -> anyhow::Result<()> { push.{output_serial_no} # => [SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT, pad(4)] - call.build_recipient_hash + exec.note::build_recipient_hash # => [RECIPIENT, pad(12)] push.{execution_hint} From 34694de9cb63c0778d43e01ce15e6284ad52e627 Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Wed, 10 Sep 2025 12:53:22 +0530 Subject: [PATCH 031/133] chore: Move `TransactionKernelError` to miden-tx (#1859) * chore: Move TransactionKernelError to miden-tx * fix: add changelog * Update CHANGELOG.md * Update crates/miden-tx/src/errors/mod.rs * Update CHANGELOG.md * fix: use AuthenticationError * fix: make clippy happy --------- Co-authored-by: Marti Co-authored-by: Marti Co-authored-by: Philipp Gackstatter --- CHANGELOG.md | 1 + crates/miden-lib/src/errors/mod.rs | 6 +- .../src/errors/transaction_errors.rs | 140 ------------------ crates/miden-lib/src/transaction/mod.rs | 6 +- .../tests/auth/rpo_falcon_acl.rs | 3 +- crates/miden-tx/src/errors/mod.rs | 125 +++++++++++++++- crates/miden-tx/src/executor/exec_host.rs | 9 +- crates/miden-tx/src/executor/mod.rs | 2 +- .../miden-tx/src/host/account_procedures.rs | 4 +- crates/miden-tx/src/host/mod.rs | 3 +- crates/miden-tx/src/host/note_builder.rs | 9 +- crates/miden-tx/src/lib.rs | 1 + 12 files changed, 140 insertions(+), 169 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc8508aba..92cc9da90a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - [BREAKING] Renamed the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). - [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). +- [BREAKING] Move `TransactionKernelError` to miden-tx ([#1859](https://github.com/0xMiden/miden-base/pull/1859)). ## 0.11.2 (2025-09-08) diff --git a/crates/miden-lib/src/errors/mod.rs b/crates/miden-lib/src/errors/mod.rs index cd36431118..6bd4bda685 100644 --- a/crates/miden-lib/src/errors/mod.rs +++ b/crates/miden-lib/src/errors/mod.rs @@ -13,8 +13,4 @@ mod script_builder_errors; pub use script_builder_errors::ScriptBuilderError; mod transaction_errors; -pub use transaction_errors::{ - TransactionEventError, - TransactionKernelError, - TransactionTraceParsingError, -}; +pub use transaction_errors::{TransactionEventError, TransactionTraceParsingError}; diff --git a/crates/miden-lib/src/errors/transaction_errors.rs b/crates/miden-lib/src/errors/transaction_errors.rs index 38c54e98d7..ba2155f346 100644 --- a/crates/miden-lib/src/errors/transaction_errors.rs +++ b/crates/miden-lib/src/errors/transaction_errors.rs @@ -1,133 +1,5 @@ -use alloc::boxed::Box; -use alloc::string::String; -use alloc::vec::Vec; -use core::error::Error; - -use miden_objects::note::NoteMetadata; -use miden_objects::transaction::TransactionSummary; -use miden_objects::{AccountDeltaError, AssetError, Felt, NoteError, Word}; use thiserror::Error; -// TRANSACTION KERNEL ERROR -// ================================================================================================ - -#[derive(Debug, Error)] -pub enum TransactionKernelError { - #[error("failed to add asset to account delta")] - AccountDeltaAddAssetFailed(#[source] AccountDeltaError), - #[error("failed to remove asset to account delta")] - AccountDeltaRemoveAssetFailed(#[source] AccountDeltaError), - #[error("failed to add asset to note")] - FailedToAddAssetToNote(#[source] NoteError), - #[error("note input data has hash {actual} but expected hash {expected}")] - InvalidNoteInputs { expected: Word, actual: Word }, - #[error( - "storage slot index {actual} is invalid, must be smaller than the number of account storage slots {max}" - )] - InvalidStorageSlotIndex { max: u64, actual: u64 }, - #[error( - "failed to respond to signature requested since no authenticator is assigned to the host" - )] - MissingAuthenticator, - #[error("failed to generate signature")] - SignatureGenerationFailed(#[source] Box), - #[error("transaction returned unauthorized event but a commitment did not match: {0}")] - TransactionSummaryCommitmentMismatch(#[source] Box), - #[error("failed to construct transaction summary")] - TransactionSummaryConstructionFailed(#[source] Box), - #[error("asset data extracted from the stack by event handler `{handler}` is not well formed")] - MalformedAssetInEventHandler { - handler: &'static str, - source: AssetError, - }, - #[error( - "note inputs data extracted from the advice map by the event handler is not well formed" - )] - MalformedNoteInputs(#[source] NoteError), - #[error("note metadata created by the event handler is not well formed")] - MalformedNoteMetadata(#[source] NoteError), - #[error( - "note script data `{data:?}` extracted from the advice map by the event handler is not well formed" - )] - MalformedNoteScript { - data: Vec, - // This is always a DeserializationError, but we can't import it directly here without - // adding dependencies, so we make it a trait object instead. - // thiserror will return this when calling Error::source on TransactionKernelError. - source: Box, - }, - #[error("recipient data `{0:?}` in the advice provider is not well formed")] - MalformedRecipientData(Vec), - #[error("cannot add asset to note with index {0}, note does not exist in the advice provider")] - MissingNote(u64), - #[error( - "public note with metadata {0:?} and recipient digest {1} is missing details in the advice provider" - )] - PublicNoteMissingDetails(NoteMetadata, Word), - #[error( - "note input data in advice provider contains fewer elements ({actual}) than specified ({specified}) by its inputs length" - )] - TooFewElementsForNoteInputs { specified: u64, actual: u64 }, - #[error("account procedure with procedure root {0} is not in the advice provider")] - UnknownAccountProcedure(Word), - #[error("code commitment {0} is not in the advice provider")] - UnknownCodeCommitment(Word), - #[error("account storage slots number is missing in memory at address {0}")] - AccountStorageSlotsNumMissing(u32), - #[error("account nonce can only be incremented once")] - NonceCanOnlyIncrementOnce, - #[error("failed to convert fee asset into fungible asset")] - FailedToConvertFeeAsset(#[source] AssetError), - #[error( - "failed to get vault asset witness from data store for vault root {vault_root} and vault_key {vault_key}" - )] - GetVaultAssetWitness { - vault_root: Word, - vault_key: Word, - // TODO: Change to DataStoreError when this error moves to miden-tx. - // This is always a DataStoreError, but we can't import it from miden-tx here. - // thiserror will return this when calling Error::source on TransactionKernelError. - source: Box, - }, - #[error( - "native asset amount {account_balance} in the account vault is not sufficient to cover the transaction fee of {tx_fee}" - )] - InsufficientFee { account_balance: u64, tx_fee: u64 }, - /// This variant signals that a signature over the contained commitments is required, but - /// missing. - #[error("transaction requires a signature")] - Unauthorized(Box), - /// A generic error returned when the transaction kernel did not behave as expected. - #[error("{message}")] - Other { - message: Box, - // thiserror will return this when calling Error::source on TransactionKernelError. - source: Option>, - }, -} - -impl TransactionKernelError { - /// Creates a custom error using the [`TransactionKernelError::Other`] variant from an error - /// message. - pub fn other(message: impl Into) -> Self { - let message: String = message.into(); - Self::Other { message: message.into(), source: None } - } - - /// Creates a custom error using the [`TransactionKernelError::Other`] variant from an error - /// message and a source error. - pub fn other_with_source( - message: impl Into, - source: impl Error + Send + Sync + 'static, - ) -> Self { - let message: String = message.into(); - Self::Other { - message: message.into(), - source: Some(Box::new(source)), - } - } -} - // TRANSACTION EVENT PARSING ERROR // ================================================================================================ @@ -149,15 +21,3 @@ pub enum TransactionTraceParsingError { #[error("trace id {0} is an unknown transaction kernel trace")] UnknownTransactionTrace(u32), } - -#[cfg(test)] -mod error_assertions { - use super::*; - - /// Asserts at compile time that the passed error has Send + Sync + 'static bounds. - fn _assert_error_is_send_sync_static(_: E) {} - - fn _assert_transaction_kernel_error_bounds(err: TransactionKernelError) { - _assert_error_is_send_sync_static(err); - } -} diff --git a/crates/miden-lib/src/transaction/mod.rs b/crates/miden-lib/src/transaction/mod.rs index 29e9d25172..e69c2a261c 100644 --- a/crates/miden-lib/src/transaction/mod.rs +++ b/crates/miden-lib/src/transaction/mod.rs @@ -42,11 +42,7 @@ pub use outputs::{ parse_final_account_header, }; -pub use crate::errors::{ - TransactionEventError, - TransactionKernelError, - TransactionTraceParsingError, -}; +pub use crate::errors::{TransactionEventError, TransactionTraceParsingError}; mod kernel_procedures; use kernel_procedures::KERNEL_PROCEDURES; diff --git a/crates/miden-testing/tests/auth/rpo_falcon_acl.rs b/crates/miden-testing/tests/auth/rpo_falcon_acl.rs index 6da9a34b12..63f9f99da8 100644 --- a/crates/miden-testing/tests/auth/rpo_falcon_acl.rs +++ b/crates/miden-testing/tests/auth/rpo_falcon_acl.rs @@ -3,7 +3,6 @@ use core::slice; use assert_matches::assert_matches; use miden_lib::testing::account_component::MockAccountComponent; use miden_lib::testing::note::NoteBuilder; -use miden_lib::transaction::TransactionKernelError; use miden_lib::utils::ScriptBuilder; use miden_objects::account::{ AccountBuilder, @@ -18,7 +17,7 @@ use miden_objects::transaction::OutputNote; use miden_objects::{Felt, FieldElement, Word}; use miden_processor::ExecutionError; use miden_testing::{Auth, MockChain}; -use miden_tx::TransactionExecutorError; +use miden_tx::{TransactionExecutorError, TransactionKernelError}; // CONSTANTS // ================================================================================================ diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index 442adfd27d..9490fa071d 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -1,5 +1,6 @@ use alloc::boxed::Box; use alloc::string::String; +use alloc::vec::Vec; use core::error::Error; use miden_lib::transaction::TransactionAdviceMapMismatch; @@ -7,18 +8,20 @@ use miden_objects::account::AccountId; use miden_objects::assembly::diagnostics::reporting::PrintDiagnostic; use miden_objects::block::BlockNumber; use miden_objects::crypto::merkle::SmtProofError; -use miden_objects::note::NoteId; +use miden_objects::note::{NoteId, NoteMetadata}; use miden_objects::transaction::TransactionSummary; use miden_objects::{ AccountDeltaError, AccountError, + AssetError, Felt, + NoteError, ProvenTransactionError, TransactionInputError, TransactionOutputError, Word, }; -use miden_processor::ExecutionError; +use miden_processor::{DeserializationError, ExecutionError}; use miden_verifier::VerificationError; use thiserror::Error; @@ -201,6 +204,120 @@ pub enum TransactionHostError { AccountProcedureInfoCreationFailed(#[source] AccountError), } +// TRANSACTION KERNEL ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum TransactionKernelError { + #[error("failed to add asset to account delta")] + AccountDeltaAddAssetFailed(#[source] AccountDeltaError), + #[error("failed to remove asset from account delta")] + AccountDeltaRemoveAssetFailed(#[source] AccountDeltaError), + #[error("failed to add asset to note")] + FailedToAddAssetToNote(#[source] NoteError), + #[error("note input data has hash {actual} but expected hash {expected}")] + InvalidNoteInputs { expected: Word, actual: Word }, + #[error( + "storage slot index {actual} is invalid, must be smaller than the number of account storage slots {max}" + )] + InvalidStorageSlotIndex { max: u64, actual: u64 }, + #[error( + "failed to respond to signature requested since no authenticator is assigned to the host" + )] + MissingAuthenticator, + #[error("failed to generate signature")] + SignatureGenerationFailed(#[source] AuthenticationError), + #[error("transaction returned unauthorized event but a commitment did not match: {0}")] + TransactionSummaryCommitmentMismatch(#[source] Box), + #[error("failed to construct transaction summary")] + TransactionSummaryConstructionFailed(#[source] Box), + #[error("asset data extracted from the stack by event handler `{handler}` is not well formed")] + MalformedAssetInEventHandler { + handler: &'static str, + source: AssetError, + }, + #[error( + "note inputs data extracted from the advice map by the event handler is not well formed" + )] + MalformedNoteInputs(#[source] NoteError), + #[error("note metadata created by the event handler is not well formed")] + MalformedNoteMetadata(#[source] NoteError), + #[error( + "note script data `{data:?}` extracted from the advice map by the event handler is not well formed" + )] + MalformedNoteScript { + data: Vec, + source: DeserializationError, + }, + #[error("recipient data `{0:?}` in the advice provider is not well formed")] + MalformedRecipientData(Vec), + #[error("cannot add asset to note with index {0}, note does not exist in the advice provider")] + MissingNote(u64), + #[error( + "public note with metadata {0:?} and recipient digest {1} is missing details in the advice provider" + )] + PublicNoteMissingDetails(NoteMetadata, Word), + #[error( + "note input data in advice provider contains fewer elements ({actual}) than specified ({specified}) by its inputs length" + )] + TooFewElementsForNoteInputs { specified: u64, actual: u64 }, + #[error("account procedure with procedure root {0} is not in the advice provider")] + UnknownAccountProcedure(Word), + #[error("code commitment {0} is not in the advice provider")] + UnknownCodeCommitment(Word), + #[error("account storage slots number is missing in memory at address {0}")] + AccountStorageSlotsNumMissing(u32), + #[error("account nonce can only be incremented once")] + NonceCanOnlyIncrementOnce, + #[error("failed to convert fee asset into fungible asset")] + FailedToConvertFeeAsset(#[source] AssetError), + #[error( + "failed to get vault asset witness from data store for vault root {vault_root} and vault_key {vault_key}" + )] + GetVaultAssetWitness { + vault_root: Word, + vault_key: Word, + source: DataStoreError, + }, + #[error( + "native asset amount {account_balance} in the account vault is not sufficient to cover the transaction fee of {tx_fee}" + )] + InsufficientFee { account_balance: u64, tx_fee: u64 }, + /// This variant signals that a signature over the contained commitments is required, but + /// missing. + #[error("transaction requires a signature")] + Unauthorized(Box), + /// A generic error returned when the transaction kernel did not behave as expected. + #[error("{message}")] + Other { + message: Box, + // thiserror will return this when calling Error::source on TransactionKernelError. + source: Option>, + }, +} + +impl TransactionKernelError { + /// Creates a custom error using the [`TransactionKernelError::Other`] variant from an error + /// message. + pub fn other(message: impl Into) -> Self { + let message: String = message.into(); + Self::Other { message: message.into(), source: None } + } + + /// Creates a custom error using the [`TransactionKernelError::Other`] variant from an error + /// message and a source error. + pub fn other_with_source( + message: impl Into, + source: impl Error + Send + Sync + 'static, + ) -> Self { + let message: String = message.into(); + Self::Other { + message: message.into(), + source: Some(Box::new(source)), + } + } +} + // DATA STORE ERROR // ================================================================================================ @@ -296,4 +413,8 @@ mod error_assertions { fn _assert_authentication_error_bounds(err: AuthenticationError) { _assert_error_is_send_sync_static(err); } + + fn _assert_transaction_kernel_error_bounds(err: TransactionKernelError) { + _assert_error_is_send_sync_static(err); + } } diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 2fd6ecaf79..c2f713b7de 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -1,9 +1,7 @@ -use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_lib::errors::TransactionKernelError; use miden_lib::transaction::TransactionEvent; use miden_objects::account::{AccountDelta, AccountId, PartialAccount}; use miden_objects::assembly::debuginfo::Location; @@ -24,6 +22,7 @@ use miden_processor::{ }; use crate::auth::{SigningInputs, TransactionAuthenticator}; +use crate::errors::TransactionKernelError; use crate::host::{ ScriptMastForestStore, TransactionBaseHost, @@ -126,7 +125,7 @@ where let signature: Vec = authenticator .get_signature(pub_key_hash, &signing_inputs) .await - .map_err(|err| TransactionKernelError::SignatureGenerationFailed(Box::new(err)))?; + .map_err(TransactionKernelError::SignatureGenerationFailed)?; let signature_key = Hasher::merge(&[pub_key_hash, signing_inputs.to_commitment()]); @@ -153,7 +152,7 @@ where .map_err(|err| TransactionKernelError::GetVaultAssetWitness { vault_root: self.base_host.initial_account_header().vault_root(), vault_key: fee_asset.vault_key(), - source: Box::new(err), + source: err, })?; // Find fee asset in the witness or default to 0 if it isn't present. @@ -258,7 +257,7 @@ where .map_err(|err| TransactionKernelError::GetVaultAssetWitness { vault_root, vault_key, - source: Box::new(err), + source: err, })?; Ok(asset_witness_to_advice_mutation(asset_witness)) diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index bdb8af60c7..bb5c50d0c5 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -2,7 +2,6 @@ use alloc::collections::BTreeSet; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_lib::errors::TransactionKernelError; use miden_lib::transaction::TransactionKernel; use miden_objects::account::AccountId; use miden_objects::assembly::DefaultSourceManager; @@ -26,6 +25,7 @@ pub use miden_processor::{ExecutionOptions, MastForestStore}; use super::TransactionExecutorError; use crate::auth::TransactionAuthenticator; +use crate::errors::TransactionKernelError; use crate::host::{AccountProcedureIndexMap, ScriptMastForestStore}; mod exec_host; diff --git a/crates/miden-tx/src/host/account_procedures.rs b/crates/miden-tx/src/host/account_procedures.rs index dfbf0b83ea..6223444b4b 100644 --- a/crates/miden-tx/src/host/account_procedures.rs +++ b/crates/miden-tx/src/host/account_procedures.rs @@ -1,13 +1,13 @@ use alloc::string::ToString; +use miden_lib::transaction::TransactionAdviceInputs; use miden_lib::transaction::memory::{ACCOUNT_STACK_TOP_PTR, ACCT_CODE_COMMITMENT_OFFSET}; -use miden_lib::transaction::{TransactionAdviceInputs, TransactionKernelError}; use miden_objects::account::{AccountCode, AccountProcedureInfo}; use miden_objects::transaction::{TransactionArgs, TransactionInputs}; use miden_processor::AdviceInputs; use super::{BTreeMap, Felt, ProcessState, Word}; -use crate::errors::TransactionHostError; +use crate::errors::{TransactionHostError, TransactionKernelError}; // ACCOUNT PROCEDURE INDEX MAP // ================================================================================================ diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index aad1af6108..eeb73203b0 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -27,7 +27,7 @@ use miden_lib::transaction::memory::{ CURRENT_INPUT_NOTE_PTR, NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR, }; -use miden_lib::transaction::{TransactionEvent, TransactionEventError, TransactionKernelError}; +use miden_lib::transaction::{TransactionEvent, TransactionEventError}; use miden_objects::account::{AccountDelta, AccountHeader, AccountId, PartialAccount}; use miden_objects::asset::{Asset, AssetVault, FungibleAsset}; use miden_objects::note::NoteId; @@ -55,6 +55,7 @@ use miden_processor::{ pub use tx_progress::TransactionProgress; use crate::auth::SigningInputs; +use crate::errors::TransactionKernelError; // TRANSACTION BASE HOST // ================================================================================================ diff --git a/crates/miden-tx/src/host/note_builder.rs b/crates/miden-tx/src/host/note_builder.rs index ae921bd120..ae13b20d61 100644 --- a/crates/miden-tx/src/host/note_builder.rs +++ b/crates/miden-tx/src/host/note_builder.rs @@ -1,4 +1,3 @@ -use alloc::boxed::Box; use alloc::vec::Vec; use miden_objects::asset::Asset; @@ -13,7 +12,8 @@ use miden_objects::note::{ }; use miden_processor::AdviceProvider; -use super::{Felt, OutputNote, TransactionKernelError, Word}; +use super::{Felt, OutputNote, Word}; +use crate::errors::TransactionKernelError; // OUTPUT NOTE BUILDER // ================================================================================================ @@ -104,10 +104,7 @@ impl OutputNoteBuilder { } let script = NoteScript::try_from(script_data).map_err(|source| { - TransactionKernelError::MalformedNoteScript { - data: script_data.to_vec(), - source: Box::new(source), - } + TransactionKernelError::MalformedNoteScript { data: script_data.to_vec(), source } })?; let recipient = NoteRecipient::new(serial_num, script, inputs); diff --git a/crates/miden-tx/src/lib.rs b/crates/miden-tx/src/lib.rs index 2aa3523c76..4a115601c2 100644 --- a/crates/miden-tx/src/lib.rs +++ b/crates/miden-tx/src/lib.rs @@ -41,6 +41,7 @@ pub use errors::{ DataStoreError, NoteCheckerError, TransactionExecutorError, + TransactionKernelError, TransactionProverError, TransactionVerifierError, }; From 0cd62f06741586ebd1e1431bf0b942ab7d6ecc4d Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 10 Sep 2025 09:29:02 +0200 Subject: [PATCH 032/133] feat: Remove `MockChain::add_pending_p2id_note` (#1842) * feat: Use move_asset_to_note in `SPAWN` note * chore: Use `impl IntoIterator` for `create_spawn_note` * feat: Remove `add_pending_p2id_note` * fix: `unauthenticated_note_converted_to_authenticated` test * fix: incorrect sender in `test_epilogue_asset_preservation...` * chore: add error docs for spawn note * chore: add changelog * chore: nicer assert * fix: docs * feat: Remove sender_id parameter from SPAWN note function * chore: Use `?` instead of unwrapping * chore: Match `TestSetup` exhaustively * fix: typo re reference block * fix: typo --- CHANGELOG.md | 3 + bin/bench-tx/src/main.rs | 13 +- .../src/kernel_tests/batch/proposed_batch.rs | 125 +++++++++++------- .../block/proposed_block_errors.rs | 4 +- .../block/proven_block_success.rs | 14 +- .../src/kernel_tests/tx/test_account_delta.rs | 72 +++++----- .../kernel_tests/tx/test_account_interface.rs | 2 +- .../src/kernel_tests/tx/test_epilogue.rs | 19 ++- .../src/kernel_tests/tx/test_faucet.rs | 2 +- .../src/kernel_tests/tx/test_fee.rs | 15 +-- .../src/kernel_tests/tx/test_note.rs | 2 +- .../src/kernel_tests/tx/test_prologue.rs | 6 +- .../src/kernel_tests/tx/test_tx.rs | 50 ++++--- crates/miden-testing/src/mock_chain/chain.rs | 84 ++---------- .../src/mock_chain/chain_builder.rs | 22 +-- crates/miden-testing/src/utils.rs | 74 +++++++++-- crates/miden-testing/tests/auth/multisig.rs | 2 +- 17 files changed, 257 insertions(+), 252 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92cc9da90a..66b54288b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ ### Changes - [BREAKING] Incremented MSRV to 1.89. +- [BREAKING] Remove `MockChain::add_pending_p2id_note` in favor of using `MockChainBuilder` ([#1842](https://github.com/0xMiden/miden-base/pull/#1842)). +- [BREAKING] Remove versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). +- [BREAKING] Move `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Removed versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). - Added `AccountComponent::from_package()` method to create components from `miden-mast-package::Package` ([#1802](https://github.com/0xMiden/miden-base/pull/1802)). - [BREAKING] Removed some of the `note` kernel procedures and use `input_note` procedures instead ([#1834](https://github.com/0xMiden/miden-base/pull/1834)). diff --git a/bin/bench-tx/src/main.rs b/bin/bench-tx/src/main.rs index a5ee64217a..4b98012b3b 100644 --- a/bin/bench-tx/src/main.rs +++ b/bin/bench-tx/src/main.rs @@ -68,10 +68,10 @@ pub fn benchmark_default_tx() -> anyhow::Result { Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, IncrNonceAuthComponent); let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(100)]); + create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); let input_note_2 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(150)]); + create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(150)]); TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note_1, input_note_2]) .build()? @@ -86,11 +86,11 @@ pub fn benchmark_default_tx() -> anyhow::Result { #[allow(clippy::arc_with_non_send_sync)] pub fn benchmark_p2id() -> anyhow::Result { // Create assets - let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); - let fungible_asset: Asset = FungibleAsset::new(faucet_id, 100).unwrap().into(); + let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; + let fungible_asset: Asset = FungibleAsset::new(faucet_id, 100)?.into(); // Create sender and target account - let sender_account_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); + let sender_account_id = AccountId::try_from(ACCOUNT_ID_SENDER)?; let (target_pub_key, falcon_auth) = get_new_pk_and_authenticator(); @@ -110,8 +110,7 @@ pub fn benchmark_p2id() -> anyhow::Result { NoteType::Public, Felt::new(0), &mut RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])), - ) - .unwrap(); + )?; let tx_context = TransactionContextBuilder::new(target_account.clone()) .extend_input_notes(vec![note]) diff --git a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs index a140e0efd8..9040d978d2 100644 --- a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs +++ b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs @@ -17,6 +17,7 @@ use rand::rngs::SmallRng; use rand::{Rng, SeedableRng}; use super::proven_tx_builder::MockProvenTxBuilder; +use crate::kernel_tests::block::utils::generate_untracked_note; use crate::{AccountState, Auth, MockChain, MockChainBuilder}; fn mock_account_id(num: u8) -> AccountId { @@ -36,16 +37,20 @@ struct TestSetup { chain: MockChain, account1: Account, account2: Account, + note1: Note, } fn setup_chain() -> TestSetup { let mut builder = MockChain::builder(); let account1 = generate_account(&mut builder); let account2 = generate_account(&mut builder); + let note1 = builder + .add_p2id_note(account1.id(), account2.id(), &[], NoteType::Public) + .expect("adding p2id note1 should work"); let mut chain = builder.build().expect("genesis should be valid"); chain.prove_next_block().expect("valid setup"); - TestSetup { chain, account1, account2 } + TestSetup { chain, account1, account2, note1 } } fn generate_account(chain: &mut MockChainBuilder) -> Account { @@ -77,7 +82,7 @@ fn empty_transaction_batch() -> anyhow::Result<()> { /// output note commitments. #[test] fn note_created_and_consumed_in_same_batch() -> anyhow::Result<()> { - let TestSetup { mut chain, account1, account2 } = setup_chain(); + let TestSetup { mut chain, account1, account2, .. } = setup_chain(); let block1 = chain.block_header(1); let block2 = chain.prove_next_block()?; @@ -110,7 +115,7 @@ fn note_created_and_consumed_in_same_batch() -> anyhow::Result<()> { /// times in different transactions. #[test] fn duplicate_unauthenticated_input_notes() -> anyhow::Result<()> { - let TestSetup { chain, account1, account2 } = setup_chain(); + let TestSetup { chain, account1, account2, .. } = setup_chain(); let block1 = chain.block_header(1); let note = mock_note(50); @@ -149,20 +154,19 @@ fn duplicate_unauthenticated_input_notes() -> anyhow::Result<()> { /// times in different transactions. #[test] fn duplicate_authenticated_input_notes() -> anyhow::Result<()> { - let TestSetup { mut chain, account1, account2 } = setup_chain(); - let note = chain.add_pending_p2id_note(account1.id(), account2.id(), &[], NoteType::Public)?; + let TestSetup { mut chain, account1, account2, note1 } = setup_chain(); let block1 = chain.block_header(1); let block2 = chain.prove_next_block()?; let tx1 = MockProvenTxBuilder::with_account(account1.id(), Word::empty(), account1.commitment()) .ref_block_commitment(block1.commitment()) - .authenticated_notes(vec![note.clone()]) + .authenticated_notes(vec![note1.clone()]) .build()?; let tx2 = MockProvenTxBuilder::with_account(account2.id(), Word::empty(), account2.commitment()) .ref_block_commitment(block1.commitment()) - .authenticated_notes(vec![note.clone()]) + .authenticated_notes(vec![note1.clone()]) .build()?; let error = ProposedBatch::new( @@ -177,7 +181,7 @@ fn duplicate_authenticated_input_notes() -> anyhow::Result<()> { note_nullifier, first_transaction_id, second_transaction_id - } if note_nullifier == note.nullifier() && + } if note_nullifier == note1.nullifier() && first_transaction_id == tx1.id() && second_transaction_id == tx2.id() ); @@ -189,20 +193,19 @@ fn duplicate_authenticated_input_notes() -> anyhow::Result<()> { /// transactions as an unauthenticated or authenticated note. #[test] fn duplicate_mixed_input_notes() -> anyhow::Result<()> { - let TestSetup { mut chain, account1, account2 } = setup_chain(); - let note = chain.add_pending_p2id_note(account1.id(), account2.id(), &[], NoteType::Public)?; + let TestSetup { mut chain, account1, account2, note1 } = setup_chain(); let block1 = chain.block_header(1); let block2 = chain.prove_next_block()?; let tx1 = MockProvenTxBuilder::with_account(account1.id(), Word::empty(), account1.commitment()) .ref_block_commitment(block1.commitment()) - .unauthenticated_notes(vec![note.clone()]) + .unauthenticated_notes(vec![note1.clone()]) .build()?; let tx2 = MockProvenTxBuilder::with_account(account2.id(), Word::empty(), account2.commitment()) .ref_block_commitment(block1.commitment()) - .authenticated_notes(vec![note.clone()]) + .authenticated_notes(vec![note1.clone()]) .build()?; let error = ProposedBatch::new( @@ -217,7 +220,7 @@ fn duplicate_mixed_input_notes() -> anyhow::Result<()> { note_nullifier, first_transaction_id, second_transaction_id - } if note_nullifier == note.nullifier() && + } if note_nullifier == note1.nullifier() && first_transaction_id == tx1.id() && second_transaction_id == tx2.id() ); @@ -229,7 +232,7 @@ fn duplicate_mixed_input_notes() -> anyhow::Result<()> { /// transactions. #[test] fn duplicate_output_notes() -> anyhow::Result<()> { - let TestSetup { chain, account1, account2 } = setup_chain(); + let TestSetup { chain, account1, account2, .. } = setup_chain(); let block1 = chain.block_header(1); let note0 = mock_output_note(50); @@ -265,29 +268,54 @@ fn duplicate_output_notes() -> anyhow::Result<()> { /// Test that an unauthenticated input note for which a proof exists is converted into an /// authenticated one and becomes part of the batch's input note commitment. -#[test] -fn unauthenticated_note_converted_to_authenticated() -> anyhow::Result<()> { - let TestSetup { mut chain, account1, account2 } = setup_chain(); - let note0 = chain.add_pending_p2id_note(account2.id(), account1.id(), &[], NoteType::Public)?; - let note1 = chain.add_pending_p2id_note(account1.id(), account2.id(), &[], NoteType::Public)?; - // The just created note will be provable against block2. +#[tokio::test] +async fn unauthenticated_note_converted_to_authenticated() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let account1 = generate_account(&mut builder); + let note1 = generate_untracked_note(account1.id(), account1.id()); + let note2 = generate_untracked_note(account1.id(), account1.id()); + let spawn_note = builder.add_spawn_note([¬e1, ¬e2])?; + let mut chain = builder.build()?; + + let tx = chain + .build_tx_context(account1.clone(), &[spawn_note.id()], &[])? + .extend_expected_output_notes(vec![ + OutputNote::Full(note1.clone()), + OutputNote::Full(note2.clone()), + ]) + .build()? + .execute() + .await?; + chain.add_pending_executed_transaction(&tx)?; + + // Note1 and note2 are included and therefore provable against block1. + let block1 = chain.prove_next_block()?; let block2 = chain.prove_next_block()?; let block3 = chain.prove_next_block()?; - let block4 = chain.prove_next_block()?; + + assert_eq!(block1.output_notes().count(), 2, "block 1 should contain note1 and note2"); + assert!( + block1.output_notes().any(|(_, note)| note.commitment() == note1.commitment()), + "block 1 should contain note1" + ); + assert!( + block1.output_notes().any(|(_, note)| note.commitment() == note2.commitment()), + "block 1 should contain note2" + ); // Consume the authenticated note as an unauthenticated one in the transaction. let tx1 = MockProvenTxBuilder::with_account(account1.id(), Word::empty(), account1.commitment()) - .ref_block_commitment(block3.commitment()) - .unauthenticated_notes(vec![note1.clone()]) + .ref_block_commitment(block2.commitment()) + .unauthenticated_notes(vec![note2.clone()]) .build()?; - let input_note0 = chain.get_public_note(¬e0.id()).expect("note not found"); - let note_inclusion_proof0 = input_note0.proof().expect("note should be of type authenticated"); - let input_note1 = chain.get_public_note(¬e1.id()).expect("note not found"); let note_inclusion_proof1 = input_note1.proof().expect("note should be of type authenticated"); + let input_note2 = chain.get_public_note(¬e2.id()).expect("note not found"); + let note_inclusion_proof2 = input_note2.proof().expect("note should be of type authenticated"); + // The partial blockchain will contain all blocks in the mock chain, in particular block2 which // both note inclusion proofs need for verification. let partial_blockchain = chain.latest_partial_blockchain(); @@ -297,9 +325,9 @@ fn unauthenticated_note_converted_to_authenticated() -> anyhow::Result<()> { let error = ProposedBatch::new( [tx1.clone()].into_iter().map(Arc::new).collect(), - block4.header().clone(), + block3.header().clone(), partial_blockchain.clone(), - BTreeMap::from_iter([(input_note1.id(), note_inclusion_proof0.clone())]), + BTreeMap::from_iter([(input_note2.id(), note_inclusion_proof1.clone())]), ) .unwrap_err(); @@ -307,27 +335,29 @@ fn unauthenticated_note_converted_to_authenticated() -> anyhow::Result<()> { note_id, block_num, source: MerkleError::ConflictingRoots { .. }, - } if note_id == note1.id() && - block_num == block2.header().block_num() + } => { + assert_eq!(note_id, note2.id()); + assert_eq!(block_num, block1.header().block_num()); + } ); // Case 2: Error: The block referenced by the (valid) note inclusion proof is missing. // -------------------------------------------------------------------------------------------- - // Make a clone of the partial blockchain where block2 is missing. + // Make a clone of the partial blockchain where block1 is missing. let mut mmr = partial_blockchain.mmr().clone(); - mmr.untrack(block2.header().block_num().as_usize()); + mmr.untrack(block1.header().block_num().as_usize()); let blocks = partial_blockchain .block_headers() - .filter(|header| header.block_num() != block2.header().block_num()) + .filter(|header| header.block_num() != block1.header().block_num()) .cloned(); let error = ProposedBatch::new( [tx1.clone()].into_iter().map(Arc::new).collect(), - block4.header().clone(), + block3.header().clone(), PartialBlockchain::new(mmr, blocks) .context("failed to build partial blockchain with missing block")?, - BTreeMap::from_iter([(input_note1.id(), note_inclusion_proof1.clone())]), + BTreeMap::from_iter([(input_note2.id(), note_inclusion_proof2.clone())]), ) .unwrap_err(); @@ -336,8 +366,10 @@ fn unauthenticated_note_converted_to_authenticated() -> anyhow::Result<()> { ProposedBatchError::UnauthenticatedInputNoteBlockNotInPartialBlockchain { block_number, note_id - } if block_number == note_inclusion_proof1.location().block_num() && - note_id == input_note1.id() + } => { + assert_eq!(block_number, note_inclusion_proof2.location().block_num()); + assert_eq!(note_id, input_note2.id()); + } ); // Case 3: Success: The correct proof is passed. @@ -345,9 +377,9 @@ fn unauthenticated_note_converted_to_authenticated() -> anyhow::Result<()> { let batch = ProposedBatch::new( [tx1].into_iter().map(Arc::new).collect(), - block4.header().clone(), + block3.header().clone(), partial_blockchain, - BTreeMap::from_iter([(input_note1.id(), note_inclusion_proof1.clone())]), + BTreeMap::from_iter([(input_note2.id(), note_inclusion_proof2.clone())]), )?; // We expect the unauthenticated input note to have become an authenticated one, @@ -357,7 +389,7 @@ fn unauthenticated_note_converted_to_authenticated() -> anyhow::Result<()> { batch .input_notes() .iter() - .any(|commitment| commitment == &InputNoteCommitment::from(&input_note1)) + .any(|commitment| commitment == &InputNoteCommitment::from(&input_note2)) ); assert_eq!(batch.output_notes().len(), 0); @@ -376,8 +408,7 @@ fn unauthenticated_note_converted_to_authenticated() -> anyhow::Result<()> { /// attack vector. #[test] fn authenticated_note_created_in_same_batch() -> anyhow::Result<()> { - let TestSetup { mut chain, account1, account2 } = setup_chain(); - let note = chain.add_pending_p2id_note(account1.id(), account2.id(), &[], NoteType::Public)?; + let TestSetup { mut chain, account1, account2, note1 } = setup_chain(); let block1 = chain.block_header(1); let block2 = chain.prove_next_block()?; @@ -390,7 +421,7 @@ fn authenticated_note_created_in_same_batch() -> anyhow::Result<()> { let tx2 = MockProvenTxBuilder::with_account(account2.id(), Word::empty(), account2.commitment()) .ref_block_commitment(block1.commitment()) - .authenticated_notes(vec![note.clone()]) + .authenticated_notes(vec![note1.clone()]) .build()?; let batch = ProposedBatch::new( @@ -481,7 +512,7 @@ fn multiple_transactions_against_same_account() -> anyhow::Result<()> { /// - The output note commitment is sorted by [`NoteId`]. #[test] fn input_and_output_notes_commitment() -> anyhow::Result<()> { - let TestSetup { chain, account1, account2 } = setup_chain(); + let TestSetup { chain, account1, account2, .. } = setup_chain(); let block1 = chain.block_header(1); let note0 = mock_output_note(50); @@ -536,7 +567,7 @@ fn input_and_output_notes_commitment() -> anyhow::Result<()> { /// Tests that the expiration block number of a batch is the minimum of all contained transactions. #[test] fn batch_expiration() -> anyhow::Result<()> { - let TestSetup { chain, account1, account2 } = setup_chain(); + let TestSetup { chain, account1, account2, .. } = setup_chain(); let block1 = chain.block_header(1); let tx1 = @@ -594,7 +625,7 @@ fn duplicate_transaction() -> anyhow::Result<()> { /// TX 2: Inputs [Y] -> Outputs [X] #[test] fn circular_note_dependency() -> anyhow::Result<()> { - let TestSetup { chain, account1, account2 } = setup_chain(); + let TestSetup { chain, account1, account2, .. } = setup_chain(); let block1 = chain.block_header(1); let note_x = mock_note(20); @@ -629,7 +660,7 @@ fn circular_note_dependency() -> anyhow::Result<()> { /// Tests that expired transactions cannot be included in a batch. #[test] fn expired_transaction() -> anyhow::Result<()> { - let TestSetup { chain, account1, account2 } = setup_chain(); + let TestSetup { chain, account1, account2, .. } = setup_chain(); let block1 = chain.block_header(1); // This transaction expired at the batch's reference block. diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs index 98ac48c8e6..ee1bcbd2b3 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs @@ -281,8 +281,8 @@ fn proposed_block_fails_on_duplicate_output_note() -> anyhow::Result<()> { // Create two different notes that will create the same output note. Their IDs will be different // due to having a different serial number generated from contained RNG. - let note0 = create_spawn_note(account.id(), vec![&output_note])?; - let note1 = create_spawn_note(account.id(), vec![&output_note])?; + let note0 = create_spawn_note([&output_note])?; + let note1 = create_spawn_note([&output_note])?; chain.add_pending_note(OutputNote::Full(note0.clone())); chain.add_pending_note(OutputNote::Full(note1.clone())); diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs index 1a21339987..024096f7e5 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs @@ -49,10 +49,10 @@ fn proven_block_success() -> anyhow::Result<()> { let output_note2 = generate_output_note(account2.id(), [2; 32]); let output_note3 = generate_output_note(account3.id(), [3; 32]); - let input_note0 = create_spawn_note(account0.id(), vec![&output_note0])?; - let input_note1 = create_spawn_note(account1.id(), vec![&output_note1])?; - let input_note2 = create_spawn_note(account2.id(), vec![&output_note2])?; - let input_note3 = create_spawn_note(account3.id(), vec![&output_note3])?; + let input_note0 = create_spawn_note([&output_note0])?; + let input_note1 = create_spawn_note([&output_note1])?; + let input_note2 = create_spawn_note([&output_note2])?; + let input_note3 = create_spawn_note([&output_note3])?; // Add input notes to chain so we can consume them. chain.add_pending_note(OutputNote::Full(input_note0.clone())); @@ -220,9 +220,9 @@ fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { let output_note3 = generate_output_note(account3.id(), rng.random()); // Create notes that, when consumed, will create the above corresponding output notes. - let note0 = create_spawn_note(account0.id(), vec![&output_note0])?; - let note2 = create_spawn_note(account2.id(), vec![&output_note2])?; - let note3 = create_spawn_note(account3.id(), vec![&output_note3])?; + let note0 = create_spawn_note([&output_note0])?; + let note2 = create_spawn_note([&output_note2])?; + let note3 = create_spawn_note([&output_note3])?; // Add note{0,2,3} to the chain so we can consume them. chain.add_pending_note(OutputNote::Full(note0.clone())); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index 357b5e0c05..e1c3db55aa 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -56,8 +56,7 @@ use crate::{Auth, MockChain, TransactionContextBuilder}; fn empty_account_delta_commitment_is_empty_word() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::Noop)?; - let p2any_note = - builder.add_p2any_note(AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(), &[])?; + let p2any_note = builder.add_p2any_note(AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(), [])?; let mock_chain = builder.build()?; let executed_tx = mock_chain @@ -77,7 +76,7 @@ fn empty_account_delta_commitment_is_empty_word() -> anyhow::Result<()> { /// Tests that a noop transaction with [`Auth::IncrNonce`] results in a nonce delta of 1. #[test] fn delta_nonce() -> anyhow::Result<()> { - let TestSetup { mock_chain, account_id } = setup_test([], [])?; + let TestSetup { mock_chain, account_id, .. } = setup_test([], [], [])?; let executed_tx = mock_chain .build_tx_context(account_id, &[], &[]) @@ -113,7 +112,7 @@ fn storage_delta_for_value_slots() -> anyhow::Result<()> { let slot_3_tmp_value = Word::from([2, 3, 4, 5u32]); let slot_3_final_value = slot_3_init_value; - let TestSetup { mock_chain, account_id } = setup_test( + let TestSetup { mock_chain, account_id, .. } = setup_test( vec![ StorageSlot::Value(slot_0_init_value), StorageSlot::Value(slot_1_init_value), @@ -121,6 +120,7 @@ fn storage_delta_for_value_slots() -> anyhow::Result<()> { StorageSlot::Value(slot_3_init_value), ], [], + [], )?; let tx_script = compile_tx_script(format!( @@ -237,7 +237,7 @@ fn storage_delta_for_map_slots() -> anyhow::Result<()> { let mut map2 = StorageMap::new(); map2.insert(key5, key5_init_value); - let TestSetup { mock_chain, account_id } = setup_test( + let TestSetup { mock_chain, account_id, .. } = setup_test( vec![ StorageSlot::Map(map0), StorageSlot::Map(map1), @@ -248,6 +248,7 @@ fn storage_delta_for_map_slots() -> anyhow::Result<()> { StorageSlot::Map(StorageMap::new()), ], [], + [], )?; let tx_script = compile_tx_script(format!( @@ -370,25 +371,12 @@ fn fungible_asset_delta() -> anyhow::Result<()> { let removed_asset2 = FungibleAsset::new(faucet2, 100)?; let removed_asset3 = FungibleAsset::new(faucet3, FungibleAsset::MAX_AMOUNT)?; - let TestSetup { mut mock_chain, account_id } = setup_test( + let TestSetup { mock_chain, account_id, notes } = setup_test( [], [original_asset0, original_asset1, original_asset2, original_asset3].map(Asset::from), + [added_asset0, added_asset1, added_asset2, added_asset4].map(Asset::from), )?; - let mut added_notes = vec![]; - for added_asset in [added_asset0, added_asset1, added_asset2, added_asset4] { - let added_note = mock_chain - .add_pending_p2id_note( - account_id, - account_id, - &[Asset::from(added_asset)], - NoteType::Public, - ) - .context("failed to add note with asset")?; - added_notes.push(added_note); - } - mock_chain.prove_next_block()?; - let tx_script = compile_tx_script(format!( " begin @@ -409,7 +397,7 @@ fn fungible_asset_delta() -> anyhow::Result<()> { ))?; let executed_tx = mock_chain - .build_tx_context(account_id, &added_notes.iter().map(Note::id).collect::>(), &[])? + .build_tx_context(account_id, ¬es.iter().map(Note::id).collect::>(), &[])? .tx_script(tx_script) .build()? .execute_blocking() @@ -477,22 +465,8 @@ fn non_fungible_asset_delta() -> anyhow::Result<()> { let asset2 = NonFungibleAssetBuilder::new(faucet2.prefix(), &mut rng)?.build()?; let asset3 = NonFungibleAssetBuilder::new(faucet3.prefix(), &mut rng)?.build()?; - let TestSetup { mut mock_chain, account_id } = - setup_test([], [asset1, asset3].map(Asset::from))?; - - let mut added_notes = vec![]; - for added_asset in [asset0, asset2] { - let added_note = mock_chain - .add_pending_p2id_note( - account_id, - account_id, - &[Asset::from(added_asset)], - NoteType::Public, - ) - .context("failed to add note with asset")?; - added_notes.push(added_note); - } - mock_chain.prove_next_block()?; + let TestSetup { mock_chain, account_id, notes } = + setup_test([], [asset1, asset3].map(Asset::from), [asset0, asset2].map(Asset::from))?; let tx_script = compile_tx_script(format!( " @@ -516,7 +490,7 @@ fn non_fungible_asset_delta() -> anyhow::Result<()> { ))?; let executed_tx = mock_chain - .build_tx_context(account_id, &added_notes.iter().map(Note::id).collect::>(), &[])? + .build_tx_context(account_id, ¬es.iter().map(Note::id).collect::>(), &[])? .tx_script(tx_script) .build()? .execute_blocking() @@ -695,7 +669,7 @@ fn asset_and_storage_delta() -> anyhow::Result<()> { FungibleAsset::new(faucet_id_3, CONSUMED_ASSET_3_AMOUNT)?.into(); let nonfungible_asset_1: Asset = NonFungibleAsset::mock(&NON_FUNGIBLE_ASSET_DATA_2); - create_p2any_note(account.id(), &[fungible_asset_1, fungible_asset_3, nonfungible_asset_1]) + create_p2any_note(account.id(), [fungible_asset_1, fungible_asset_3, nonfungible_asset_1]) }; let tx_context = TransactionContextBuilder::new(account) @@ -798,22 +772,36 @@ fn adding_amount_zero_fungible_asset_to_account_vault_works() -> anyhow::Result< struct TestSetup { mock_chain: MockChain, account_id: AccountId, + notes: Vec, } fn setup_test( storage_slots: impl IntoIterator, - assets: impl IntoIterator, + vault_assets: impl IntoIterator, + note_assets: impl IntoIterator, ) -> anyhow::Result { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account_with_storage_and_assets( Auth::IncrNonce, storage_slots, - assets, + vault_assets, )?; + let mut notes = vec![]; + for note_asset in note_assets { + let added_note = builder + .add_p2id_note(account.id(), account.id(), &[note_asset], NoteType::Public) + .context("failed to add note with asset")?; + notes.push(added_note); + } + let mock_chain = builder.build()?; - Ok(TestSetup { mock_chain, account_id: account.id() }) + Ok(TestSetup { + mock_chain, + account_id: account.id(), + notes, + }) } fn compile_tx_script(code: impl AsRef) -> anyhow::Result { diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index 5447e03e46..b4d7423943 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -84,7 +84,7 @@ async fn check_note_consumability_well_known_notes_success() -> anyhow::Result<( } #[rstest::rstest] -#[case::one(vec![create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(100)])])] +#[case::one(vec![create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)])])] #[tokio::test] async fn check_note_consumability_custom_notes_success( #[case] notes: Vec, diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index e237183072..b3dfc454ae 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -54,12 +54,12 @@ fn test_epilogue() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let tx_context = { let output_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(100)]); + create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); // input_note_1 is needed for maintaining cohesion of involved assets let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(100)]); - let input_note_2 = create_spawn_note(ACCOUNT_ID_SENDER.try_into()?, vec![&output_note_1])?; + create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); + let input_note_2 = create_spawn_note([&output_note_1])?; TransactionContextBuilder::new(account.clone()) .extend_input_notes(vec![input_note_1, input_note_2]) .extend_expected_output_notes(vec![OutputNote::Full(output_note_1)]) @@ -153,12 +153,12 @@ fn test_compute_output_note_id() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let output_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, &[FungibleAsset::mock(100)]); + create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); // input_note_1 is needed for maintaining cohesion of involved assets let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, &[FungibleAsset::mock(100)]); - let input_note_2 = create_spawn_note(ACCOUNT_ID_SENDER.try_into()?, vec![&output_note_1])?; + create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); + let input_note_2 = create_spawn_note([&output_note_1])?; TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note_1, input_note_2]) .extend_expected_output_notes(vec![OutputNote::Full(output_note_1)]) @@ -235,7 +235,7 @@ fn test_epilogue_asset_preservation_violation_too_few_input() -> anyhow::Result< .dynamically_linked_libraries(TransactionKernel::mock_libraries()) .build()?; - let input_note = create_spawn_note(account.id(), vec![&output_note_1, &output_note_2])?; + let input_note = create_spawn_note([&output_note_1, &output_note_2])?; let tx_context = mock_chain .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[input_note])? @@ -309,10 +309,7 @@ fn test_epilogue_asset_preservation_violation_too_many_fungible_input() -> anyho .dynamically_linked_libraries(TransactionKernel::mock_libraries()) .build()?; - let input_note = create_spawn_note( - ACCOUNT_ID_SENDER.try_into()?, - vec![&output_note_1, &output_note_2, &output_note_3], - )?; + let input_note = create_spawn_note([&output_note_1, &output_note_2, &output_note_3])?; let tx_context = mock_chain .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[input_note])? diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index f14e4643e7..75f3dddf63 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -338,7 +338,7 @@ fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { ); let note = create_p2any_note( ACCOUNT_ID_SENDER.try_into().unwrap(), - &[FungibleAsset::new(account.id(), 100u64).unwrap().into()], + [FungibleAsset::new(account.id(), 100u64).unwrap().into()], ); TransactionContextBuilder::new(account).extend_input_notes(vec![note]).build()? }; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fee.rs b/crates/miden-testing/src/kernel_tests/tx/test_fee.rs index 888f7bdcd2..63fbd83bdd 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fee.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fee.rs @@ -1,6 +1,5 @@ use anyhow::Context; use assert_matches::assert_matches; -use miden_lib::testing::note::NoteBuilder; use miden_objects::account::{AccountId, StorageMap, StorageSlot}; use miden_objects::asset::{Asset, FungibleAsset, NonFungibleAsset}; use miden_objects::note::NoteType; @@ -166,18 +165,14 @@ fn create_output_notes() -> anyhow::Result { let note_asset0 = FungibleAsset::mock(200).unwrap_fungible(); let note_asset1 = FungibleAsset::mock(500).unwrap_fungible(); - // This creates a note that adds the given assets to the transaction vault without moving them - // to the account. This is needed to preserve the overall transaction asset rules, since the - // SPAWN note does not remove the assets from the account vault. - let asset_note = NoteBuilder::new(account.id(), &mut rand::rng()) - .add_assets([Asset::from(note_asset0.add(note_asset1)?)]) - .build()?; + // This creates a note that adds the given assets to the account vault. + let asset_note = create_p2any_note(account.id(), [Asset::from(note_asset0.add(note_asset1)?)]); builder.add_note(OutputNote::Full(asset_note.clone())); - let output_note0 = create_p2any_note(account.id(), &[note_asset0.into()]); - let output_note1 = create_p2any_note(account.id(), &[note_asset1.into()]); + let output_note0 = create_p2any_note(account.id(), [note_asset0.into()]); + let output_note1 = create_p2any_note(account.id(), [note_asset1.into()]); - let spawn_note = builder.add_spawn_note(account.id(), [&output_note0, &output_note1])?; + let spawn_note = builder.add_spawn_note([&output_note0, &output_note1])?; builder .build()? .build_tx_context(account, &[asset_note.id(), spawn_note.id()], &[])? diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index d3737ede3d..aa80b67a31 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -98,7 +98,7 @@ fn test_get_sender() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let input_note = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(100)]); + create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note]) .build()? diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index e991dfc68b..ded557fe01 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -111,11 +111,11 @@ fn test_transaction_prologue() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(100)]); + create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); let input_note_2 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(100)]); + create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); let input_note_3 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(111)]); + create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(111)]); TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note_1, input_note_2, input_note_3]) .build()? diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 42f4d028dd..950356a88d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -130,25 +130,31 @@ fn transaction_with_stale_foreign_account_inputs_fails() -> anyhow::Result<()> { async fn consuming_note_created_in_future_block_fails() -> anyhow::Result<()> { // Create a chain with an account let mut builder = MockChain::builder(); - let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let asset = FungibleAsset::mock(400); + let account1 = builder.add_existing_wallet_with_assets(Auth::BasicAuth, [asset])?; + let account2 = builder.add_existing_wallet_with_assets(Auth::BasicAuth, [asset])?; + let output_note = create_p2any_note(account1.id(), [asset]); + let spawn_note = builder.add_spawn_note([&output_note])?; let mut mock_chain = builder.build()?; mock_chain.prove_until_block(10u32)?; - // Create a note that will be contained in block 11. - let note = mock_chain - .add_pending_p2id_note( - account.id(), - ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(), - &[], - NoteType::Private, - ) - .unwrap(); + // Consume the spawn note which creates a note for account 2 to consume. It will be contained in + // block 11. We use account 1 for this, so that account 2 remains unchanged and is still valid + // against reference block 1 which we'll use for the later transaction. + let tx = mock_chain + .build_tx_context(account1.id(), &[spawn_note.id()], &[])? + .extend_expected_output_notes(vec![OutputNote::Full(output_note.clone())]) + .build()? + .execute() + .await?; + + // Add the transaction to the mock chain's mempool so it will be included in the next block. + mock_chain.add_pending_executed_transaction(&tx)?; // Create block 11. mock_chain.prove_next_block()?; - // Get as input note, and assert that the note was created after block 1 (which we'll - // use as reference) - let input_note = mock_chain.get_public_note(¬e.id()).expect("note not found"); + // Get the input note and assert that the note was created after block 11. + let input_note = mock_chain.get_public_note(&output_note.id()).expect("note not found"); assert_eq!(input_note.location().unwrap().block_num().as_u32(), 11); mock_chain.prove_next_block()?; @@ -156,7 +162,7 @@ async fn consuming_note_created_in_future_block_fails() -> anyhow::Result<()> { // Attempt to execute a transaction against reference block 1 with the note created in block 11 // - which should fail. - let tx_context = mock_chain.build_tx_context(account.id(), &[], &[])?.build()?; + let tx_context = mock_chain.build_tx_context(account2.id(), &[], &[])?.build()?; let tx_executor = TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context) .with_source_manager(tx_context.source_manager()); @@ -164,7 +170,7 @@ async fn consuming_note_created_in_future_block_fails() -> anyhow::Result<()> { // Try to execute with block_ref==1 let error = tx_executor .execute_transaction( - account.id(), + account2.id(), BlockNumber::from(1), InputNotes::new(vec![input_note]).unwrap(), TransactionArgs::default(), @@ -341,13 +347,13 @@ fn test_get_output_notes_commitment() -> anyhow::Result<()> { Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let output_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, &[FungibleAsset::mock(100)]); + create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); let input_note_1 = - create_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, &[FungibleAsset::mock(100)]); + create_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [FungibleAsset::mock(100)]); let input_note_2 = - create_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, &[FungibleAsset::mock(200)]); + create_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [FungibleAsset::mock(200)]); TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note_1, input_note_2]) @@ -737,7 +743,7 @@ fn creating_note_with_fungible_asset_amount_zero_works() -> anyhow::Result<()> { &[FungibleAsset::mock(0)], NoteType::Private, )?; - let input_note = builder.add_spawn_note(account.id(), [&output_note])?; + let input_note = builder.add_spawn_note([&output_note])?; let chain = builder.build()?; chain @@ -755,7 +761,7 @@ fn test_build_recipient_hash() -> anyhow::Result<()> { Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), &[FungibleAsset::mock(100)]); + create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note_1]) .build()? @@ -1173,7 +1179,7 @@ fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { Felt::ZERO, &mut rng, )?; - let input_note = create_spawn_note(account.id(), vec![&output_note])?; + let input_note = create_spawn_note(vec![&output_note])?; let mut mock_chain = MockChain::new(); @@ -1217,7 +1223,7 @@ fn tx_summary_commitment_is_signed_by_falcon_auth() -> anyhow::Result<()> { Felt::ZERO, &mut rng, )?; - let spawn_note = builder.add_spawn_note(account.id(), [&p2id_note])?; + let spawn_note = builder.add_spawn_note([&p2id_note])?; let chain = builder.build()?; let tx = chain diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index bb194a5712..55be26b269 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -4,10 +4,8 @@ use alloc::vec::Vec; use anyhow::Context; use miden_block_prover::{LocalBlockProver, ProvenBlockError}; -use miden_lib::note::{create_p2id_note, create_p2ide_note}; use miden_objects::account::delta::AccountUpdateDetails; use miden_objects::account::{Account, AccountId, AuthSecretKey, PartialAccount, StorageSlot}; -use miden_objects::asset::Asset; use miden_objects::batch::{ProposedBatch, ProvenBatch}; use miden_objects::block::{ AccountTree, @@ -22,7 +20,7 @@ use miden_objects::block::{ ProvenBlock, }; use miden_objects::crypto::merkle::SmtProof; -use miden_objects::note::{Note, NoteHeader, NoteId, NoteInclusionProof, NoteType, Nullifier}; +use miden_objects::note::{Note, NoteHeader, NoteId, NoteInclusionProof, Nullifier}; use miden_objects::transaction::{ AccountInputs, ExecutedTransaction, @@ -33,8 +31,7 @@ use miden_objects::transaction::{ ProvenTransaction, TransactionInputs, }; -use miden_objects::{MAX_BATCHES_PER_BLOCK, MAX_OUTPUT_NOTES_PER_BATCH, NoteError}; -use miden_processor::crypto::RpoRandomCoin; +use miden_objects::{MAX_BATCHES_PER_BLOCK, MAX_OUTPUT_NOTES_PER_BATCH}; use miden_processor::{DeserializationError, Word}; use miden_tx::LocalTransactionProver; use miden_tx::auth::BasicAuthenticator; @@ -58,18 +55,11 @@ use crate::{MockChainBuilder, TransactionContextBuilder}; /// note creation in a test setting. Once entities are set up, [`TransactionContextBuilder`] objects /// can be obtained in order to execute transactions accordingly. /// -/// On a high-level, there are two ways to interact with the mock chain: -/// - Generating transactions yourself and adding them to the mock chain "mempool" using -/// [`MockChain::add_pending_executed_transaction`] or -/// [`MockChain::add_pending_proven_transaction`]. Once some transactions have been added, they -/// can be proven into a block using [`MockChain::prove_next_block`], which commits them to the -/// chain state. -/// - Using any of the other pending APIs to _magically_ add new notes, accounts or nullifiers in -/// the next block. For example, [`MockChain::add_pending_p2id_note`] will create a new P2ID note -/// in the next proven block, without actually containing a transaction that creates that note. -/// -/// Both approaches can be mixed in the same block, within limits. In particular, avoid modification -/// of the _same_ entities using both regular transactions and the magic pending APIs. +/// The primary way to interact with the mock chain is by generating transactions yourself and +/// adding them to the mock chain "mempool" using [`MockChain::add_pending_executed_transaction`] +/// or [`MockChain::add_pending_proven_transaction`]. Once some transactions have been added, they +/// can be proven into a block using [`MockChain::prove_next_block`], which commits them to the +/// chain state. /// /// The mock chain uses the batch and block provers underneath to process pending transactions, so /// the generated blocks are realistic and indistinguishable from a real node. The only caveat is @@ -889,63 +879,6 @@ impl MockChain { self.pending_output_notes.push(note); } - /// Adds a plain P2ID [`OutputNote`] to the list of pending notes. - /// - /// The note is immediately spendable by `target_account_id` and carries no - /// additional reclaim or timelock conditions. - pub fn add_pending_p2id_note( - &mut self, - sender_account_id: AccountId, - target_account_id: AccountId, - asset: &[Asset], - note_type: NoteType, - ) -> Result { - let mut rng = RpoRandomCoin::new(Word::empty()); - - let note = create_p2id_note( - sender_account_id, - target_account_id, - asset.to_vec(), - note_type, - Default::default(), - &mut rng, - )?; - - self.add_pending_note(OutputNote::Full(note.clone())); - Ok(note) - } - - /// Adds a P2IDE [`OutputNote`] (pay‑to‑ID‑extended) to the list of pending notes. - /// - /// A P2IDE note can include an optional `timelock_height` and/or an optional - /// `reclaim_height` after which the `sender_account_id` may reclaim the - /// funds. - pub fn add_pending_p2ide_note( - &mut self, - sender_account_id: AccountId, - target_account_id: AccountId, - asset: &[Asset], - note_type: NoteType, - reclaim_height: Option, - timelock_height: Option, - ) -> Result { - let mut rng = RpoRandomCoin::new(Word::empty()); - - let note = create_p2ide_note( - sender_account_id, - target_account_id, - asset.to_vec(), - reclaim_height, - timelock_height, - note_type, - Default::default(), - &mut rng, - )?; - - self.add_pending_note(OutputNote::Full(note.clone())); - Ok(note) - } - // PRIVATE HELPERS // ---------------------------------------------------------------------------------------- @@ -1317,7 +1250,8 @@ impl From for TxContextInput { mod tests { use miden_lib::account::wallets::BasicWallet; use miden_objects::account::{AccountBuilder, AccountStorageMode}; - use miden_objects::asset::FungibleAsset; + use miden_objects::asset::{Asset, FungibleAsset}; + use miden_objects::note::NoteType; use miden_objects::testing::account_id::{ ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 6626352d69..02940fc5bd 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -414,7 +414,7 @@ impl MockChainBuilder { pub fn add_p2any_note( &mut self, sender_account_id: AccountId, - asset: &[Asset], + asset: impl IntoIterator, ) -> anyhow::Result { let note = create_p2any_note(sender_account_id, asset); @@ -507,14 +507,20 @@ impl MockChainBuilder { /// /// A `SPAWN` note contains a note script that creates all `output_notes` that get passed as a /// parameter. - pub fn add_spawn_note<'note>( + /// + /// # Errors + /// + /// Returns an error if: + /// - the sender account ID of the provided output notes is not consistent or does not match the + /// transaction's sender. + pub fn add_spawn_note<'note, I>( &mut self, - sender_id: AccountId, - output_notes: impl IntoIterator, - ) -> anyhow::Result { - let output_notes = output_notes.into_iter().collect(); - let note = create_spawn_note(sender_id, output_notes)?; - + output_notes: impl IntoIterator, + ) -> anyhow::Result + where + I: ExactSizeIterator, + { + let note = create_spawn_note(output_notes)?; self.add_note(OutputNote::Full(note.clone())); Ok(note) diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 914d1d3cac..4c8270e2a5 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -79,7 +79,8 @@ pub fn input_note_data_ptr(note_idx: u32) -> memory::MemoryAddress { /// vault. /// /// The created note does not require authentication and can be consumed by any account. -pub fn create_p2any_note(sender: AccountId, assets: &[Asset]) -> Note { +pub fn create_p2any_note(sender: AccountId, assets: impl IntoIterator) -> Note { + let assets: Vec<_> = assets.into_iter().collect(); let mut code_body = String::new(); for i in 0..assets.len() { if i == 0 { @@ -142,8 +143,30 @@ pub fn create_p2any_note(sender: AccountId, assets: &[Asset]) -> Note { /// /// A `SPAWN` note contains a note script that creates all `output_notes` that get passed as a /// parameter. -pub fn create_spawn_note(sender_id: AccountId, output_notes: Vec<&Note>) -> anyhow::Result { - let note_code = note_script_that_creates_notes(output_notes); +/// +/// # Errors +/// +/// Returns an error if: +/// - the sender account ID of the provided output notes is not consistent or does not match the +/// transaction's sender. +pub fn create_spawn_note<'note, I>( + output_notes: impl IntoIterator, +) -> anyhow::Result +where + I: ExactSizeIterator, +{ + let mut output_notes = output_notes.into_iter().peekable(); + if output_notes.len() == 0 { + anyhow::bail!("at least one output note is needed to create a SPAWN note"); + } + + let sender_id = output_notes + .peek() + .expect("at least one output note should be present") + .metadata() + .sender(); + + let note_code = note_script_that_creates_notes(sender_id, output_notes)?; let note = NoteBuilder::new(sender_id, SmallRng::from_os_rng()) .code(note_code) @@ -154,23 +177,44 @@ pub fn create_spawn_note(sender_id: AccountId, output_notes: Vec<&Note>) -> anyh } /// Returns the code for a note that creates all notes in `output_notes` -fn note_script_that_creates_notes(output_notes: Vec<&Note>) -> String { +fn note_script_that_creates_notes<'note>( + sender_id: AccountId, + output_notes: impl Iterator, +) -> anyhow::Result { let mut out = String::from("use.miden::tx\nuse.mock::account\n\nbegin\n"); - for (idx, note) in output_notes.iter().enumerate() { + for (idx, note) in output_notes.into_iter().enumerate() { + anyhow::ensure!( + note.metadata().sender() == sender_id, + "sender IDs of output notes passed to SPAWN note are inconsistent" + ); + + // Make sure that the transaction's native account matches the note sender. + out.push_str(&format!( + r#"exec.::miden::account::get_native_id + # => [native_account_id_prefix, native_account_id_suffix] + push.{sender_prefix} assert_eq.err="sender ID prefix does not match native account ID's prefix" + # => [native_account_id_suffix] + push.{sender_suffix} assert_eq.err="sender ID suffix does not match native account ID's suffix" + # => [] + "#, + sender_prefix = sender_id.prefix().as_felt(), + sender_suffix = sender_id.suffix() + )); + if idx == 0 { out.push_str("padw padw\n"); } else { out.push_str("dropw dropw dropw\n"); } - let assets_str = prepare_assets(note.assets()); out.push_str(&format!( - " push.{recipient} - push.{hint} - push.{note_type} - push.{aux} - push.{tag} - call.tx::create_note\n", + " + push.{recipient} + push.{hint} + push.{note_type} + push.{aux} + push.{tag} + call.tx::create_note\n", recipient = note.recipient().digest(), hint = Felt::from(note.metadata().execution_hint()), note_type = note.metadata().note_type() as u8, @@ -178,14 +222,16 @@ fn note_script_that_creates_notes(output_notes: Vec<&Note>) -> String { tag = note.metadata().tag(), )); + let assets_str = prepare_assets(note.assets()); for asset in assets_str { out.push_str(&format!( " push.{asset} - call.tx::add_asset_to_note\n", + call.::miden::contracts::wallets::basic::move_asset_to_note\n", )); } } out.push_str("repeat.5 dropw end\nend"); - out + + Ok(out) } diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index 724450495a..c065881040 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -115,7 +115,7 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { )?; // Create spawn note that will create the output note - let input_note = mock_chain_builder.add_spawn_note(multisig_account.id(), [&output_note])?; + let input_note = mock_chain_builder.add_spawn_note([&output_note])?; let mut mock_chain = mock_chain_builder.build().unwrap(); From 2f18f6e568a7ce0de7ddd1cd9ba22c30ea601e68 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 10 Sep 2025 09:38:54 +0200 Subject: [PATCH 033/133] feat: Load storage map items lazily (#1857) * feat: Add `StorageMapWitness` * feat: Implement lazy storage map witness loading * feat: Test `set_map_item` with lazy loading * feat: Test `get_map_item` with lazy loading * fix: error message and iterator type * chore: Return slice instead of reference to `Vec` * chore: Use has_merkle_path for lazy asset loading * chore: add changelog * chore: API quality improvements * fix: storage map docs * chore: update changelog --- CHANGELOG.md | 6 +- .../asm/kernels/transaction/lib/account.masm | 24 ++- crates/miden-lib/src/transaction/events.rs | 8 + .../src/transaction/kernel_procedures.rs | 10 +- crates/miden-objects/src/account/mod.rs | 1 + .../src/account/storage/map/mod.rs | 26 ++- .../src/account/storage/map/partial.rs | 8 +- .../src/account/storage/map/witness.rs | 50 ++++++ .../miden-objects/src/account/storage/mod.rs | 4 +- .../src/account/storage/partial.rs | 4 +- .../src/account/storage/slot/type.rs | 10 ++ crates/miden-objects/src/asset/vault/mod.rs | 1 + .../src/kernel_tests/tx/test_lazy_loading.rs | 129 +++++++++++++- crates/miden-testing/src/mock_chain/chain.rs | 12 +- .../miden-testing/src/tx_context/builder.rs | 34 +++- .../miden-testing/src/tx_context/context.rs | 37 +++- crates/miden-tx/src/errors/mod.rs | 10 ++ crates/miden-tx/src/executor/data_store.rs | 17 +- crates/miden-tx/src/executor/exec_host.rs | 77 +++++++- crates/miden-tx/src/host/mod.rs | 167 +++++++++++++++--- crates/miden-tx/src/prover/prover_host.rs | 1 + 21 files changed, 563 insertions(+), 73 deletions(-) create mode 100644 crates/miden-objects/src/account/storage/map/witness.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 66b54288b0..16d19c6fed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,12 @@ - Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). - Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). +- [BREAKING] Enabled lazy loading of storage map entries during transaction execution ([#1857](https://github.com/0xMiden/miden-base/pull/1857)). +- Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). - Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). - Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). -- Enable lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). -- Lazy load the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). +- Enabled lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). +- Added lazy loading of the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). - Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). ### Changes diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 704050c025..2d06ac6078 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -137,6 +137,9 @@ const.ACCOUNT_STORAGE_BEFORE_SET_ITEM_EVENT=131076 # Event emitted after an account storage item is updated. const.ACCOUNT_STORAGE_AFTER_SET_ITEM_EVENT=131077 +# Event emitted before an account storage map item is accessed. +const.ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT=131103 + # Event emitted before an account storage map item is updated. const.ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM_EVENT=131078 # Event emitted after an account storage map item is updated. @@ -533,20 +536,29 @@ export.get_map_item assert.err=ERR_ACCOUNT_READING_MAP_VALUE_FROM_NON_MAP_SLOT # => [index, KEY] + dup movdn.5 + # => [index, KEY, index] + # fetch the account storage item, which is ROOT of the map exec.get_item swapw - # => [KEY, ROOT] + # => [KEY, ROOT, index] + + emit.ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT + # => [KEY, ROOT, index] # see hash_map_key's docs for why this is done exec.hash_map_key - # => [HASHED_KEY, ROOT] + # => [HASHED_KEY, ROOT, index] # fetch the VALUE located under HASHED_KEY in the tree exec.smt::get - # => [VALUE, ROOT] + # => [VALUE, ROOT, index] # remove the ROOT from the stack swapw dropw + # => [VALUE, index] + + movup.4 drop # => [VALUE] end @@ -576,6 +588,9 @@ export.set_map_item.12 movdnw.2 loc_load.0 # => [index, KEY, NEW_VALUE, OLD_ROOT, ...] + emit.ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM_EVENT + # => [index, KEY, NEW_VALUE, OLD_ROOT, ...] + # check if storage type is map exec.get_storage_slot_type # => [slot_type, KEY, NEW_VALUE, OLD_ROOT] @@ -585,9 +600,6 @@ export.set_map_item.12 assert.err=ERR_ACCOUNT_SETTING_MAP_ITEM_ON_NON_MAP_SLOT # => [KEY, NEW_VALUE, OLD_ROOT] - emit.ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM_EVENT - # => [KEY, NEW_VALUE, OLD_ROOT] - # duplicate the original KEY and the NEW_VALUE to be able to emit an event after the # account storage item was updated movupw.2 dupw.2 dupw.2 diff --git a/crates/miden-lib/src/transaction/events.rs b/crates/miden-lib/src/transaction/events.rs index 4909a7bfc8..53030e7665 100644 --- a/crates/miden-lib/src/transaction/events.rs +++ b/crates/miden-lib/src/transaction/events.rs @@ -17,6 +17,8 @@ const ACCOUNT_VAULT_AFTER_REMOVE_ASSET: u32 = 0x2_0003; // 131075 const ACCOUNT_STORAGE_BEFORE_SET_ITEM: u32 = 0x2_0004; // 131076 const ACCOUNT_STORAGE_AFTER_SET_ITEM: u32 = 0x2_0005; // 131077 +const ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT: u32 = 0x2_001f; // 131103 + const ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM: u32 = 0x2_0006; // 131078 const ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM: u32 = 0x2_0007; // 131079 @@ -74,6 +76,8 @@ pub enum TransactionEvent { AccountStorageBeforeSetItem = ACCOUNT_STORAGE_BEFORE_SET_ITEM, AccountStorageAfterSetItem = ACCOUNT_STORAGE_AFTER_SET_ITEM, + AccountStorageBeforeGetMapItem = ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT, + AccountStorageBeforeSetMapItem = ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM, AccountStorageAfterSetMapItem = ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM, @@ -151,6 +155,10 @@ impl TryFrom for TransactionEvent { ACCOUNT_STORAGE_BEFORE_SET_ITEM => Ok(TransactionEvent::AccountStorageBeforeSetItem), ACCOUNT_STORAGE_AFTER_SET_ITEM => Ok(TransactionEvent::AccountStorageAfterSetItem), + ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT => { + Ok(TransactionEvent::AccountStorageBeforeGetMapItem) + }, + ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM => { Ok(TransactionEvent::AccountStorageBeforeSetMapItem) }, diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index a1d483ecc7..7154420b07 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -32,9 +32,9 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ // account_set_item word!("0xd2232daa3895669f2bb34af764504d72432fb119eb0be0ce07481290c8701af8"), // account_get_map_item - word!("0x5a2678ee09bf93cb284b4539345c8eb601614fa60d75259d6502ca19a4acc921"), + word!("0x061afed82416f597aa87e2de48d7dad96a40c5f3e2c839d6522bafd3646414ca"), // account_set_map_item - word!("0x9fff2a2b67d045ec5baa1db7f63a1f1670d6fc0015a1dd2cfb47d040c25ca55e"), + word!("0xb539c160a862fedb3a4bd81bcea2076adc54c4e064a2c74b8770cef778ec5c59"), // account_get_initial_vault_root word!("0x4d4d91079aaacad1bc86b29a0d61d25508ccb705c29d1b1357016f7373bf299e"), // account_get_vault_root @@ -52,13 +52,13 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ // account_was_procedure_called word!("0x84c8c518a005605619909976ce54c41d6a88505e815421ff4b5516d0285b28bf"), // faucet_mint_asset - word!("0x68ba9ead0f07cd4012c8a4282bab32b12d74ed5443eb734541166f6fe0b063a8"), + word!("0x80876450b0bcff4534e17cd850c75bcfdff4cc0b00703465f713d350001cce79"), // faucet_burn_asset - word!("0x9c44cd37081368b335b2c3f9ab4629c57bd13d12a38df75ab87a42d9d72886f9"), + word!("0xa78c7ae04e75f6e447fa72df757dfb0854a3236b86ece2b5478a757b3c156ad5"), // faucet_get_total_fungible_asset_issuance word!("0x7d32952d4dc0edd0311e3424b8128df2d48cf949f800c28218fbc851a8db42b5"), // faucet_is_non_fungible_asset_issued - word!("0xc827d2430763c70880216804d3523c14e70e5cf926d5d8a72844c6476add5ef7"), + word!("0xe0d7571e530b703ededf549f5ca8c57c5cffe0d1d9f59da20e5db05ca18de58b"), // input_note_get_metadata word!("0x7ad3e94585e7a397ee27443c98b376ed8d4ba762122af6413fde9314c00a6219"), // input_note_get_assets_info diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index 276bdff713..0c9035f846 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -66,6 +66,7 @@ pub use storage::{ PartialStorage, PartialStorageMap, StorageMap, + StorageMapWitness, StorageSlot, StorageSlotType, }; diff --git a/crates/miden-objects/src/account/storage/map/mod.rs b/crates/miden-objects/src/account/storage/map/mod.rs index 529ae29e25..166adcc7d2 100644 --- a/crates/miden-objects/src/account/storage/map/mod.rs +++ b/crates/miden-objects/src/account/storage/map/mod.rs @@ -4,21 +4,24 @@ use miden_core::EMPTY_WORD; use miden_crypto::merkle::EmptySubtreeRoots; use super::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, Word}; -use crate::Hasher; use crate::account::StorageMapDelta; -use crate::crypto::merkle::{InnerNodeInfo, LeafIndex, SMT_DEPTH, Smt, SmtLeaf, SmtProof}; +use crate::crypto::merkle::{InnerNodeInfo, LeafIndex, SMT_DEPTH, Smt, SmtLeaf}; use crate::errors::StorageMapError; +use crate::{Felt, Hasher}; mod partial; pub use partial::PartialStorageMap; +mod witness; +pub use witness::StorageMapWitness; + // ACCOUNT STORAGE MAP // ================================================================================================ /// Empty storage map root. -pub const EMPTY_STORAGE_MAP_ROOT: Word = *EmptySubtreeRoots::entry(StorageMap::TREE_DEPTH, 0); +pub const EMPTY_STORAGE_MAP_ROOT: Word = *EmptySubtreeRoots::entry(StorageMap::DEPTH, 0); -/// An account storage map is a sparse merkle tree of depth [`Self::TREE_DEPTH`] (64). +/// An account storage map is a sparse merkle tree of depth [`Self::DEPTH`]. /// /// It can be used to store a large amount of data in an account than would be otherwise possible /// using just the account's storage slots. This works by storing the root of the map's underlying @@ -50,8 +53,8 @@ impl StorageMap { // CONSTANTS // -------------------------------------------------------------------------------------------- - /// Depth of the storage tree. - pub const TREE_DEPTH: u8 = SMT_DEPTH; + /// The depth of the SMT that represents the storage map. + pub const DEPTH: u8 = SMT_DEPTH; /// The default value of empty leaves. pub const EMPTY_VALUE: Word = Smt::EMPTY_VALUE; @@ -116,9 +119,9 @@ impl StorageMap { /// Returns an opening of the leaf associated with `key`. /// /// Conceptually, an opening is a Merkle path to the leaf, as well as the leaf itself. - pub fn open(&self, key: &Word) -> SmtProof { + pub fn open(&self, key: &Word) -> StorageMapWitness { let key = Self::hash_key(*key); - self.smt.open(&key) + StorageMapWitness::new(self.smt.open(&key)) } // ITERATORS @@ -176,6 +179,13 @@ impl StorageMap { pub fn hash_key(key: Word) -> Word { Hasher::hash_elements(key.as_elements()) } + + // TODO: Replace with https://github.com/0xMiden/crypto/issues/515 once implemented. + /// Returns the leaf index of a map key. + pub fn hashed_map_key_to_leaf_index(hashed_map_key: Word) -> Felt { + // The third element in an SMT key is the index. + hashed_map_key[3] + } } impl Default for StorageMap { diff --git a/crates/miden-objects/src/account/storage/map/partial.rs b/crates/miden-objects/src/account/storage/map/partial.rs index 6a02438f41..24b9aeca67 100644 --- a/crates/miden-objects/src/account/storage/map/partial.rs +++ b/crates/miden-objects/src/account/storage/map/partial.rs @@ -10,7 +10,7 @@ use miden_crypto::merkle::{ SmtProof, }; -use crate::account::StorageMap; +use crate::account::{StorageMap, StorageMapWitness}; /// A partial representation of a [`StorageMap`], containing only proofs for a subset of the /// key-value pairs. @@ -76,9 +76,9 @@ impl PartialStorageMap { // MUTATORS // -------------------------------------------------------------------------------------------- - /// Adds an [`SmtProof`] to this [`PartialStorageMap`]. - pub fn add(&mut self, proof: SmtProof) -> Result<(), MerkleError> { - self.partial_smt.add_proof(proof) + /// Adds a [`StorageMapWitness`] to this [`PartialStorageMap`]. + pub fn add(&mut self, witness: StorageMapWitness) -> Result<(), MerkleError> { + self.partial_smt.add_proof(SmtProof::from(witness)) } } diff --git a/crates/miden-objects/src/account/storage/map/witness.rs b/crates/miden-objects/src/account/storage/map/witness.rs new file mode 100644 index 0000000000..7cf4cc2875 --- /dev/null +++ b/crates/miden-objects/src/account/storage/map/witness.rs @@ -0,0 +1,50 @@ +use miden_crypto::merkle::{InnerNodeInfo, SmtProof}; + +use crate::Word; + +/// A witness of an asset in a [`StorageMap`](super::StorageMap). +/// +/// It proves inclusion of a certain storage item in the map. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StorageMapWitness(SmtProof); + +impl StorageMapWitness { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`StorageMapWitness`] from an SMT proof. + pub fn new(smt_proof: SmtProof) -> Self { + Self(smt_proof) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Searches for a value in the witness with the given `map_key`. + pub fn find(&self, hashed_map_key: Word) -> Option { + self.entries() + .find_map(|(key, value)| if *key == hashed_map_key { Some(*value) } else { None }) + } + + /// Returns an iterator over the key-value pairs in this witness. + /// + /// Note that the returned key is the hashed map key. + pub fn entries(&self) -> impl Iterator { + // Convert &(Word, Word) into (&Word, &Word) as it is more flexible. + self.0.leaf().entries().into_iter().map(|(key, value)| (key, value)) + } + + /// Returns an iterator over every inner node of this witness' merkle path. + pub fn authenticated_nodes(&self) -> impl Iterator + '_ { + self.0 + .path() + .authenticated_nodes(self.0.leaf().index().value(), self.0.leaf().hash()) + .expect("leaf index is u64 and should be less than 2^SMT_DEPTH") + } +} + +impl From for SmtProof { + fn from(witness: StorageMapWitness) -> Self { + witness.0 + } +} diff --git a/crates/miden-objects/src/account/storage/mod.rs b/crates/miden-objects/src/account/storage/mod.rs index 672e931266..5561a65be1 100644 --- a/crates/miden-objects/src/account/storage/mod.rs +++ b/crates/miden-objects/src/account/storage/mod.rs @@ -19,7 +19,7 @@ mod slot; pub use slot::{StorageSlot, StorageSlotType}; mod map; -pub use map::{PartialStorageMap, StorageMap}; +pub use map::{PartialStorageMap, StorageMap, StorageMapWitness}; mod header; pub use header::{AccountStorageHeader, StorageSlotHeader}; @@ -112,7 +112,7 @@ impl AccountStorage { } /// Returns a reference to the storage slots. - pub fn slots(&self) -> &Vec { + pub fn slots(&self) -> &[StorageSlot] { &self.slots } diff --git a/crates/miden-objects/src/account/storage/partial.rs b/crates/miden-objects/src/account/storage/partial.rs index e2f993a235..f433e1b3f4 100644 --- a/crates/miden-objects/src/account/storage/partial.rs +++ b/crates/miden-objects/src/account/storage/partial.rs @@ -155,8 +155,8 @@ mod tests { // Create partial storage with validation of one map key let storage_header = AccountStorageHeader::from(&storage); - let proof = map_1.open(&map_key_present); - let partial_smt = PartialSmt::from_proofs([proof])?; + let witness = map_1.open(&map_key_present); + let partial_smt = PartialSmt::from_proofs([witness.into()])?; let partial_storage = PartialStorage::new(storage_header, [partial_smt.into()]) .context("creating partial storage")?; diff --git a/crates/miden-objects/src/account/storage/slot/type.rs b/crates/miden-objects/src/account/storage/slot/type.rs index b15d09be06..bb5d9f2645 100644 --- a/crates/miden-objects/src/account/storage/slot/type.rs +++ b/crates/miden-objects/src/account/storage/slot/type.rs @@ -29,6 +29,16 @@ impl StorageSlotType { StorageSlotType::Map => Word::from([1, 0, 0, 0u32]), } } + + /// Returns `true` if the slot is a value slot, `false` otherwise. + pub fn is_value(&self) -> bool { + matches!(self, Self::Value) + } + + /// Returns `true` if the slot is a map slot, `false` otherwise. + pub fn is_map(&self) -> bool { + matches!(self, Self::Map) + } } impl TryFrom for StorageSlotType { diff --git a/crates/miden-objects/src/asset/vault/mod.rs b/crates/miden-objects/src/asset/vault/mod.rs index f8e1c84c0b..1d08f8c51d 100644 --- a/crates/miden-objects/src/asset/vault/mod.rs +++ b/crates/miden-objects/src/asset/vault/mod.rs @@ -120,6 +120,7 @@ impl AssetVault { self.asset_tree.is_empty() } + // TODO: Replace with https://github.com/0xMiden/crypto/issues/515 once implemented. /// Returns the leaf index of a vault key. pub fn vault_key_to_leaf_index(vault_key: Word) -> Felt { // The third element in an SMT key is the index. diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index c83d4ce302..d59599a288 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -4,7 +4,8 @@ use miden_lib::testing::note::NoteBuilder; use miden_lib::utils::ScriptBuilder; -use miden_objects::account::AccountId; +use miden_objects::LexicographicWord; +use miden_objects::account::{AccountId, AccountStorage}; use miden_objects::asset::{Asset, FungibleAsset}; use miden_objects::testing::account_id::{ ACCOUNT_ID_NATIVE_ASSET_FAUCET, @@ -16,6 +17,9 @@ use miden_objects::testing::constants::FUNGIBLE_ASSET_AMOUNT; use super::Word; use crate::{Auth, MockChain, TransactionContextBuilder}; +// ASSET LAZY LOADING +// ================================================================================================ + /// Tests that adding two different assets to the account vault succeeds when lazy loading is /// enabled. #[test] @@ -161,3 +165,126 @@ fn loading_fee_asset_succeeds() -> anyhow::Result<()> { Ok(()) } + +// STORAGE LAZY LOADING +// ================================================================================================ + +/// Tests that updating or inserting a map item into a storage map succeeds when lazy loading is +/// enabled. +#[test] +fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { + // Fetch a random existing key from the map. + let mock_map = AccountStorage::mock_map(); + let existing_key = *mock_map.entries().next().unwrap().0; + + let non_existent_key = Word::from([5, 5, 5, 5u32]); + assert!( + mock_map.open(&non_existent_key).find(non_existent_key).is_none(), + "test setup requires that the non existent key does not exist" + ); + + // The index of the mock map in account storage is 2. + let map_index = 2; + + let value0 = Word::from([3, 4, 5, 6u32]); + let value1 = Word::from([9, 8, 7, 6u32]); + + let code = format!( + " + use.mock::account + + begin + # Update an existing key. + push.{value0} + push.{existing_key} + push.{map_index} + # => [index, KEY, VALUE] + call.account::set_map_item + + # Insert a non-existent key. + push.{value1} + push.{non_existent_key} + push.{map_index} + # => [index, KEY, VALUE] + call.account::set_map_item + + exec.::std::sys::truncate_stack + end + " + ); + + let builder = ScriptBuilder::with_mock_libraries()?; + let source_manager = builder.source_manager(); + let tx_script = builder.compile_tx_script(code)?; + + let tx = TransactionContextBuilder::with_existing_mock_account() + .tx_script(tx_script) + .enable_partial_loading() + .with_source_manager(source_manager) + .build()? + .execute_blocking()?; + + let map_delta = tx.account_delta().storage().maps().get(&map_index).unwrap(); + assert_eq!(map_delta.entries().get(&LexicographicWord::new(existing_key)).unwrap(), &value0); + assert_eq!( + map_delta.entries().get(&LexicographicWord::new(non_existent_key)).unwrap(), + &value1 + ); + + Ok(()) +} + +/// Tests that getting a map item from a storage map succeeds when lazy loading is enabled. +#[test] +fn getting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { + // Fetch a random existing key from the map. + let mock_map = AccountStorage::mock_map(); + let (existing_key, existing_value) = mock_map.entries().next().unwrap(); + + let non_existent_key = Word::from([5, 5, 5, 5u32]); + assert!( + mock_map.open(&non_existent_key).find(non_existent_key).is_none(), + "test setup requires that the non existent key does not exist" + ); + + let code = format!( + r#" + use.std::word + use.mock::account + + begin + # Fetch value from existing key. + push.{existing_key} + push.2 + # => [index, KEY] + call.account::get_map_item + + push.{existing_value} + assert_eqw.err="existing value does not match expected value" + + # Fetch a non-existent key. + push.{non_existent_key} + push.2 + # => [index, KEY] + call.account::get_map_item + + padw assert_eqw.err="non-existent value should be the empty word" + + exec.::std::sys::truncate_stack + end + "# + ); + + let builder = ScriptBuilder::with_mock_libraries()?; + let source_manager = builder.source_manager(); + let tx_script = builder.compile_tx_script(code)?; + + TransactionContextBuilder::with_existing_mock_account() + .tx_script(tx_script) + .enable_partial_loading() + .with_source_manager(source_manager) + .build()? + .execute_blocking()?; + + Ok(()) +} diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 55be26b269..6a03de85ff 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; use anyhow::Context; use miden_block_prover::{LocalBlockProver, ProvenBlockError}; use miden_objects::account::delta::AccountUpdateDetails; -use miden_objects::account::{Account, AccountId, AuthSecretKey, PartialAccount, StorageSlot}; +use miden_objects::account::{Account, AccountId, AuthSecretKey, PartialAccount}; use miden_objects::batch::{ProposedBatch, ProvenBatch}; use miden_objects::block::{ AccountTree, @@ -19,7 +19,6 @@ use miden_objects::block::{ ProposedBlock, ProvenBlock, }; -use miden_objects::crypto::merkle::SmtProof; use miden_objects::note::{Note, NoteHeader, NoteId, NoteInclusionProof, Nullifier}; use miden_objects::transaction::{ AccountInputs, @@ -743,15 +742,6 @@ impl MockChain { let account_witness = self.account_tree().open(account_id); assert_eq!(account_witness.state_commitment(), account.commitment()); - let mut storage_map_proofs = vec![]; - for slot in account.storage().slots() { - // if there are storage maps, we populate the merkle store and advice map - if let StorageSlot::Map(map) = slot { - let proofs: Vec = map.entries().map(|(key, _)| map.open(key)).collect(); - storage_map_proofs.extend(proofs); - } - } - Ok(AccountInputs::new(account.into(), account_witness)) } diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 0d67cdd5f3..221cbfb0fa 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -9,7 +9,14 @@ use anyhow::Context; use miden_lib::testing::account_component::IncrNonceAuthComponent; use miden_lib::testing::mock_account::MockAccountExt; use miden_objects::EMPTY_WORD; -use miden_objects::account::{Account, AccountHeader, PartialAccount}; +use miden_objects::account::{ + Account, + AccountHeader, + PartialAccount, + PartialStorage, + PartialStorageMap, + StorageSlot, +}; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::debuginfo::SourceManagerSync; use miden_objects::asset::PartialVault; @@ -285,8 +292,8 @@ impl TransactionContextBuilder { }; // If partial loading is enabled, construct an account that doesn't contain all - // merkle paths of assets, in order to test lazy loading. Otherwise, load the full - // account. + // merkle paths of assets and storage maps, in order to test lazy loading. + // Otherwise, load the full account. let tx_inputs = if self.load_partial_account { let (account, account_seed, block_header, partial_blockchain, input_notes) = tx_inputs.into_parts(); @@ -299,11 +306,30 @@ impl TransactionContextBuilder { let mut partial_vault = PartialVault::default(); partial_vault.add(self.account.vault().open(Word::empty()).into())?; + // Construct a partial storage that tracks the empty word in all storage maps, but none + // of the other keys, following the same rationale as the partial vault above. + let storage_header = self.account.storage().to_header(); + let storage_maps = + self.account.storage().slots().iter().filter_map( + |storage_slot| match storage_slot { + StorageSlot::Map(storage_map) => { + let mut partial_storage_map = PartialStorageMap::default(); + partial_storage_map + .add(storage_map.open(&Word::empty())) + .expect("adding the first proof should never error"); + Some(partial_storage_map) + }, + _ => None, + }, + ); + let partial_storage = PartialStorage::new(storage_header, storage_maps) + .expect("provided storage maps should match storage header"); + let account = PartialAccount::new( account.id(), account.nonce(), account.code().clone(), - account.storage().clone(), + partial_storage, partial_vault, ); diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index f9e97b5b99..20c5754f9a 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -5,7 +5,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_lib::transaction::TransactionKernel; -use miden_objects::account::{Account, AccountId, PartialAccount}; +use miden_objects::account::{Account, AccountId, PartialAccount, StorageMapWitness, StorageSlot}; use miden_objects::assembly::debuginfo::{SourceLanguage, Uri}; use miden_objects::assembly::{SourceManager, SourceManagerSync}; use miden_objects::asset::AssetWitness; @@ -226,6 +226,41 @@ impl DataStore for TransactionContext { async { Ok(asset_witness) } } + + fn get_storage_map_witness( + &self, + account_id: AccountId, + map_root: Word, + map_key: Word, + ) -> impl FutureMaybeSend> { + assert_eq!( + account_id, + self.account.id(), + "only native account storage map witnesses can be requested (for now)" + ); + + async move { + // Iterate the account storage to find the map with the requested root. + let storage_map = self + .account() + .storage() + .slots() + .iter() + .find_map(|slot| match slot { + StorageSlot::Map(storage_map) if storage_map.root() == map_root => { + Some(storage_map) + }, + _ => None, + }) + .ok_or_else(|| { + DataStoreError::other(format!( + "failed to find storage map with root {map_root} in account storage" + )) + })?; + + Ok(storage_map.open(&map_key)) + } + } } impl MastForestStore for TransactionContext { diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index 9490fa071d..321f3862ce 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -277,6 +277,16 @@ pub enum TransactionKernelError { GetVaultAssetWitness { vault_root: Word, vault_key: Word, + // thiserror will return this when calling Error::source on TransactionKernelError. + source: DataStoreError, + }, + #[error( + "failed to get storage map witness from data store for map root {map_root} and map_key {map_key}" + )] + GetStorageMapWitness { + map_root: Word, + map_key: Word, + // thiserror will return this when calling Error::source on TransactionKernelError. source: DataStoreError, }, #[error( diff --git a/crates/miden-tx/src/executor/data_store.rs b/crates/miden-tx/src/executor/data_store.rs index 037e074994..354400607c 100644 --- a/crates/miden-tx/src/executor/data_store.rs +++ b/crates/miden-tx/src/executor/data_store.rs @@ -1,6 +1,6 @@ use alloc::collections::BTreeSet; -use miden_objects::account::{AccountId, PartialAccount}; +use miden_objects::account::{AccountId, PartialAccount, StorageMapWitness}; use miden_objects::asset::AssetWitness; use miden_objects::block::{BlockHeader, BlockNumber}; use miden_objects::transaction::PartialBlockchain; @@ -46,4 +46,19 @@ pub trait DataStore: MastForestStore { vault_root: Word, vault_key: Word, ) -> impl FutureMaybeSend>; + + /// Returns a witness for a storage map item identified by `map_key` in the requested account's + /// storage with the requested storage `map_root`. + /// + /// Note that the `map_key` needs to be hashed in order to get the actual key into the storage + /// map. + /// + /// This is the witness that needs to be added to the advice provider's merkle store and advice + /// map to make access to the specified storage map item possible. + fn get_storage_map_witness( + &self, + account_id: AccountId, + map_root: Word, + map_key: Word, + ) -> impl FutureMaybeSend>; } diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index c2f713b7de..11d25fc682 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -3,7 +3,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_lib::transaction::TransactionEvent; -use miden_objects::account::{AccountDelta, AccountId, PartialAccount}; +use miden_objects::account::{AccountDelta, AccountId, PartialAccount, StorageSlotType}; use miden_objects::assembly::debuginfo::Location; use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; use miden_objects::asset::{Asset, AssetWitness, FungibleAsset}; @@ -210,6 +210,67 @@ where Ok(asset_witness_to_advice_mutation(asset_witness)) } + /// Handles a request for a storage map witness by querying the data store for a merkle path. + /// + /// Note that we request witnesses against the initial map root for native accounts. See also + /// [`Self::on_account_vault_asset_witness_requested`] for more on this topic. + async fn on_account_storage_map_witness_requested( + &self, + current_account_id: AccountId, + slot_index: usize, + _map_root: Word, + map_key: Word, + ) -> Result, TransactionKernelError> { + // For now, we only support getting witnesses for the native account, so return early if the + // requested account is not the native one. + if current_account_id != self.base_host.initial_account_header().id() { + return Ok(Vec::new()); + } + + // For native accounts, we have to request witnesses against the initial root instead of the + // _current_ one, since the data store only has witnesses for initial one. + let map_root = { + let (slot_type, slot_value) = + self.base_host.initial_account_storage_header().slot(slot_index).map_err( + |err| { + TransactionKernelError::other_with_source( + "failed to access storage map in storage header", + err, + ) + }, + )?; + if *slot_type != StorageSlotType::Map { + return Err(TransactionKernelError::other(format!( + "expected map slot type at slot index {slot_index}" + ))); + } + *slot_value + }; + + let storage_map_witness = self + .base_host + .store() + .get_storage_map_witness(current_account_id, map_root, map_key) + .await + .map_err(|err| TransactionKernelError::GetStorageMapWitness { + map_root, + map_key, + source: err, + })?; + + // Get the nodes in the proof and insert them into the merkle store. + let merkle_store_ext = + AdviceMutation::extend_merkle_store(storage_map_witness.authenticated_nodes()); + + let smt_proof = SmtProof::from(storage_map_witness); + let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([( + smt_proof.leaf().hash(), + smt_proof.leaf().to_elements(), + )])); + + Ok(vec![merkle_store_ext, map_ext]) + } + /// Handles a request to an asset witness by querying the data store for a merkle path. /// /// ## Native Account @@ -346,6 +407,20 @@ where .on_account_vault_asset_witness_requested(current_account_id, vault_root, asset) .await .map_err(EventError::from), + TransactionEventData::AccountStorageMapWitness { + current_account_id, + slot_index, + map_root, + map_key, + } => self + .on_account_storage_map_witness_requested( + current_account_id, + slot_index, + map_root, + map_key, + ) + .await + .map_err(EventError::from), } } } diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index eeb73203b0..9a9dcdbf4b 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -28,7 +28,14 @@ use miden_lib::transaction::memory::{ NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR, }; use miden_lib::transaction::{TransactionEvent, TransactionEventError}; -use miden_objects::account::{AccountDelta, AccountHeader, AccountId, PartialAccount}; +use miden_objects::account::{ + AccountDelta, + AccountHeader, + AccountId, + AccountStorageHeader, + PartialAccount, + StorageMap, +}; use miden_objects::asset::{Asset, AssetVault, FungibleAsset}; use miden_objects::note::NoteId; use miden_objects::transaction::{ @@ -73,6 +80,9 @@ pub struct TransactionBaseHost<'store, STORE> { /// The header of the account at the beginning of transaction execution. initial_account_header: AccountHeader, + /// The storage header of the native account at the beginning of transaction execution. + initial_account_storage_header: AccountStorageHeader, + /// Account state changes accumulated during transaction execution. /// /// The delta is updated by event handlers. @@ -114,6 +124,7 @@ where mast_store, scripts_mast_store, initial_account_header: account.into(), + initial_account_storage_header: account.storage().header().clone(), account_delta: AccountDeltaTracker::new( account.id(), account.storage().header().clone(), @@ -148,6 +159,12 @@ where &self.initial_account_header } + /// Returns a reference to the initial storage header of the native account, which represents + /// the state at the beginning of the transaction. + pub fn initial_account_storage_header(&self) -> &AccountStorageHeader { + &self.initial_account_storage_header + } + /// Returns a reference to the account delta tracker of this transaction host. pub fn account_delta_tracker(&self) -> &AccountDeltaTracker { &self.account_delta @@ -213,12 +230,18 @@ where self.on_account_vault_after_remove_asset(process).map(|_| TransactionEventHandling::Handled(Vec::new())) }, + TransactionEvent::AccountStorageBeforeGetMapItem => { + Self::on_account_storage_before_get_map_item(process) + } + TransactionEvent::AccountStorageBeforeSetItem => Ok(TransactionEventHandling::Handled(Vec::new())), TransactionEvent::AccountStorageAfterSetItem => { self.on_account_storage_after_set_item(process).map(|_| TransactionEventHandling::Handled(Vec::new())) }, - TransactionEvent::AccountStorageBeforeSetMapItem => Ok(TransactionEventHandling::Handled(Vec::new())), + TransactionEvent::AccountStorageBeforeSetMapItem => { + Self::on_account_storage_before_set_map_item(process) + }, TransactionEvent::AccountStorageAfterSetMapItem => { self.on_account_storage_after_set_map_item(process).map(|_| TransactionEventHandling::Handled(Vec::new())) }, @@ -530,6 +553,74 @@ where Ok(()) } + /// Checks if the necessary witness for accessing the map item is already in the merkle store, + /// and if not, extracts all necessary data for requesting it. + /// + /// Expected stack state: `[KEY, ROOT, index]` + pub fn on_account_storage_before_get_map_item( + process: &ProcessState, + ) -> Result { + let map_key = process.get_stack_word(0); + let map_root = process.get_stack_word(1); + let slot_index = process.get_stack_item(8); + + Self::on_account_storage_before_get_or_set_map_item(slot_index, map_root, map_key, process) + } + + /// Checks if the necessary witness for accessing the map item is already in the merkle store, + /// and if not, extracts all necessary data for requesting it. + /// + /// Expected stack state: `[index, KEY, NEW_VALUE, OLD_ROOT]` + pub fn on_account_storage_before_set_map_item( + process: &ProcessState, + ) -> Result { + let slot_index = process.get_stack_item(0); + let map_key = Word::from([ + process.get_stack_item(4), + process.get_stack_item(3), + process.get_stack_item(2), + process.get_stack_item(1), + ]); + let map_root = Word::from([ + process.get_stack_item(12), + process.get_stack_item(11), + process.get_stack_item(10), + process.get_stack_item(9), + ]); + Self::on_account_storage_before_get_or_set_map_item(slot_index, map_root, map_key, process) + } + + /// Checks if the necessary witness for accessing the map item is already in the merkle store, + /// and if not, extracts all necessary data for requesting it. + fn on_account_storage_before_get_or_set_map_item( + slot_index: Felt, + map_root: Word, + map_key: Word, + process: &ProcessState, + ) -> Result { + let current_account_id = Self::get_current_account_id(process)?; + let hashed_map_key = StorageMap::hash_key(map_key); + let leaf_index = StorageMap::hashed_map_key_to_leaf_index(hashed_map_key); + + if Self::advice_provider_has_merkle_path::<{ StorageMap::DEPTH }>( + process, map_root, leaf_index, + )? { + // If the merkle path is already in the store there is nothing to do. + Ok(TransactionEventHandling::Handled(Vec::new())) + } else { + // If the merkle path is not in the store return the data to request it. + Ok(TransactionEventHandling::Unhandled( + TransactionEventData::AccountStorageMapWitness { + current_account_id, + // Slot index should always fit into a usize. + slot_index: slot_index.as_int() as usize, + map_root, + map_key, + }, + )) + } + } + /// Extracts information from the process state about the storage map being updated and /// records the latest values of this storage map. /// @@ -644,30 +735,22 @@ where })?; let current_account_id = Self::get_current_account_id(process)?; - let leaf_index = AssetVault::vault_key_to_leaf_index(asset.vault_key()); - match process.advice_provider().get_merkle_path( - vault_root, - &Felt::from(AssetVault::DEPTH), - &leaf_index, - ) { - // Merkle path is already in the store; consider the event handled. - Ok(_) => Ok(TransactionEventHandling::Handled(Vec::new())), - // This means the merkle path is missing in the advice provider. - Err(AdviceError::MerkleStoreLookupFailed(_)) => { - Ok(TransactionEventHandling::Unhandled( - TransactionEventData::AccountVaultAssetWitness { - current_account_id, - vault_root, - asset, - }, - )) - }, - // We should never encounter this as long as our inputs to get_merkle_path are correct. - Err(err) => Err(TransactionKernelError::other_with_source( - "unexpected get_merkle_path error", - err, - )), + + if Self::advice_provider_has_merkle_path::<{ AssetVault::DEPTH }>( + process, vault_root, leaf_index, + )? { + // If the merkle path is already in the store there is nothing to do. + Ok(TransactionEventHandling::Handled(Vec::new())) + } else { + // If the merkle path is not in the store return the data to request it. + Ok(TransactionEventHandling::Unhandled( + TransactionEventData::AccountVaultAssetWitness { + current_account_id, + vault_root, + asset, + }, + )) } } @@ -839,6 +922,29 @@ where Ok(TransactionSummary::new(account_delta, input_notes, output_notes, salt)) } + + /// Returns `true` if the advice provider has a merkle path for the provided root and leaf + /// index, `false` otherwise. + fn advice_provider_has_merkle_path( + process: &ProcessState, + root: Word, + leaf_index: Felt, + ) -> Result { + match process + .advice_provider() + .get_merkle_path(root, &Felt::from(TREE_DEPTH), &leaf_index) + { + // Merkle path is already in the store; consider the event handled. + Ok(_) => Ok(true), + // This means the merkle path is missing in the advice provider. + Err(AdviceError::MerkleStoreLookupFailed(_)) => Ok(false), + // We should never encounter this as long as our inputs to get_merkle_path are correct. + Err(err) => Err(TransactionKernelError::other_with_source( + "unexpected get_merkle_path error", + err, + )), + } + } } impl<'store, STORE> TransactionBaseHost<'store, STORE> { @@ -892,4 +998,15 @@ pub(super) enum TransactionEventData { /// The asset for which a witness is requested. asset: Asset, }, + /// The data necessary to request a storage map witness from the data store. + AccountStorageMapWitness { + /// The account ID for whose storage a witness is requested. + current_account_id: AccountId, + /// The index of the slot that contains the map root. + slot_index: usize, + /// The root of the storage map in the account. + map_root: Word, + /// The unhashed map key for which a witness is requested. + map_key: Word, + }, } diff --git a/crates/miden-tx/src/prover/prover_host.rs b/crates/miden-tx/src/prover/prover_host.rs index fd6f0eac75..611ebc460f 100644 --- a/crates/miden-tx/src/prover/prover_host.rs +++ b/crates/miden-tx/src/prover/prover_host.rs @@ -126,6 +126,7 @@ where // Witnesses should be in the advice provider at proving time, so there is // nothing to do. TransactionEventData::AccountVaultAssetWitness { .. } => Ok(Vec::new()), + TransactionEventData::AccountStorageMapWitness { .. } => Ok(Vec::new()), // We don't track enough information to handle this event. Since this just // improves error messages for users and the error should not be relevant during // proving, we ignore it. From 955f6fc83edc9aada4bab7f7b8df3f09ca79033c Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Thu, 11 Sep 2025 23:45:38 +0300 Subject: [PATCH 034/133] refactor: move note related procs from `tx` to corresponding note module (#1874) --- CHANGELOG.md | 1 + .../multisig_rpo_falcon_512.masm | 1 - .../asm/kernels/transaction/api.masm | 55 +- .../asm/kernels/transaction/lib/memory.masm | 2 +- .../kernels/transaction/lib/output_note.masm | 449 +++++++++++ .../asm/kernels/transaction/lib/tx.masm | 453 +---------- .../contracts/faucets/basic_fungible.masm | 8 +- .../asm/miden/contracts/wallets/basic.masm | 4 +- .../asm/miden/kernel_proc_offsets.masm | 119 +-- crates/miden-lib/asm/miden/note.masm | 69 +- crates/miden-lib/asm/miden/output_note.masm | 58 ++ crates/miden-lib/asm/miden/tx.masm | 60 +- crates/miden-lib/asm/note_scripts/P2IDE.masm | 1 - crates/miden-lib/asm/note_scripts/SWAP.masm | 4 +- .../src/account/interface/component.rs | 4 +- .../miden-lib/src/errors/tx_kernel_errors.rs | 2 - crates/miden-lib/src/testing/mock_util_lib.rs | 6 +- .../src/transaction/kernel_procedures.rs | 14 +- crates/miden-lib/src/transaction/memory.rs | 2 +- .../src/kernel_tests/tx/test_account_delta.rs | 8 +- .../src/kernel_tests/tx/test_epilogue.rs | 10 +- .../src/kernel_tests/tx/test_note.rs | 10 +- .../src/kernel_tests/tx/test_output_note.rs | 708 +++++++++++++++++- .../src/kernel_tests/tx/test_tx.rs | 686 +---------------- crates/miden-testing/src/utils.rs | 4 +- crates/miden-testing/tests/scripts/p2id.rs | 6 +- crates/miden-testing/tests/scripts/swap.rs | 4 +- docs/src/protocol_library.md | 9 +- 28 files changed, 1384 insertions(+), 1373 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16d19c6fed..cb17456c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). - [BREAKING] Move `TransactionKernelError` to miden-tx ([#1859](https://github.com/0xMiden/miden-base/pull/1859)). +- [BREAKING] Move and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). ## 0.11.2 (2025-09-08) diff --git a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm index 1941b430fd..9f4ba1d512 100644 --- a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm @@ -2,7 +2,6 @@ use.miden::account use.miden::auth -use.miden::tx # CONSTANTS # ================================================================================================= diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 9f7b08478a..5637059a32 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -984,6 +984,29 @@ end ### OUTPUT NOTE ################################# +#! Creates a new note and returns its index. +#! +#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] +#! Outputs: [note_idx, pad(15)] +#! +#! Where: +#! - tag is the tag to be included in the note. +#! - aux is the auxiliary metadata to be included in the note. +#! - note_type is the note storage type. +#! - execution_hint is the note execution hint tag and payload. +#! - RECIPIENT is the recipient of the note. +#! - note_idx is the index of the created note. +#! +#! Invocation: dynexec +export.output_note_create + # check that this procedure was executed against the native account + exec.memory::assert_native_account + # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] + + exec.output_note::create + # => [note_idx, pad(15)] +end + #! Adds the ASSET to the note specified by the index. #! #! Inputs: [note_idx, ASSET, pad(11)] @@ -1006,7 +1029,7 @@ export.output_note_add_asset movdn.4 dupw movup.8 # => [note_idx, ASSET, ASSET, pad(11)] - exec.tx::add_asset_to_note + exec.output_note::add_asset # => [note_idx, ASSET, pad(11)] end @@ -1104,29 +1127,6 @@ end ### TRANSACTION ################################# -#! Creates a new note and returns the index of the note. -#! -#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] -#! Outputs: [note_idx, pad(15)] -#! -#! Where: -#! - tag is the tag to be included in the note. -#! - aux is the auxiliary metadata to be included in the note. -#! - note_type is the note storage type. -#! - execution_hint is the note execution hint tag and payload. -#! - RECIPIENT is the recipient of the note. -#! - note_idx is the index of the created note. -#! -#! Invocation: dynexec -export.tx_create_note - # check that this procedure was executed against the native account - exec.memory::assert_native_account - # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] - - exec.tx::create_note - # => [note_idx, pad(15)] -end - #! Returns the input notes commitment. #! #! This is computed as a sequential hash of `(NULLIFIER, EMPTY_WORD_OR_NOTE_COMMITMENT)` over all input @@ -1378,7 +1378,8 @@ export.tx_end_foreign_context # => [pad(16)] end -#! Updates the transaction expiration time delta. +#! Updates the transaction expiration block delta. +#! #! Once set, the delta can be decreased but not increased. #! #! The input block height delta is added to the reference block in order to output an upper limit @@ -1391,8 +1392,8 @@ end #! - block_height_delta is the desired expiration time delta (1 to 0xFFFF). #! #! Invocation: dynexec -export.tx_update_expiration_block_num - exec.tx::update_expiration_block_num +export.tx_update_expiration_block_delta + exec.tx::update_expiration_block_delta # => [pad(16)] end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index 39c67f916a..42f5a2e4a3 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -1589,7 +1589,7 @@ export.set_input_note_args mem_storew end -#! Returns the number of inputs of the note currently being processed. +#! Returns the number of inputs of the note located at the specified memory address. #! #! Inputs: [note_ptr] #! Outputs: [num_inputs] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm b/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm index 4f8df7b317..8eb215983f 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm @@ -1,14 +1,123 @@ +use.$kernel::account use.$kernel::memory use.$kernel::note +use.$kernel::asset +use.$kernel::constants + +# CONSTANTS +# ================================================================================================= + +# Constants for different note types +const.PUBLIC_NOTE=1 # 0b01 +const.PRIVATE_NOTE=2 # 0b10 +const.ENCRYPTED_NOTE=3 # 0b11 + +# The note type must be PUBLIC, unless the high bits are `0b11`. (See the table below.) +const.LOCAL_ANY_PREFIX=3 # 0b11 # ERRORS # ================================================================================================= +const.ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT="number of output notes in the transaction exceeds the maximum limit of 1024" + +const.ERR_NOTE_INVALID_TYPE="invalid note type" + const.ERR_OUTPUT_NOTE_INDEX_OUT_OF_BOUNDS="requested output note index should be less than the total number of created output notes" +const.ERR_NOTE_INVALID_INDEX="failed to find note at the given index; index must be within [0, num_of_notes]" + +const.ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED="adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807" + +const.ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS="non-fungible asset that already exists in the note cannot be added again" + +# The 2 highest bits in the u32 tag have the following meaning: +# +# | Prefix | Name | [`NoteExecutionMode`] | Target | Allowed [`NoteType`] | +# | :----: | :--------------------: | :-------------------: | :----------------------: | :------------------: | +# | `0b00` | `NetworkAccount` | Network | Network Account | [`NoteType::Public`] | +# | `0b01` | `NetworkUseCase` | Network | Use case | [`NoteType::Public`] | +# | `0b10` | `LocalPublicAny` | Local | Any | [`NoteType::Public`] | +# | `0b11` | `LocalAny` | Local | Any | Any | +# +# Execution: Is a hint for the network, to check if the note can be consumed by a network controlled +# account +# Target: Is a hint for the type of target. Use case means the note may be consumed by anyone, +# specific means there is a specific target for the note (the target may be a public key, a user +# that knows some secret, or a specific account ID) +# +# Only the note type from the above list is enforced. The other values are only hints intended as a +# best effort optimization strategy. A badly formatted note may 1. not be consumed because honest +# users won't see the note 2. generate slightly more load as extra validation is performed for the +# invalid tags. None of these scenarios have any significant impact. + +const.ERR_NOTE_INVALID_NOTE_TYPE_FOR_NOTE_TAG_PREFIX="invalid note type for the given note tag prefix" + +const.ERR_NOTE_TAG_MUST_BE_U32="the note's tag must fit into a u32 so the 32 most significant bits must be zero" + +# EVENTS +# ================================================================================================= + +# Event emitted before a new note is created. +const.NOTE_BEFORE_CREATED_EVENT=131083 +# Event emitted after a new note is created. +const.NOTE_AFTER_CREATED_EVENT=131084 + +# Event emitted before an ASSET is added to a note +const.NOTE_BEFORE_ADD_ASSET_EVENT=131085 +# Event emitted after an ASSET is added to a note +const.NOTE_AFTER_ADD_ASSET_EVENT=131086 + # OUTPUT NOTE PROCEDURES # ================================================================================================= +#! Creates a new note and returns the index of the note. +#! +#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT] +#! Outputs: [note_idx] +#! +#! Where: +#! - tag is the note tag which can be used by the recipient(s) to identify notes intended for them. +#! - aux is the arbitrary user-defined value. +#! - note_type is the type of the note, which defines how the note is to be stored (e.g., on-chain +#! or off-chain). +#! - execution_hint is the hint which specifies when a note is ready to be consumed. +#! - RECIPIENT defines spend conditions for the note. +#! - note_idx is the index of the created note. +#! +#! Panics if: +#! - the note_type is not valid. +#! - the note_tag is not an u32. +#! - the note_tag starts with anything but 0b11 and note_type is not public. +#! - the number of output notes exceeds the maximum limit of 1024. +export.create + emit.NOTE_BEFORE_CREATED_EVENT + + exec.build_metadata + # => [NOTE_METADATA, RECIPIENT] + + # get the index for the next note to be created and increment counter + exec.increment_num_output_notes dup movdn.9 + # => [note_idx, NOTE_METADATA, RECIPIENT, note_idx] + + # get a pointer to the memory address at which the note will be stored + exec.memory::get_output_note_ptr + # => [note_ptr, NOTE_METADATA, RECIPIENT, note_idx] + + movdn.4 + # => [NOTE_METADATA, note_ptr, RECIPIENT, note_idx] + + # emit event to signal that a new note is created + emit.NOTE_AFTER_CREATED_EVENT + + # set the metadata for the output note + dup.4 exec.memory::set_output_note_metadata dropw + # => [note_ptr, RECIPIENT, note_idx] + + # set the RECIPIENT for the output note + exec.memory::set_output_note_recipient dropw + # => [note_idx] +end + #! Returns the information about assets in the output note with the specified index. #! #! The provided output note index is expected to be less than the total number of output notes. @@ -62,6 +171,66 @@ export.get_assets_info # => [ASSETS_COMMITMENT, num_assets] end +#! Adds the ASSET to the note specified by the index. +#! +#! Inputs: [note_idx, ASSET] +#! Outputs: [note_idx] +#! +#! Where: +#! - note_idx is the index of the note to which the asset is added. +#! - ASSET can be a fungible or non-fungible asset. +#! +#! Panics if: +#! - the ASSET is malformed (e.g., invalid faucet ID). +#! - the max amount of fungible assets is exceeded. +#! - the non-fungible asset already exists in the note. +#! - the total number of ASSETs exceeds the maximum of 256. +export.add_asset + # check if the note exists, it must be within [0, num_of_notes] + dup exec.memory::get_num_output_notes lte assert.err=ERR_NOTE_INVALID_INDEX + # => [note_idx, ASSET] + + # get a pointer to the memory address of the note at which the asset will be stored + dup movdn.5 exec.memory::get_output_note_ptr + # => [note_ptr, ASSET, note_idx] + + # get current num of assets + dup exec.memory::get_output_note_num_assets movdn.5 + # => [note_ptr, ASSET, num_of_assets, note_idx] + + # validate the ASSET + movdn.4 exec.asset::validate_asset + # => [ASSET, note_ptr, num_of_assets, note_idx] + + # emit event to signal that a new asset is going to be added to the note. + emit.NOTE_BEFORE_ADD_ASSET_EVENT + # => [ASSET, note_ptr, num_of_assets, note_idx] + + # Check if ASSET to add is fungible + exec.asset::is_fungible_asset + # => [is_fungible_asset?, ASSET, note_ptr, num_of_assets, note_idx] + + if.true + # ASSET to add is fungible + exec.add_fungible_asset + # => [note_ptr, note_idx] + else + # ASSET to add is non-fungible + exec.add_non_fungible_asset + # => [note_ptr, note_idx] + end + # => [note_ptr, note_idx] + + # update the assets commitment dirty flag to signal that the current assets commitment is not + # valid anymore + push.1 swap exec.memory::set_output_note_dirty_flag + # => [note_idx] + + # emit event to signal that a new asset was added to the note. + emit.NOTE_AFTER_ADD_ASSET_EVENT + # => [note_idx] +end + #! Assert that the provided note index is less than the total number of output notes. #! #! Inputs: [note_index] @@ -75,3 +244,283 @@ export.assert_note_index_in_bounds u32lt assert.err=ERR_OUTPUT_NOTE_INDEX_OUT_OF_BOUNDS # => [note_index] end + +# HELPER PROCEDURES +# ================================================================================================= + +#! Builds the stack into the NOTE_METADATA word, encoding the note type and execution hint into a +#! single element. +#! Note that this procedure is only exported so it can be tested. It should not be called from +#! non-test code. +#! +#! Inputs: [tag, aux, note_type, execution_hint] +#! Outputs: [NOTE_METADATA] +#! +#! Where: +#! - tag is the note tag which can be used by the recipient(s) to identify notes intended for them. +#! - aux is the arbitrary user-defined value. +#! - note_type is the type of the note, which defines how the note is to be stored (e.g., on-chain +#! or off-chain). +#! - execution_hint is the hint which specifies when a note is ready to be consumed. +#! - NOTE_METADATA is the metadata associated with a note. +export.build_metadata + # Validate the note type. + # -------------------------------------------------------------------------------------------- + + # NOTE: encrypted notes are currently unsupported + dup.2 eq.PRIVATE_NOTE dup.3 eq.PUBLIC_NOTE or assert.err=ERR_NOTE_INVALID_TYPE + # => [tag, aux, note_type, execution_hint] + + # copy data to validate the tag + dup.2 push.PUBLIC_NOTE dup.1 dup.3 + # => [tag, note_type, public_note, note_type, tag, aux, note_type, execution_hint] + + u32assert.err=ERR_NOTE_TAG_MUST_BE_U32 + # => [tag, note_type, public_note, note_type, tag, aux, note_type, execution_hint] + + # enforce the note type depending on the tag' bits + u32shr.30 eq.LOCAL_ANY_PREFIX cdrop + assert_eq.err=ERR_NOTE_INVALID_NOTE_TYPE_FOR_NOTE_TAG_PREFIX + # => [tag, aux, note_type, execution_hint] + + # Split execution hint into its tag and payload parts as they are encoded in separate elements + # of the metadata. + # -------------------------------------------------------------------------------------------- + + # the execution_hint is laid out like this: [26 zero bits | payload (32 bits) | tag (6 bits)] + movup.3 + # => [execution_hint, tag, aux, note_type] + dup u32split drop + # => [execution_hint_lo, execution_hint, tag, aux, note_type] + + # mask out the lower 6 execution hint tag bits. + u32and.0x3f + # => [execution_hint_tag, execution_hint, tag, aux, note_type] + + # compute the payload by subtracting the tag value so the lower 6 bits are zero + # note that this results in the following layout: [26 zero bits | payload (32 bits) | 6 zero bits] + swap + # => [execution_hint, execution_hint_tag, tag, aux, note_type] + dup.1 + # => [execution_hint_tag, execution_hint, execution_hint_tag, tag, aux, note_type] + sub + # => [execution_hint_payload, execution_hint_tag, tag, aux, note_type] + + # Merge execution hint payload and note tag. + # -------------------------------------------------------------------------------------------- + + # we need to move the payload to the upper 32 bits of the felt + # we only need to shift by 26 bits because the payload is already shifted left by 6 bits + # we shift the payload by multiplying with 2^26 + # this results in the lower 32 bits being zero which is where the note tag will be added + mul.0x04000000 + # => [execution_hint_payload, execution_hint_tag, tag, aux, note_type] + + # add the tag to the payload to produce the merged value + movup.2 add + # => [note_tag_hint_payload, execution_hint_tag, aux, note_type] + + # Merge sender_id_suffix, note_type and execution_hint_tag. + # -------------------------------------------------------------------------------------------- + + exec.account::get_id + # => [sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux, note_type] + + movup.5 + # => [note_type, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux] + # multiply by 2^6 to shift the two note_type bits left by 6 bits. + mul.0x40 + # => [shifted_note_type, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux] + + # merge execution_hint_tag into the note_type + # this produces an 8-bit value with the layout: [note_type (2 bits) | execution_hint_tag (6 bits)] + movup.4 add + # => [merged_note_type_execution_hint_tag, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, aux] + + # merge sender_id_suffix into this value + movup.2 add + # => [sender_id_suffix_type_and_hint_tag, sender_id_prefix, note_tag_hint_payload, aux] + + # Rearrange elements to produce the final note metadata layout. + # -------------------------------------------------------------------------------------------- + + swap movdn.3 + # => [sender_id_suffix_type_and_hint_tag, note_tag_hint_payload, aux, sender_id_prefix] + swap + # => [note_tag_hint_payload, sender_id_suffix_type_and_hint_tag, aux, sender_id_prefix] + movup.2 + # => [NOTE_METADATA = [aux, note_tag_hint_payload, sender_id_suffix_type_and_hint_tag, sender_id_prefix]] +end + +#! Increments the number of output notes by one. Returns the index of the next note to be created. +#! +#! Inputs: [] +#! Outputs: [note_idx] +#! +#! Where: +#! - note_idx is the index of the next note to be created. +proc.increment_num_output_notes + # get the current number of output notes + exec.memory::get_num_output_notes + # => [note_idx] + + # assert that there is space for a new note + dup exec.constants::get_max_num_output_notes lt + assert.err=ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT + # => [note_idx] + + # increment the number of output notes + dup add.1 exec.memory::set_num_output_notes + # => [note_idx] +end + +#! Adds a fungible asset to a note. If the note already holds an asset issued by the same faucet id +#! the two quantities are summed up and the new quantity is stored at the old position in the note. +#! In the other case, the asset is stored at the next available position. +#! Returns the pointer to the note the asset was stored at. +#! +#! Inputs: [ASSET, note_ptr, num_of_assets, note_idx] +#! Outputs: [note_ptr] +#! +#! Where: +#! - ASSET is the fungible asset to be added to the note. +#! - note_ptr is the pointer to the note the asset will be added to. +#! - num_of_assets is the current number of assets. +#! - note_idx is the index of the note the asset will be added to. +#! +#! Panics if +#! - the summed amounts exceed the maximum amount of fungible assets. +proc.add_fungible_asset + dup.4 exec.memory::get_output_note_asset_data_ptr + # => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] + + # compute the pointer at which we should stop iterating + dup dup.7 mul.4 add + # => [end_asset_ptr, asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] + + # reorganize and pad the stack, prepare for the loop + movdn.5 movdn.5 padw dup.9 + # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + + # compute the loop latch + dup dup.10 neq + # => [latch, asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, + # note_idx] + + while.true + mem_loadw + # => [STORED_ASSET, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + + dup.4 eq + # => [are_equal, 0, 0, stored_amount, ASSET, end_asset_ptr, asset_ptr, note_ptr, + # num_of_assets, note_idx] + + if.true + # add the asset quantity, we don't overflow here, bc both ASSETs are valid. + movup.2 movup.6 add + # => [updated_amount, 0, 0, faucet_id, 0, 0, end_asset_ptr, asset_ptr, note_ptr, + # num_of_assets, note_idx] + + # check that we don't overflow bc we use lte + dup exec.asset::get_fungible_asset_max_amount lte + assert.err=ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED + # => [updated_amount, 0, 0, faucet_id, 0, 0, end_asset_ptr, asset_ptr, note_ptr, + # num_of_assets, note_idx] + + # prepare stack to store the "updated" ASSET'' with the new quantity + movdn.5 + # => [0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + + # decrease num_of_assets by 1 to offset incrementing it later + movup.9 sub.1 movdn.9 + # => [0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, num_of_assets - 1, note_idx] + + # end the loop we add 0's to the stack to have the correct number of elements + push.0.0 dup.9 push.0 + # => [0, asset_ptr, 0, 0, 0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, + # num_of_assets - 1, note_idx] + else + # => [0, 0, stored_amount, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, + # note_idx] + + # drop ASSETs and increment the asset pointer + movup.2 drop push.0.0 movup.9 add.4 dup movdn.10 + # => [asset_ptr + 4, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr + 4, note_ptr, + # num_of_assets, note_idx] + + # check if we reached the end of the loop + dup dup.10 neq + end + end + # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + # prepare stack for storing the ASSET + movdn.4 dropw + # => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + + # Store the fungible asset, either the combined ASSET or the new ASSET + mem_storew dropw drop drop + # => [note_ptr, num_of_assets, note_idx] + + # increase the number of assets in the note + swap add.1 dup.1 exec.memory::set_output_note_num_assets + # => [note_ptr, note_idx] +end + +#! Adds a non-fungible asset to a note at the next available position. +#! Returns the pointer to the note the asset was stored at. +#! +#! Inputs: [ASSET, note_ptr, num_of_assets, note_idx] +#! Outputs: [note_ptr, note_idx] +#! +#! Where: +#! - ASSET is the non-fungible asset to be added to the note. +#! - note_ptr is the pointer to the note the asset will be added to. +#! - num_of_assets is the current number of assets. +#! - note_idx is the index of the note the asset will be added to. +#! +#! Panics if: +#! - the non-fungible asset already exists in the note. +proc.add_non_fungible_asset + dup.4 exec.memory::get_output_note_asset_data_ptr + # => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] + + # compute the pointer at which we should stop iterating + dup dup.7 mul.4 add + # => [end_asset_ptr, asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] + + # reorganize and pad the stack, prepare for the loop + movdn.5 movdn.5 padw dup.9 + # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + + # compute the loop latch + dup dup.10 neq + # => [latch, asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, + # note_idx] + + while.true + # load the asset and compare + mem_loadw eqw assertz.err=ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS + # => [ASSET', ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + + # drop ASSET' and increment the asset pointer + dropw movup.5 add.4 dup movdn.6 padw movup.4 + # => [asset_ptr + 4, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr + 4, note_ptr, + # num_of_assets, note_idx] + + # check if we reached the end of the loop + dup dup.10 neq + end + # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + + # prepare stack for storing the ASSET + movdn.4 dropw + # => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] + + # end of the loop reached, no error so we can store the non-fungible asset + mem_storew dropw drop drop + # => [note_ptr, num_of_assets, note_idx] + + # increase the number of assets in the note + swap add.1 dup.1 exec.memory::set_output_note_num_assets + # => [note_ptr, note_idx] +end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/tx.masm b/crates/miden-lib/asm/kernels/transaction/lib/tx.masm index 96c8fd3c30..e7e137c885 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/tx.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/tx.masm @@ -1,83 +1,20 @@ -use.$kernel::account -use.$kernel::asset -use.$kernel::constants use.$kernel::memory use.$kernel::note # CONSTANTS # ================================================================================================= -# Constants for different note types -const.PUBLIC_NOTE=1 # 0b01 -const.PRIVATE_NOTE=2 # 0b10 -const.ENCRYPTED_NOTE=3 # 0b11 - -# Two raised to the power of 38 (2^38), used for shifting the note type value -const.TWO_POW_38=274877906944 - # Max value for U16, used as the upper limit for expiration block delta const.EXPIRY_UPPER_LIMIT=0xFFFF+1 -# The note type must be PUBLIC, unless the high bits are `0b11`. (See the table below.) -const.LOCAL_ANY_PREFIX=3 # 0b11 - # Max U32 value, used for initializing the expiration block number const.MAX_BLOCK_NUM=0xFFFFFFFF # ERRORS # ================================================================================================= -const.ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT="number of output notes in the transaction exceeds the maximum limit of 1024" - -const.ERR_NOTE_INVALID_TYPE="invalid note type" - -# The 2 highest bits in the u32 tag have the following meaning: -# -# | Prefix | Name | [`NoteExecutionMode`] | Target | Allowed [`NoteType`] | -# | :----: | :--------------------: | :-------------------: | :----------------------: | :------------------: | -# | `0b00` | `NetworkAccount` | Network | Network Account | [`NoteType::Public`] | -# | `0b01` | `NetworkUseCase` | Network | Use case | [`NoteType::Public`] | -# | `0b10` | `LocalPublicAny` | Local | Any | [`NoteType::Public`] | -# | `0b11` | `LocalAny` | Local | Any | Any | -# -# Execution: Is a hint for the network, to check if the note can be consumed by a network controlled -# account -# Target: Is a hint for the type of target. Use case means the note may be consumed by anyone, -# specific means there is a specific target for the note (the target may be a public key, a user -# that knows some secret, or a specific account ID) -# -# Only the note type from the above list is enforced. The other values are only hints intended as a -# best effort optimization strategy. A badly formatted note may 1. not be consumed because honest -# users won't see the note 2. generate slightly more load as extra validation is performed for the -# invalid tags. None of these scenarios have any significant impact. - -const.ERR_NOTE_INVALID_NOTE_TYPE_FOR_NOTE_TAG_PREFIX="invalid note type for the given note tag prefix" - -const.ERR_NOTE_TAG_MUST_BE_U32="the note's tag must fit into a u32 so the 32 most significant bits must be zero" - -const.ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED="adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807" - -const.ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS="non-fungible asset that already exists in the note cannot be added again" - -const.ERR_NOTE_INVALID_INDEX="failed to find note at the given index; index must be within [0, num_of_notes]" - -const.ERR_NOTE_NETWORK_EXECUTION_DOES_NOT_TARGET_NETWORK_ACCOUNT="network execution mode with a specific target can only target network accounts" - const.ERR_TX_INVALID_EXPIRATION_DELTA="transaction expiration block delta must be within 0x1 and 0xFFFF" -# EVENTS -# ================================================================================================= - -# Event emitted before a new note is created. -const.NOTE_BEFORE_CREATED_EVENT=131083 -# Event emitted after a new note is created. -const.NOTE_AFTER_CREATED_EVENT=131084 - -# Event emitted before an ASSET is added to a note -const.NOTE_BEFORE_ADD_ASSET_EVENT=131085 -# Event emitted after an ASSET is added to a note -const.NOTE_AFTER_ADD_ASSET_EVENT=131086 - # PROCEDURES # ================================================================================================= @@ -147,88 +84,7 @@ export.memory::get_num_input_notes #! - num_output_notes is the number of output notes created in this transaction so far. export.memory::get_num_output_notes -#! Increments the number of output notes by one. Returns the index of the next note to be created. -#! -#! Inputs: [] -#! Outputs: [note_idx] -#! -#! Where: -#! - note_idx is the index of the next note to be created. -proc.increment_num_output_notes - # get the current number of output notes - exec.memory::get_num_output_notes - # => [note_idx] - - # assert that there is space for a new note - dup exec.constants::get_max_num_output_notes lt - assert.err=ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT - # => [note_idx] - - # increment the number of output notes - dup add.1 exec.memory::set_num_output_notes - # => [note_idx] -end - -#! Adds a non-fungible asset to a note at the next available position. -#! Returns the pointer to the note the asset was stored at. -#! -#! Inputs: [ASSET, note_ptr, num_of_assets, note_idx] -#! Outputs: [note_ptr, note_idx] -#! -#! Where: -#! - ASSET is the non-fungible asset to be added to the note. -#! - note_ptr is the pointer to the note the asset will be added to. -#! - num_of_assets is the current number of assets. -#! - note_idx is the index of the note the asset will be added to. -#! -#! Panics if: -#! - the non-fungible asset already exists in the note. -proc.add_non_fungible_asset_to_note - dup.4 exec.memory::get_output_note_asset_data_ptr - # => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] - - # compute the pointer at which we should stop iterating - dup dup.7 mul.4 add - # => [end_asset_ptr, asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] - - # reorganize and pad the stack, prepare for the loop - movdn.5 movdn.5 padw dup.9 - # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - # compute the loop latch - dup dup.10 neq - # => [latch, asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, - # note_idx] - - while.true - # load the asset and compare - mem_loadw eqw assertz.err=ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS - # => [ASSET', ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - # drop ASSET' and increment the asset pointer - dropw movup.5 add.4 dup movdn.6 padw movup.4 - # => [asset_ptr + 4, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr + 4, note_ptr, - # num_of_assets, note_idx] - - # check if we reached the end of the loop - dup dup.10 neq - end - # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - # prepare stack for storing the ASSET - movdn.4 dropw - # => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - # end of the loop reached, no error so we can store the non-fungible asset - mem_storew dropw drop drop - # => [note_ptr, num_of_assets, note_idx] - - # increase the number of assets in the note - swap add.1 dup.1 exec.memory::set_output_note_num_assets - # => [note_ptr, note_idx] -end - -#! Updates the transaction expiration block number. +#! Updates the transaction expiration block delta. #! #! The input block_height_delta is added to the block reference number in order to output an upper #! limit at which the transaction will be considered valid (not expired). @@ -239,7 +95,7 @@ end #! #! Where: #! - block_height_delta is the desired expiration time delta (1 to 0xFFFF). -export.update_expiration_block_num +export.update_expiration_block_delta # Ensure block_height_delta is between 1 and 0xFFFF (inclusive) dup neq.0 assert.err=ERR_TX_INVALID_EXPIRATION_DELTA # => [block_height_delta] @@ -287,308 +143,3 @@ export.get_expiration_delta exec.get_block_number sub end end - -#! Adds a fungible asset to a note. If the note already holds an asset issued by the same faucet id -#! the two quantities are summed up and the new quantity is stored at the old position in the note. -#! In the other case, the asset is stored at the next available position. -#! Returns the pointer to the note the asset was stored at. -#! -#! Inputs: [ASSET, note_ptr, num_of_assets, note_idx] -#! Outputs: [note_ptr] -#! -#! Where: -#! - ASSET is the fungible asset to be added to the note. -#! - note_ptr is the pointer to the note the asset will be added to. -#! - num_of_assets is the current number of assets. -#! - note_idx is the index of the note the asset will be added to. -#! -#! Panics if -#! - the summed amounts exceed the maximum amount of fungible assets. -proc.add_fungible_asset_to_note - dup.4 exec.memory::get_output_note_asset_data_ptr - # => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] - - # compute the pointer at which we should stop iterating - dup dup.7 mul.4 add - # => [end_asset_ptr, asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] - - # reorganize and pad the stack, prepare for the loop - movdn.5 movdn.5 padw dup.9 - # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - # compute the loop latch - dup dup.10 neq - # => [latch, asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, - # note_idx] - - while.true - mem_loadw - # => [STORED_ASSET, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - dup.4 eq - # => [are_equal, 0, 0, stored_amount, ASSET, end_asset_ptr, asset_ptr, note_ptr, - # num_of_assets, note_idx] - - if.true - # add the asset quantity, we don't overflow here, bc both ASSETs are valid. - movup.2 movup.6 add - # => [updated_amount, 0, 0, faucet_id, 0, 0, end_asset_ptr, asset_ptr, note_ptr, - # num_of_assets, note_idx] - - # check that we don't overflow bc we use lte - dup exec.asset::get_fungible_asset_max_amount lte - assert.err=ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED - # => [updated_amount, 0, 0, faucet_id, 0, 0, end_asset_ptr, asset_ptr, note_ptr, - # num_of_assets, note_idx] - - # prepare stack to store the "updated" ASSET'' with the new quantity - movdn.5 - # => [0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - # decrease num_of_assets by 1 to offset incrementing it later - movup.9 sub.1 movdn.9 - # => [0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, num_of_assets - 1, note_idx] - - # end the loop we add 0's to the stack to have the correct number of elements - push.0.0 dup.9 push.0 - # => [0, asset_ptr, 0, 0, 0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, - # num_of_assets - 1, note_idx] - else - # => [0, 0, stored_amount, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, - # note_idx] - - # drop ASSETs and increment the asset pointer - movup.2 drop push.0.0 movup.9 add.4 dup movdn.10 - # => [asset_ptr + 4, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr + 4, note_ptr, - # num_of_assets, note_idx] - - # check if we reached the end of the loop - dup dup.10 neq - end - end - # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - # prepare stack for storing the ASSET - movdn.4 dropw - # => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] - - # Store the fungible asset, either the combined ASSET or the new ASSET - mem_storew dropw drop drop - # => [note_ptr, num_of_assets, note_idx] - - # increase the number of assets in the note - swap add.1 dup.1 exec.memory::set_output_note_num_assets - # => [note_ptr, note_idx] -end - -#! Builds the stack into the NOTE_METADATA word, encoding the note type and execution hint into a -#! single element. -#! Note that this procedure is only exported so it can be tested. It should not be called from -#! non-test code. -#! -#! Inputs: [tag, aux, note_type, execution_hint] -#! Outputs: [NOTE_METADATA] -#! -#! Where: -#! - tag is the note tag which can be used by the recipient(s) to identify notes intended for them. -#! - aux is the arbitrary user-defined value. -#! - note_type is the type of the note, which defines how the note is to be stored (e.g., on-chain -#! or off-chain). -#! - execution_hint is the hint which specifies when a note is ready to be consumed. -#! - NOTE_METADATA is the metadata associated with a note. -export.build_note_metadata - - # Validate the note type. - # -------------------------------------------------------------------------------------------- - - # NOTE: encrypted notes are currently unsupported - dup.2 eq.PRIVATE_NOTE dup.3 eq.PUBLIC_NOTE or assert.err=ERR_NOTE_INVALID_TYPE - # => [tag, aux, note_type, execution_hint] - - # copy data to validate the tag - dup.2 push.PUBLIC_NOTE dup.1 dup.3 - # => [tag, note_type, public_note, note_type, tag, aux, note_type, execution_hint] - - u32assert.err=ERR_NOTE_TAG_MUST_BE_U32 - # => [tag, note_type, public_note, note_type, tag, aux, note_type, execution_hint] - - # enforce the note type depending on the tag' bits - u32shr.30 eq.LOCAL_ANY_PREFIX cdrop - assert_eq.err=ERR_NOTE_INVALID_NOTE_TYPE_FOR_NOTE_TAG_PREFIX - # => [tag, aux, note_type, execution_hint] - - # Split execution hint into its tag and payload parts as they are encoded in separate elements - # of the metadata. - # -------------------------------------------------------------------------------------------- - - # the execution_hint is laid out like this: [26 zero bits | payload (32 bits) | tag (6 bits)] - movup.3 - # => [execution_hint, tag, aux, note_type] - dup u32split drop - # => [execution_hint_lo, execution_hint, tag, aux, note_type] - - # mask out the lower 6 execution hint tag bits. - u32and.0x3f - # => [execution_hint_tag, execution_hint, tag, aux, note_type] - - # compute the payload by subtracting the tag value so the lower 6 bits are zero - # note that this results in the following layout: [26 zero bits | payload (32 bits) | 6 zero bits] - swap - # => [execution_hint, execution_hint_tag, tag, aux, note_type] - dup.1 - # => [execution_hint_tag, execution_hint, execution_hint_tag, tag, aux, note_type] - sub - # => [execution_hint_payload, execution_hint_tag, tag, aux, note_type] - - # Merge execution hint payload and note tag. - # -------------------------------------------------------------------------------------------- - - # we need to move the payload to the upper 32 bits of the felt - # we only need to shift by 26 bits because the payload is already shifted left by 6 bits - # we shift the payload by multiplying with 2^26 - # this results in the lower 32 bits being zero which is where the note tag will be added - mul.0x04000000 - # => [execution_hint_payload, execution_hint_tag, tag, aux, note_type] - - # add the tag to the payload to produce the merged value - movup.2 add - # => [note_tag_hint_payload, execution_hint_tag, aux, note_type] - - # Merge sender_id_suffix, note_type and execution_hint_tag. - # -------------------------------------------------------------------------------------------- - - exec.account::get_id - # => [sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux, note_type] - - movup.5 - # => [note_type, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux] - # multiply by 2^6 to shift the two note_type bits left by 6 bits. - mul.0x40 - # => [shifted_note_type, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux] - - # merge execution_hint_tag into the note_type - # this produces an 8-bit value with the layout: [note_type (2 bits) | execution_hint_tag (6 bits)] - movup.4 add - # => [merged_note_type_execution_hint_tag, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, aux] - - # merge sender_id_suffix into this value - movup.2 add - # => [sender_id_suffix_type_and_hint_tag, sender_id_prefix, note_tag_hint_payload, aux] - - # Rearrange elements to produce the final note metadata layout. - # -------------------------------------------------------------------------------------------- - - swap movdn.3 - # => [sender_id_suffix_type_and_hint_tag, note_tag_hint_payload, aux, sender_id_prefix] - swap - # => [note_tag_hint_payload, sender_id_suffix_type_and_hint_tag, aux, sender_id_prefix] - movup.2 - # => [NOTE_METADATA = [aux, note_tag_hint_payload, sender_id_suffix_type_and_hint_tag, sender_id_prefix]] -end - -#! Creates a new note and returns the index of the note. -#! -#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT] -#! Outputs: [note_idx] -#! -#! Where: -#! - tag is the note tag which can be used by the recipient(s) to identify notes intended for them. -#! - aux is the arbitrary user-defined value. -#! - note_type is the type of the note, which defines how the note is to be stored (e.g., on-chain -#! or off-chain). -#! - execution_hint is the hint which specifies when a note is ready to be consumed. -#! - RECIPIENT defines spend conditions for the note. -#! - note_idx is the index of the created note. -#! -#! Panics if: -#! - the note_type is not valid. -#! - the note_tag is not an u32. -#! - the note_tag starts with anything but 0b11 and note_type is not public. -#! - the number of output notes exceeds the maximum limit of 1024. -export.create_note - emit.NOTE_BEFORE_CREATED_EVENT - - exec.build_note_metadata - # => [NOTE_METADATA, RECIPIENT] - - # get the index for the next note to be created and increment counter - exec.increment_num_output_notes dup movdn.9 - # => [note_idx, NOTE_METADATA, RECIPIENT, note_idx] - - # get a pointer to the memory address at which the note will be stored - exec.memory::get_output_note_ptr - # => [note_ptr, NOTE_METADATA, RECIPIENT, note_idx] - - movdn.4 - # => [NOTE_METADATA, note_ptr, RECIPIENT, note_idx] - - # emit event to signal that a new note is created - emit.NOTE_AFTER_CREATED_EVENT - - # set the metadata for the output note - dup.4 exec.memory::set_output_note_metadata dropw - # => [note_ptr, RECIPIENT, note_idx] - - # set the RECIPIENT for the output note - exec.memory::set_output_note_recipient dropw - # => [note_idx] -end - -#! Adds the ASSET to the note specified by the index. -#! -#! Inputs: [note_idx, ASSET] -#! Outputs: [note_idx] -#! -#! Where: -#! - note_idx is the index of the note to which the asset is added. -#! - ASSET can be a fungible or non-fungible asset. -#! -#! Panics if: -#! - the ASSET is malformed (e.g., invalid faucet ID). -#! - the max amount of fungible assets is exceeded. -#! - the non-fungible asset already exists in the note. -#! - the total number of ASSETs exceeds the maximum of 256. -export.add_asset_to_note - # check if the note exists, it must be within [0, num_of_notes] - dup exec.memory::get_num_output_notes lte assert.err=ERR_NOTE_INVALID_INDEX - # => [note_idx, ASSET] - - # get a pointer to the memory address of the note at which the asset will be stored - dup movdn.5 exec.memory::get_output_note_ptr - # => [note_ptr, ASSET, note_idx] - - # get current num of assets - dup exec.memory::get_output_note_num_assets movdn.5 - # => [note_ptr, ASSET, num_of_assets, note_idx] - - # validate the ASSET - movdn.4 exec.asset::validate_asset - # => [ASSET, note_ptr, num_of_assets, note_idx] - - # emit event to signal that a new asset is going to be added to the note. - emit.NOTE_BEFORE_ADD_ASSET_EVENT - # => [ASSET, note_ptr, num_of_assets, note_idx] - - # Check if ASSET to add is fungible - exec.asset::is_fungible_asset - # => [is_fungible_asset?, ASSET, note_ptr, num_of_assets, note_idx] - - if.true - # ASSET to add is fungible - exec.add_fungible_asset_to_note - # => [note_ptr, note_idx] - else - # ASSET to add is non-fungible - exec.add_non_fungible_asset_to_note - # => [note_ptr, note_idx] - end - # => [note_ptr, note_idx] - - # update the assets commitment dirty flag to signal that the current assets commitment is not - # valid anymore - push.1 swap exec.memory::set_output_note_dirty_flag - # => [note_idx] - - # emit event to signal that a new asset was added to the note. - emit.NOTE_AFTER_ADD_ASSET_EVENT - # => [note_idx] -end diff --git a/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm b/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm index 8c71a03328..69a6363b71 100644 --- a/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm +++ b/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm @@ -7,10 +7,10 @@ # - max_supply is the maximum supply of the token. # - decimals are the decimals of the token. # - token_symbol as three chars encoded in a Felt. + use.miden::account use.miden::faucet -use.miden::tx -use.miden::contracts::auth::basic +use.miden::output_note # CONSTANTS # ================================================================================================= @@ -80,11 +80,11 @@ export.distribute.4 # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] # create a note - exec.tx::create_note + exec.output_note::create # => [note_idx, pad(15)] # load the ASSET and add it to the note - movdn.4 loc_loadw.0 exec.tx::add_asset_to_note movup.4 + movdn.4 loc_loadw.0 exec.output_note::add_asset movup.4 # => [note_idx, ASSET, pad(11)] end diff --git a/crates/miden-lib/asm/miden/contracts/wallets/basic.masm b/crates/miden-lib/asm/miden/contracts/wallets/basic.masm index 5491215b39..ae21b2d8e6 100644 --- a/crates/miden-lib/asm/miden/contracts/wallets/basic.masm +++ b/crates/miden-lib/asm/miden/contracts/wallets/basic.masm @@ -1,5 +1,5 @@ use.miden::account -use.miden::tx +use.miden::output_note # CONSTANTS # ================================================================================================= @@ -53,6 +53,6 @@ export.move_asset_to_note exec.account::remove_asset # => [ASSET, note_idx, pad(11)] - exec.tx::add_asset_to_note + exec.output_note::add_asset # => [ASSET, note_idx, pad(11) ...] end diff --git a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm index bd00393ace..3f3eb390a9 100644 --- a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm +++ b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm @@ -2,6 +2,7 @@ # ------------------------------------------------------------------------------------------------- ### Account ##################################### + # Entire account commitment const.ACCOUNT_GET_INITIAL_COMMITMENT_OFFSET=0 const.ACCOUNT_COMPUTE_CURRENT_COMMITMENT_OFFSET=1 @@ -41,6 +42,7 @@ const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=20 const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=21 ### Faucet ###################################### + const.FAUCET_MINT_ASSET_OFFSET=22 const.FAUCET_BURN_ASSET_OFFSET=23 const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=24 @@ -57,22 +59,21 @@ const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=30 const.INPUT_NOTE_GET_RECIPIENT_OFFSET=31 # output notes -const.OUTPUT_NOTE_GET_METADATA_OFFSET=32 -const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=33 -const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=34 -const.OUTPUT_NOTE_ADD_ASSET_OFFSET=35 +const.OUTPUT_NOTE_CREATE_OFFSET=32 +const.OUTPUT_NOTE_GET_METADATA_OFFSET=33 +const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=34 +const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=35 +const.OUTPUT_NOTE_ADD_ASSET_OFFSET=36 ### Tx ########################################## -# creation -const.TX_CREATE_NOTE_OFFSET=36 - -# input/output notes -const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=37 -const.TX_GET_NUM_INPUT_NOTES_OFFSET=38 +# input notes +const.TX_GET_NUM_INPUT_NOTES_OFFSET=37 +const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=38 -const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=39 -const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=40 +# output notes +const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=39 +const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=40 # block info const.TX_GET_BLOCK_COMMITMENT_OFFSET=41 @@ -85,7 +86,7 @@ const.TX_END_FOREIGN_CONTEXT_OFFSET=45 # expiration data const.TX_GET_EXPIRATION_DELTA_OFFSET=46 # accessor -const.TX_UPDATE_EXPIRATION_BLOCK_NUM_OFFSET=47 # mutator +const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=47 # mutator # ACCESSORS # ------------------------------------------------------------------------------------------------- @@ -406,30 +407,30 @@ export.faucet_is_non_fungible_asset_issued_offset push.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET end -### NOTE ######################################## +### OUTPUT NOTE ######################################## -#! Returns the offset of the `output_note_add_asset` kernel procedure. +#! Returns the offset of the `output_note_create` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `output_note_add_asset` kernel procedure required to get the +#! - proc_offset is the offset of the `output_note_create` kernel procedure required to get the #! address where this procedure is stored. -export.output_note_add_asset_offset - push.OUTPUT_NOTE_ADD_ASSET_OFFSET +export.output_note_create_offset + push.OUTPUT_NOTE_CREATE_OFFSET end -#! Returns the offset of the `input_note_get_assets_info` kernel procedure. +#! Returns the offset of the `output_note_add_asset` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `input_note_get_assets_info` kernel procedure required to get -#! the address where this procedure is stored. -export.input_note_get_assets_info_offset - push.INPUT_NOTE_GET_ASSETS_INFO_OFFSET +#! - proc_offset is the offset of the `output_note_add_asset` kernel procedure required to get the +#! address where this procedure is stored. +export.output_note_add_asset_offset + push.OUTPUT_NOTE_ADD_ASSET_OFFSET end #! Returns the offset of the `output_note_get_assets_info` kernel procedure. @@ -444,52 +445,66 @@ export.output_note_get_assets_info_offset push.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET end -#! Returns the offset of the `input_note_get_recipient` kernel procedure. +#! Returns the offset of the `output_note_get_recipient` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `input_note_get_recipient` kernel procedure required to get +#! - proc_offset is the offset of the `output_note_get_recipient` kernel procedure required to get #! the address where this procedure is stored. -export.input_note_get_recipient_offset - push.INPUT_NOTE_GET_RECIPIENT_OFFSET +export.output_note_get_recipient_offset + push.OUTPUT_NOTE_GET_RECIPIENT_OFFSET end -#! Returns the offset of the `output_note_get_recipient` kernel procedure. +#! Returns the offset of the `output_note_get_metadata` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `output_note_get_recipient` kernel procedure required to get +#! - proc_offset is the offset of the `output_note_get_metadata` kernel procedure required to get #! the address where this procedure is stored. -export.output_note_get_recipient_offset - push.OUTPUT_NOTE_GET_RECIPIENT_OFFSET +export.output_note_get_metadata_offset + push.OUTPUT_NOTE_GET_METADATA_OFFSET end -#! Returns the offset of the `input_note_get_metadata` kernel procedure. +### INPUT NOTE ######################################## + +#! Returns the offset of the `input_note_get_assets_info` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `input_note_get_metadata` kernel procedure required to get +#! - proc_offset is the offset of the `input_note_get_assets_info` kernel procedure required to get +#! the address where this procedure is stored. +export.input_note_get_assets_info_offset + push.INPUT_NOTE_GET_ASSETS_INFO_OFFSET +end + +#! Returns the offset of the `input_note_get_recipient` kernel procedure. +#! +#! Inputs: [] +#! Outputs: [proc_offset] +#! +#! Where: +#! - proc_offset is the offset of the `input_note_get_recipient` kernel procedure required to get #! the address where this procedure is stored. -export.input_note_get_metadata_offset - push.INPUT_NOTE_GET_METADATA_OFFSET +export.input_note_get_recipient_offset + push.INPUT_NOTE_GET_RECIPIENT_OFFSET end -#! Returns the offset of the `output_note_get_metadata` kernel procedure. +#! Returns the offset of the `input_note_get_metadata` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `output_note_get_metadata` kernel procedure required to get +#! - proc_offset is the offset of the `input_note_get_metadata` kernel procedure required to get #! the address where this procedure is stored. -export.output_note_get_metadata_offset - push.OUTPUT_NOTE_GET_METADATA_OFFSET +export.input_note_get_metadata_offset + push.INPUT_NOTE_GET_METADATA_OFFSET end #! Returns the offset of the `input_note_get_serial_number` kernel procedure. @@ -530,18 +545,6 @@ end ### TRANSACTION ################################# -#! Returns the offset of the `tx_create_note` kernel procedure. -#! -#! Inputs: [] -#! Outputs: [proc_offset] -#! -#! Where: -#! - proc_offset is the offset of the `tx_create_note` kernel procedure required to get the address -#! where this procedure is stored. -export.tx_create_note_offset - push.TX_CREATE_NOTE_OFFSET -end - #! Returns the offset of the `tx_get_input_notes_commitment` kernel procedure. #! #! Inputs: [] @@ -572,7 +575,7 @@ end #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `tx_get_num_input_notes` kernel procedure required to get the +#! - proc_offset is the offset of the `tx_get_num_input_notes` kernel procedure required to get the #! address where this procedure is stored. export.tx_get_num_input_notes_offset push.TX_GET_NUM_INPUT_NOTES_OFFSET @@ -584,7 +587,7 @@ end #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `tx_get_num_output_notes` kernel procedure required to get the +#! - proc_offset is the offset of the `tx_get_num_output_notes` kernel procedure required to get the #! address where this procedure is stored. export.tx_get_num_output_notes_offset push.TX_GET_NUM_OUTPUT_NOTES_OFFSET @@ -650,16 +653,16 @@ export.tx_end_foreign_context_offset push.TX_END_FOREIGN_CONTEXT_OFFSET end -#! Returns the offset of the `tx_update_expiration_block_num` kernel procedure. +#! Returns the offset of the `tx_update_expiration_block_delta` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `tx_update_expiration_block_num` kernel procedure required to -#! get the address where this procedure is stored. -export.tx_update_expiration_block_num_offset - push.TX_UPDATE_EXPIRATION_BLOCK_NUM_OFFSET +#! - proc_offset is the offset of the `tx_update_expiration_block_delta` kernel procedure required +#! to get the address where this procedure is stored. +export.tx_update_expiration_block_delta_offset + push.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET end #! Returns the offset of the `tx_get_expiration_delta` kernel procedure. diff --git a/crates/miden-lib/asm/miden/note.masm b/crates/miden-lib/asm/miden/note.masm index 78499f7077..a6761faed3 100644 --- a/crates/miden-lib/asm/miden/note.masm +++ b/crates/miden-lib/asm/miden/note.masm @@ -87,7 +87,7 @@ export.get_recipient # => [RECIPIENT] end -#! Writes the note's inputs to `dest_ptr`. +#! Writes the active note's inputs to memory starting at the specified address. #! #! Inputs: #! Stack: [dest_ptr] @@ -200,6 +200,39 @@ export.get_serial_number # => [SERIAL_NUMBER] end +#! Returns the script root of the note currently being processed. +#! +#! Inputs: [] +#! Outputs: [SCRIPT_ROOT] +#! +#! Where: +#! - SCRIPT_ROOT is the script root of the note currently being processed. +#! +#! Panics if: +#! - no note is being processed. +#! +#! Invocation: exec +export.get_script_root + # pad the stack + padw padw padw push.0.0 + # => [pad(14)] + + # push the flag indicating that we want to request script root from the currently processing + # note + push.1 + # => [is_current_note = 1, pad(14)] + + exec.kernel_proc_offsets::input_note_get_script_root_offset + # => [offset, is_current_note = 1, pad(14)] + + syscall.exec_kernel_proc + # => [SCRIPT_ROOT, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [SCRIPT_ROOT] +end + #! Computes the commitment to the output note inputs starting at the specified memory address. #! #! This procedure checks that the provided number of note inputs is within limits and then computes @@ -237,39 +270,6 @@ export.compute_inputs_commitment # => [INPUTS_COMMITMENT] end -#! Returns the script root of the note currently being processed. -#! -#! Inputs: [] -#! Outputs: [SCRIPT_ROOT] -#! -#! Where: -#! - SCRIPT_ROOT is the script root of the note currently being processed. -#! -#! Panics if: -#! - no note is being processed. -#! -#! Invocation: exec -export.get_script_root - # pad the stack - padw padw padw push.0.0 - # => [pad(14)] - - # push the flag indicating that we want to request script root from the currently processing - # note - push.1 - # => [is_current_note = 1, pad(14)] - - exec.kernel_proc_offsets::input_note_get_script_root_offset - # => [offset, is_current_note = 1, pad(14)] - - syscall.exec_kernel_proc - # => [SCRIPT_ROOT, pad(12)] - - # clean the stack - swapdw dropw dropw swapw dropw - # => [SCRIPT_ROOT] -end - #! Returns the max allowed number of input values per note. #! #! Stack: [] @@ -367,7 +367,6 @@ export.write_assets_to_memory # AS => [] end - #! Builds the recipient hash from note inputs, script root, and serial number. #! #! This procedure computes the commitment of the note inputs and then uses it to calculate the note diff --git a/crates/miden-lib/asm/miden/output_note.masm b/crates/miden-lib/asm/miden/output_note.masm index 0054ce8281..cb3877d5ed 100644 --- a/crates/miden-lib/asm/miden/output_note.masm +++ b/crates/miden-lib/asm/miden/output_note.masm @@ -5,6 +5,37 @@ use.std::mem # PROCEDURES # ================================================================================================= +#! Creates a new note and returns the index of the note. +#! +#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT] +#! Outputs: [note_idx] +#! +#! Where: +#! - tag is the tag to be included in the note. +#! - aux is the auxiliary metadata to be included in the note. +#! - note_type is the storage type of the note. +#! - execution_hint is the note's execution hint. +#! - RECIPIENT is the recipient of the note. +#! - note_idx is the index of the created note. +#! +#! Invocation: exec +export.create + # pad the stack before the syscall to prevent accidental modification of the deeper stack + # elements + padw padw swapdw movup.8 drop + # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] + + exec.kernel_proc_offsets::output_note_create_offset + # => [offset, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] + + syscall.exec_kernel_proc + # => [note_idx, pad(15)] + + # remove excess PADs from the stack + swapdw dropw dropw movdn.7 dropw drop drop drop + # => [note_idx] +end + #! Returns the information about assets in the output note with the specified index. #! #! This information can then be used to retrieve the actual assets from the advice map. @@ -77,6 +108,33 @@ export.get_assets # => [num_assets, dest_ptr, note_index] end +#! Adds the ASSET to the note specified by the index. +#! +#! Inputs: [ASSET, note_idx] +#! Outputs: [ASSET, note_idx] +#! +#! Where: +#! - note_idx is the index of the note to which the asset is added. +#! - ASSET can be a fungible or non-fungible asset. +#! +#! Invocation: exec +export.add_asset + movup.4 exec.kernel_proc_offsets::output_note_add_asset_offset + # => [offset, note_idx, ASSET] + + # pad the stack before the syscall to prevent accidental modification of the deeper stack + # elements + push.0.0 movdn.7 movdn.7 padw padw swapdw + # => [offset, note_idx, ASSET, pad(10)] + + syscall.exec_kernel_proc + # => [note_idx, ASSET, pad(11)] + + # remove excess PADs from the stack + swapdw dropw dropw swapw movdn.7 drop drop drop movdn.4 + # => [ASSET, note_idx] +end + #! Returns the recipient of the output note with the specified index. #! #! Inputs: [note_index] diff --git a/crates/miden-lib/asm/miden/tx.masm b/crates/miden-lib/asm/miden/tx.masm index 91a826a386..a1c6addea9 100644 --- a/crates/miden-lib/asm/miden/tx.masm +++ b/crates/miden-lib/asm/miden/tx.masm @@ -199,64 +199,6 @@ export.get_num_output_notes # => [num_output_notes] end -#! Creates a new note and returns the index of the note. -#! -#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT] -#! Outputs: [note_idx] -#! -#! Where: -#! - tag is the tag to be included in the note. -#! - aux is the auxiliary metadata to be included in the note. -#! - note_type is the storage type of the note. -#! - execution_hint is the note's execution hint. -#! - RECIPIENT is the recipient of the note. -#! - note_idx is the index of the created note. -#! -#! Invocation: exec -export.create_note - # pad the stack before the syscall to prevent accidental modification of the deeper stack - # elements - padw padw swapdw movup.8 drop - # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] - - exec.kernel_proc_offsets::tx_create_note_offset - # => [offset, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] - - syscall.exec_kernel_proc - # => [note_idx, pad(15)] - - # remove excess PADs from the stack - swapdw dropw dropw movdn.7 dropw drop drop drop - # => [note_idx] -end - -#! Adds the ASSET to the note specified by the index. -#! -#! Inputs: [ASSET, note_idx] -#! Outputs: [ASSET, note_idx] -#! -#! Where: -#! - note_idx is the index of the note to which the asset is added. -#! - ASSET can be a fungible or non-fungible asset. -#! -#! Invocation: exec -export.add_asset_to_note - movup.4 exec.kernel_proc_offsets::output_note_add_asset_offset - # => [offset, note_idx, ASSET] - - # pad the stack before the syscall to prevent accidental modification of the deeper stack - # elements - push.0.0 movdn.7 movdn.7 padw padw swapdw - # => [offset, note_idx, ASSET, pad(10)] - - syscall.exec_kernel_proc - # => [note_idx, ASSET, pad(11)] - - # remove excess PADs from the stack - swapdw dropw dropw swapw movdn.7 drop drop drop movdn.4 - # => [ASSET, note_idx] -end - #! Executes the provided procedure against the foreign account. #! #! WARNING: the procedure to be invoked can not have more than 15 inputs and it can not return more @@ -327,7 +269,7 @@ end #! #! Annotation hint: is not used anywhere export.update_expiration_block_delta - exec.kernel_proc_offsets::tx_update_expiration_block_num_offset + exec.kernel_proc_offsets::tx_update_expiration_block_delta_offset # => [offset, expiration_delta, ...] # pad the stack diff --git a/crates/miden-lib/asm/note_scripts/P2IDE.masm b/crates/miden-lib/asm/note_scripts/P2IDE.masm index 6482825723..1771cef85e 100644 --- a/crates/miden-lib/asm/note_scripts/P2IDE.masm +++ b/crates/miden-lib/asm/note_scripts/P2IDE.masm @@ -2,7 +2,6 @@ use.miden::account use.miden::account_id use.miden::note use.miden::tx -use.note_scripts::utils # ERRORS # ================================================================================================= diff --git a/crates/miden-lib/asm/note_scripts/SWAP.masm b/crates/miden-lib/asm/note_scripts/SWAP.masm index 15e13e1c32..cb8e79a40a 100644 --- a/crates/miden-lib/asm/note_scripts/SWAP.masm +++ b/crates/miden-lib/asm/note_scripts/SWAP.masm @@ -1,5 +1,5 @@ use.miden::note -use.miden::tx +use.miden::output_note use.miden::contracts::wallets::basic->wallet # CONSTANTS @@ -66,7 +66,7 @@ begin # => [tag, aux, note_type, execution_hint, PAYBACK_NOTE_RECIPIENT, REQUESTED_ASSET] # create payback P2ID note - exec.tx::create_note + exec.output_note::create # => [note_idx, REQUESTED_ASSET] movdn.4 diff --git a/crates/miden-lib/src/account/interface/component.rs b/crates/miden-lib/src/account/interface/component.rs index 374f7a72d4..d52356c1ad 100644 --- a/crates/miden-lib/src/account/interface/component.rs +++ b/crates/miden-lib/src/account/interface/component.rs @@ -174,7 +174,7 @@ impl AccountComponentInterface { /// /// ```masm /// push.{note_information} - /// call.::miden::tx::create_note + /// call.::miden::output_note::create /// /// push.{note asset} /// call.::miden::contracts::wallets::basic::move_asset_to_note dropw @@ -248,7 +248,7 @@ impl AccountComponentInterface { // stack => [] }, AccountComponentInterface::BasicWallet => { - body.push_str("call.::miden::tx::create_note\n"); + body.push_str("call.::miden::output_note::create\n"); // stack => [note_idx] for asset in partial_note.assets().iter() { diff --git a/crates/miden-lib/src/errors/tx_kernel_errors.rs b/crates/miden-lib/src/errors/tx_kernel_errors.rs index 89a79a523f..f27b38f709 100644 --- a/crates/miden-lib/src/errors/tx_kernel_errors.rs +++ b/crates/miden-lib/src/errors/tx_kernel_errors.rs @@ -167,8 +167,6 @@ pub const ERR_NOTE_INVALID_NOTE_TYPE_FOR_NOTE_TAG_PREFIX: MasmError = MasmError: pub const ERR_NOTE_INVALID_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("the specified number of note inputs does not match the actual number"); /// Error Message: "invalid note type" pub const ERR_NOTE_INVALID_TYPE: MasmError = MasmError::from_static_str("invalid note type"); -/// Error Message: "network execution mode with a specific target can only target network accounts" -pub const ERR_NOTE_NETWORK_EXECUTION_DOES_NOT_TARGET_NETWORK_ACCOUNT: MasmError = MasmError::from_static_str("network execution mode with a specific target can only target network accounts"); /// Error Message: "number of assets in a note exceed 255" pub const ERR_NOTE_NUM_OF_ASSETS_EXCEED_LIMIT: MasmError = MasmError::from_static_str("number of assets in a note exceed 255"); /// Error Message: "the note's tag must fit into a u32 so the 32 most significant bits must be zero" diff --git a/crates/miden-lib/src/testing/mock_util_lib.rs b/crates/miden-lib/src/testing/mock_util_lib.rs index 1d569ffcbb..92fc52a041 100644 --- a/crates/miden-lib/src/testing/mock_util_lib.rs +++ b/crates/miden-lib/src/testing/mock_util_lib.rs @@ -5,7 +5,7 @@ use miden_objects::utils::sync::LazyLock; use crate::transaction::TransactionKernel; const MOCK_UTIL_LIBRARY_CODE: &str = " - use.miden::tx + use.miden::output_note # Inputs: [] # Outputs: [note_idx] @@ -17,7 +17,7 @@ const MOCK_UTIL_LIBRARY_CODE: &str = " push.0xc0000000 # = NoteTag::LocalAny # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] - exec.tx::create_note + exec.output_note::create # => [note_idx] end @@ -30,7 +30,7 @@ const MOCK_UTIL_LIBRARY_CODE: &str = " movdn.4 # => [ASSET, note_idx] - exec.tx::add_asset_to_note dropw + exec.output_note::add_asset dropw # => [note_idx] end "; diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index 7154420b07..a46ef3afd1 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -71,6 +71,8 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ word!("0x25815e02b7976d8e5c297dde60d372cc142c81f702f424ac0920190528c547ee"), // input_note_get_recipient word!("0xd3c255177f9243bb1a523a87615bbe76dd5a3605fcae87eb9d3a626d4ecce33c"), + // output_note_create + word!("0x52b37f8b25e26517f22f1f600acae7fbfffa84094595ba961af2af807a484736"), // output_note_get_metadata word!("0xde4a5b57f9d53692459383e6cf6302ef3602a348896ed6ab6fdf67e07fa483ff"), // output_note_get_assets_info @@ -79,16 +81,14 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ word!("0xc824115ed79a2e1670daed8c18fba1bc15f54c5ec0ec6699de69a00b21d9df92"), // output_note_add_asset word!("0x47673b932aac8c186cb0979bbc3c4c2afa00fa1b80c0afb5e5efb4924bba48d9"), - // tx_create_note - word!("0x52b37f8b25e26517f22f1f600acae7fbfffa84094595ba961af2af807a484736"), - // tx_get_input_notes_commitment - word!("0xc3a334434daa7d4ea15e1b2cb1a8000ad757f9348560a7246336662b77b0d89a"), // tx_get_num_input_notes word!("0xfcc186d4b65c584f3126dda1460b01eef977efd76f9e36f972554af28e33c685"), - // tx_get_output_notes_commitment - word!("0xd5b22dae48ec4b20ed479f2c43573d34930720886371ef6b484310a3bea4e818"), + // tx_get_input_notes_commitment + word!("0xc3a334434daa7d4ea15e1b2cb1a8000ad757f9348560a7246336662b77b0d89a"), // tx_get_num_output_notes word!("0x2511fca9c078cd96e526fd488d1362cbfd597eb3db8452aedb00beffee9782b4"), + // tx_get_output_notes_commitment + word!("0xd5b22dae48ec4b20ed479f2c43573d34930720886371ef6b484310a3bea4e818"), // tx_get_block_commitment word!("0xe474b491a64d222397fcf83ee5db7b048061988e5e83ce99b91bae6fd75a3522"), // tx_get_block_number @@ -101,6 +101,6 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ word!("0xaa0018aa8da890b73511879487f65553753fb7df22de380dd84c11e6f77eec6f"), // tx_get_expiration_delta word!("0xa60286e820a755128b2269db5057b0e2d9b79fef6f813bf3fe3337553a8fbb53"), - // tx_update_expiration_block_num + // tx_update_expiration_block_delta word!("0xa16440a9a8cd2a6d0ff7f5c3bcce2958976e5d3e6e8a6935ff40ae1863c324f0"), ]; diff --git a/crates/miden-lib/src/transaction/memory.rs b/crates/miden-lib/src/transaction/memory.rs index e33fa73848..7c0619b0cd 100644 --- a/crates/miden-lib/src/transaction/memory.rs +++ b/crates/miden-lib/src/transaction/memory.rs @@ -415,7 +415,7 @@ pub const INPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 40; // // Dirty flag is set to 0 after every recomputation of the assets commitment in the // `kernel::note::compute_output_note_assets_commitment` procedure. It is set to 1 in the -// `kernel::tx::add_asset_to_note` procedure after any change was made to the assets data . +// `kernel::output_note::add_asset` procedure after any change was made to the assets data. /// The memory address at which the output notes section begins. pub const OUTPUT_NOTE_SECTION_OFFSET: MemoryOffset = 16_777_216; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index e1c3db55aa..63a391006e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -589,7 +589,7 @@ fn asset_and_storage_delta() -> anyhow::Result<()> { # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] # create the note - call.tx::create_note + call.output_note::create # => [note_idx, pad(15)] # move an asset to the created note to partially deplete fungible asset balance @@ -611,7 +611,7 @@ fn asset_and_storage_delta() -> anyhow::Result<()> { let tx_script_src = format!( "\ use.mock::account - use.miden::tx + use.miden::output_note ## TRANSACTION SCRIPT ## ======================================================================================== @@ -820,7 +820,7 @@ fn compile_tx_script(code: impl AsRef) -> anyhow::Result const TEST_ACCOUNT_CONVENIENCE_WRAPPERS: &str = " use.mock::account - use.miden::tx + use.miden::output_note #! Inputs: [index, VALUE] #! Outputs: [] @@ -873,7 +873,7 @@ const TEST_ACCOUNT_CONVENIENCE_WRAPPERS: &str = " repeat.8 push.0 movdn.8 end # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] - call.tx::create_note + call.output_note::create # => [note_idx, pad(15)] repeat.15 swap drop end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index b3dfc454ae..c90535f017 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -361,9 +361,9 @@ fn test_block_expiration_height_monotonically_decreases() -> anyhow::Result<()> begin exec.prologue::prepare_transaction push.{value_1} - exec.tx::update_expiration_block_num + exec.tx::update_expiration_block_delta push.{value_2} - exec.tx::update_expiration_block_num + exec.tx::update_expiration_block_delta push.{min_value} exec.tx::get_expiration_delta assert_eq @@ -402,7 +402,7 @@ fn test_invalid_expiration_deltas() -> anyhow::Result<()> { begin push.{value_1} - exec.tx::update_expiration_block_num + exec.tx::update_expiration_block_delta end "; @@ -535,7 +535,7 @@ fn test_epilogue_empty_transaction_with_empty_output_note() -> anyhow::Result<() // create an empty output note in the transaction script let tx_script_source = format!( r#" - use.miden::tx + use.miden::output_note use.$kernel::prologue use.$kernel::epilogue use.$kernel::note @@ -556,7 +556,7 @@ fn test_epilogue_empty_transaction_with_empty_output_note() -> anyhow::Result<() # => [tag, aux, execution_hint, note_type, RECIPIENT, pad(8)] # create the note - call.tx::create_note + call.output_note::create # => [note_idx, GARBAGE(15)] # make sure that output note was created: compare the output note hash with an empty diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index aa80b67a31..e8dff741bb 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -35,11 +35,11 @@ use miden_objects::testing::account_id::{ ACCOUNT_ID_SENDER, }; use miden_objects::transaction::{AccountInputs, OutputNote, TransactionArgs}; -use miden_objects::{EMPTY_WORD, ONE, WORD_SIZE, Word}; +use miden_objects::{EMPTY_WORD, Felt, ONE, WORD_SIZE, Word, ZERO}; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; -use super::{Felt, Process, ZERO}; +use super::Process; use crate::kernel_tests::tx::ProcessMemoryExt; use crate::utils::{create_p2any_note, input_note_data_ptr}; use crate::{ @@ -837,7 +837,7 @@ fn test_get_current_script_root() -> anyhow::Result<()> { } #[test] -fn test_build_note_metadata() -> miette::Result<()> { +fn test_build_metadata() -> miette::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); let sender = tx_context.account().id(); @@ -868,12 +868,12 @@ fn test_build_note_metadata() -> miette::Result<()> { let code = format!( " use.$kernel::prologue - use.$kernel::tx + use.$kernel::output_note begin exec.prologue::prepare_transaction push.{execution_hint}.{note_type}.{aux}.{tag} - exec.tx::build_note_metadata + exec.output_note::build_metadata # truncate the stack swapw dropw diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index aeccac7117..b110988b4c 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -1,21 +1,708 @@ use alloc::string::String; +use alloc::vec::Vec; +use anyhow::Context; +use miden_lib::errors::tx_kernel_errors::{ + ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS, + ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT, +}; use miden_lib::note::create_p2id_note; +use miden_lib::testing::mock_account::MockAccountExt; +use miden_lib::transaction::memory::{ + NOTE_MEM_SIZE, + NUM_OUTPUT_NOTES_PTR, + OUTPUT_NOTE_ASSETS_OFFSET, + OUTPUT_NOTE_METADATA_OFFSET, + OUTPUT_NOTE_RECIPIENT_OFFSET, + OUTPUT_NOTE_SECTION_OFFSET, +}; use miden_lib::utils::ScriptBuilder; -use miden_objects::Word; -use miden_objects::account::AccountId; -use miden_objects::asset::{Asset, FungibleAsset}; +use miden_objects::account::{Account, AccountId}; +use miden_objects::asset::{Asset, FungibleAsset, NonFungibleAsset}; use miden_objects::crypto::rand::RpoRandomCoin; -use miden_objects::note::{Note, NoteType}; +use miden_objects::note::{ + Note, + NoteAssets, + NoteExecutionHint, + NoteExecutionMode, + NoteInputs, + NoteMetadata, + NoteRecipient, + NoteTag, + NoteType, +}; use miden_objects::testing::account_id::{ + ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET, + ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, + ACCOUNT_ID_PRIVATE_SENDER, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, + ACCOUNT_ID_SENDER, }; -use miden_objects::transaction::OutputNote; +use miden_objects::testing::constants::NON_FUNGIBLE_ASSET_DATA_2; +use miden_objects::transaction::{OutputNote, OutputNotes}; +use miden_objects::{Felt, Word, ZERO}; + +use super::{TestSetup, setup_test}; +use crate::kernel_tests::tx::ProcessMemoryExt; +use crate::utils::create_p2any_note; +use crate::{Auth, MockChain, TransactionContextBuilder, assert_execution_error}; + +#[test] +fn test_create_note() -> anyhow::Result<()> { + let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; + let account_id = tx_context.account().id(); + + let recipient = Word::from([0, 1, 2, 3u32]); + let aux = Felt::new(27); + let tag = NoteTag::from_account_id(account_id); + + let code = format!( + " + use.miden::output_note + + use.$kernel::prologue + + begin + exec.prologue::prepare_transaction + + push.{recipient} + push.{note_execution_hint} + push.{PUBLIC_NOTE} + push.{aux} + push.{tag} + + call.output_note::create + + # truncate the stack + swapdw dropw dropw + end + ", + recipient = recipient, + PUBLIC_NOTE = NoteType::Public as u8, + note_execution_hint = Felt::from(NoteExecutionHint::after_block(23.into()).unwrap()), + tag = tag, + ); + + let process = &tx_context.execute_code(&code)?; + + assert_eq!( + process.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), + Word::from([1, 0, 0, 0u32]), + "number of output notes must increment by 1", + ); + + assert_eq!( + process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_RECIPIENT_OFFSET), + recipient, + "recipient must be stored at the correct memory location", + ); + + let expected_note_metadata: Word = NoteMetadata::new( + account_id, + NoteType::Public, + tag, + NoteExecutionHint::after_block(23.into())?, + Felt::new(27), + )? + .into(); + + assert_eq!( + process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET), + expected_note_metadata, + "metadata must be stored at the correct memory location", + ); + + assert_eq!( + process.stack.get(0), + ZERO, + "top item on the stack is the index of the output note" + ); + Ok(()) +} + +#[test] +fn test_create_note_with_invalid_tag() -> anyhow::Result<()> { + let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; + + let invalid_tag = Felt::new((NoteType::Public as u64) << 62); + let valid_tag: Felt = NoteTag::for_local_use_case(0, 0).unwrap().into(); + + // Test invalid tag + assert!(tx_context.execute_code(¬e_creation_script(invalid_tag)).is_err()); + + // Test valid tag + assert!(tx_context.execute_code(¬e_creation_script(valid_tag)).is_ok()); + + Ok(()) +} + +fn note_creation_script(tag: Felt) -> String { + format!( + " + use.miden::output_note + use.$kernel::prologue + + begin + exec.prologue::prepare_transaction + + push.{recipient} + push.{execution_hint_always} + push.{PUBLIC_NOTE} + push.{aux} + push.{tag} + + call.output_note::create + + # clean the stack + dropw dropw + end + ", + recipient = Word::from([0, 1, 2, 3u32]), + execution_hint_always = Felt::from(NoteExecutionHint::always()), + PUBLIC_NOTE = NoteType::Public as u8, + aux = ZERO, + ) +} + +#[test] +fn test_create_note_too_many_notes() -> anyhow::Result<()> { + let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; + + let code = format!( + " + use.miden::output_note + use.$kernel::constants + use.$kernel::memory + use.$kernel::prologue + + begin + exec.constants::get_max_num_output_notes + exec.memory::set_num_output_notes + exec.prologue::prepare_transaction + + push.{recipient} + push.{execution_hint_always} + push.{PUBLIC_NOTE} + push.{aux} + push.{tag} + + call.output_note::create + end + ", + tag = NoteTag::for_local_use_case(1234, 5678).unwrap(), + recipient = Word::from([0, 1, 2, 3u32]), + execution_hint_always = Felt::from(NoteExecutionHint::always()), + PUBLIC_NOTE = NoteType::Public as u8, + aux = ZERO, + ); + + let process = tx_context.execute_code(&code); + + assert_execution_error!(process, ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT); + Ok(()) +} + +#[test] +fn test_get_output_notes_commitment() -> anyhow::Result<()> { + let tx_context = { + let account = + Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); + + let output_note_1 = + create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); + + let input_note_1 = + create_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [FungibleAsset::mock(100)]); + + let input_note_2 = + create_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [FungibleAsset::mock(200)]); + + TransactionContextBuilder::new(account) + .extend_input_notes(vec![input_note_1, input_note_2]) + .extend_expected_output_notes(vec![OutputNote::Full(output_note_1)]) + .build()? + }; + + // extract input note data + let input_note_1 = tx_context.tx_inputs().input_notes().get_note(0).note(); + let input_asset_1 = **input_note_1 + .assets() + .iter() + .take(1) + .collect::>() + .first() + .context("getting first expected input asset")?; + let input_note_2 = tx_context.tx_inputs().input_notes().get_note(1).note(); + let input_asset_2 = **input_note_2 + .assets() + .iter() + .take(1) + .collect::>() + .first() + .context("getting second expected input asset")?; + + // Choose random accounts as the target for the note tag. + let network_account = AccountId::try_from(ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET)?; + let local_account = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET)?; + + // create output note 1 + let output_serial_no_1 = Word::from([8u32; 4]); + let output_tag_1 = NoteTag::from_account_id(network_account); + let assets = NoteAssets::new(vec![input_asset_1])?; + let metadata = NoteMetadata::new( + tx_context.tx_inputs().account().id(), + NoteType::Public, + output_tag_1, + NoteExecutionHint::Always, + ZERO, + )?; + let inputs = NoteInputs::new(vec![])?; + let recipient = NoteRecipient::new(output_serial_no_1, input_note_1.script().clone(), inputs); + let output_note_1 = Note::new(assets, metadata, recipient); + + // create output note 2 + let output_serial_no_2 = Word::from([11u32; 4]); + let output_tag_2 = NoteTag::from_account_id(local_account); + let assets = NoteAssets::new(vec![input_asset_2])?; + let metadata = NoteMetadata::new( + tx_context.tx_inputs().account().id(), + NoteType::Public, + output_tag_2, + NoteExecutionHint::after_block(123.into())?, + ZERO, + )?; + let inputs = NoteInputs::new(vec![])?; + let recipient = NoteRecipient::new(output_serial_no_2, input_note_2.script().clone(), inputs); + let output_note_2 = Note::new(assets, metadata, recipient); + + // compute expected output notes commitment + let expected_output_notes_commitment = OutputNotes::new(vec![ + OutputNote::Full(output_note_1.clone()), + OutputNote::Full(output_note_2.clone()), + ])? + .commitment(); + + let code = format!( + " + use.std::sys + + use.miden::tx + use.miden::output_note + + use.$kernel::prologue + + begin + # => [BH, acct_id, IAH, NC] + exec.prologue::prepare_transaction + # => [] + + # create output note 1 + push.{recipient_1} + push.{NOTE_EXECUTION_HINT_1} + push.{PUBLIC_NOTE} + push.{aux_1} + push.{tag_1} + call.output_note::create + # => [note_idx] + + push.{asset_1} + call.output_note::add_asset + # => [ASSET, note_idx] + + dropw drop + # => [] + + # create output note 2 + push.{recipient_2} + push.{NOTE_EXECUTION_HINT_2} + push.{PUBLIC_NOTE} + push.{aux_2} + push.{tag_2} + call.output_note::create + # => [note_idx] + + push.{asset_2} + call.output_note::add_asset + # => [ASSET, note_idx] + + dropw drop + # => [] + + # compute the output notes commitment + exec.tx::get_output_notes_commitment + # => [OUTPUT_NOTES_COMMITMENT] + + # truncate the stack + exec.sys::truncate_stack + # => [OUTPUT_NOTES_COMMITMENT] + end + ", + PUBLIC_NOTE = NoteType::Public as u8, + NOTE_EXECUTION_HINT_1 = Felt::from(output_note_1.metadata().execution_hint()), + recipient_1 = output_note_1.recipient().digest(), + tag_1 = output_note_1.metadata().tag(), + aux_1 = output_note_1.metadata().aux(), + asset_1 = Word::from( + **output_note_1.assets().iter().take(1).collect::>().first().unwrap() + ), + recipient_2 = output_note_2.recipient().digest(), + NOTE_EXECUTION_HINT_2 = Felt::from(output_note_2.metadata().execution_hint()), + tag_2 = output_note_2.metadata().tag(), + aux_2 = output_note_2.metadata().aux(), + asset_2 = Word::from( + **output_note_2.assets().iter().take(1).collect::>().first().unwrap() + ), + ); + + let process = &tx_context.execute_code(&code)?; + + assert_eq!( + process.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), + Word::from([2u32, 0, 0, 0]), + "The test creates two notes", + ); + assert_eq!( + NoteMetadata::try_from( + process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET) + ) + .unwrap(), + *output_note_1.metadata(), + "Validate the output note 1 metadata", + ); + assert_eq!( + NoteMetadata::try_from(process.get_kernel_mem_word( + OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET + NOTE_MEM_SIZE + )) + .unwrap(), + *output_note_2.metadata(), + "Validate the output note 1 metadata", + ); + + assert_eq!(process.stack.get_word(0), expected_output_notes_commitment); + Ok(()) +} + +#[test] +fn test_create_note_and_add_asset() -> anyhow::Result<()> { + let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; + + let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; + let recipient = Word::from([0, 1, 2, 3u32]); + let aux = Felt::new(27); + let tag = NoteTag::from_account_id(faucet_id); + let asset = Word::from(FungibleAsset::new(faucet_id, 10)?); + + let code = format!( + " + use.miden::output_note + + use.$kernel::prologue + use.mock::account -use super::{Felt, TestSetup, setup_test}; -use crate::{Auth, MockChain}; + begin + exec.prologue::prepare_transaction + + push.{recipient} + push.{NOTE_EXECUTION_HINT} + push.{PUBLIC_NOTE} + push.{aux} + push.{tag} + + call.output_note::create + # => [note_idx] + + push.{asset} + call.output_note::add_asset + # => [ASSET, note_idx] + + dropw + # => [note_idx] + + # truncate the stack + swapdw dropw dropw + end + ", + recipient = recipient, + PUBLIC_NOTE = NoteType::Public as u8, + NOTE_EXECUTION_HINT = Felt::from(NoteExecutionHint::always()), + tag = tag, + asset = asset, + ); + + let process = &tx_context.execute_code(&code)?; + + assert_eq!( + process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), + asset, + "asset must be stored at the correct memory location", + ); + + assert_eq!( + process.stack.get(0), + ZERO, + "top item on the stack is the index to the output note" + ); + Ok(()) +} + +#[test] +fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { + let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; + + let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; + let faucet_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2)?; + + let recipient = Word::from([0, 1, 2, 3u32]); + let aux = Felt::new(27); + let tag = NoteTag::from_account_id(faucet_2); + + let asset = Word::from(FungibleAsset::new(faucet, 10)?); + let asset_2 = Word::from(FungibleAsset::new(faucet_2, 20)?); + let asset_3 = Word::from(FungibleAsset::new(faucet_2, 30)?); + let asset_2_and_3 = Word::from(FungibleAsset::new(faucet_2, 50)?); + + let non_fungible_asset = NonFungibleAsset::mock(&NON_FUNGIBLE_ASSET_DATA_2); + let non_fungible_asset_encoded = Word::from(non_fungible_asset); + + let code = format!( + " + use.miden::output_note + + use.$kernel::prologue + use.mock::account + + begin + exec.prologue::prepare_transaction + + push.{recipient} + push.{PUBLIC_NOTE} + push.{aux} + push.{tag} + + call.output_note::create + # => [note_idx] + + push.{asset} + call.output_note::add_asset dropw + # => [note_idx] + + push.{asset_2} + call.output_note::add_asset dropw + # => [note_idx] + + push.{asset_3} + call.output_note::add_asset dropw + # => [note_idx] + + push.{nft} + call.output_note::add_asset dropw + # => [note_idx] + + # truncate the stack + swapdw dropw drop drop drop + end + ", + recipient = recipient, + PUBLIC_NOTE = NoteType::Public as u8, + tag = tag, + asset = asset, + asset_2 = asset_2, + asset_3 = asset_3, + nft = non_fungible_asset_encoded, + ); + + let process = &tx_context.execute_code(&code)?; + + assert_eq!( + process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), + asset, + "asset must be stored at the correct memory location", + ); + + assert_eq!( + process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 4), + asset_2_and_3, + "asset_2 and asset_3 must be stored at the same correct memory location", + ); + + assert_eq!( + process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 8), + non_fungible_asset_encoded, + "non_fungible_asset must be stored at the correct memory location", + ); + + assert_eq!( + process.stack.get(0), + ZERO, + "top item on the stack is the index to the output note" + ); + Ok(()) +} + +#[test] +fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { + let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; + + let recipient = Word::from([0, 1, 2, 3u32]); + let tag = NoteTag::for_public_use_case(999, 777, NoteExecutionMode::Local).unwrap(); + let non_fungible_asset = NonFungibleAsset::mock(&[1, 2, 3]); + let encoded = Word::from(non_fungible_asset); + + let code = format!( + " + use.$kernel::prologue + use.miden::output_note + + begin + exec.prologue::prepare_transaction + # => [] + + padw padw + push.{recipient} + push.{execution_hint_always} + push.{PUBLIC_NOTE} + push.{aux} + push.{tag} + + call.output_note::create + # => [note_idx, pad(15)] + + push.{nft} + call.output_note::add_asset + # => [NFT, note_idx, pad(15)] + dropw + + push.{nft} + call.output_note::add_asset + # => [NFT, note_idx, pad(15)] + + repeat.5 dropw end + end + ", + recipient = recipient, + PUBLIC_NOTE = NoteType::Public as u8, + execution_hint_always = Felt::from(NoteExecutionHint::always()), + aux = Felt::new(0), + tag = tag, + nft = encoded, + ); + + let process = tx_context.execute_code(&code); + + assert_execution_error!(process, ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS); + Ok(()) +} + +/// Tests that creating a note with a fungible asset with amount zero works. +#[test] +fn creating_note_with_fungible_asset_amount_zero_works() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let output_note = builder.add_p2id_note( + account.id(), + account.id(), + &[FungibleAsset::mock(0)], + NoteType::Private, + )?; + let input_note = builder.add_spawn_note([&output_note])?; + let chain = builder.build()?; + + chain + .build_tx_context(account, &[input_note.id()], &[])? + .build()? + .execute_blocking()?; + + Ok(()) +} + +#[test] +fn test_build_recipient_hash() -> anyhow::Result<()> { + let tx_context = { + let account = + Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); + + let input_note_1 = + create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); + TransactionContextBuilder::new(account) + .extend_input_notes(vec![input_note_1]) + .build()? + }; + let input_note_1 = tx_context.tx_inputs().input_notes().get_note(0).note(); + + // create output note + let output_serial_no = Word::from([0, 1, 2, 3u32]); + let aux = Felt::new(27); + let tag = NoteTag::for_public_use_case(42, 42, NoteExecutionMode::Network).unwrap(); + let single_input = 2; + let inputs = NoteInputs::new(vec![Felt::new(single_input)]).unwrap(); + let input_commitment = inputs.commitment(); + + let recipient = NoteRecipient::new(output_serial_no, input_note_1.script().clone(), inputs); + let code = format!( + " + use.miden::output_note + use.miden::note + use.$kernel::prologue + + begin + exec.prologue::prepare_transaction + + # pad the stack before call + padw + + # input + push.{input_commitment} + # SCRIPT_ROOT + push.{script_root} + # SERIAL_NUM + push.{output_serial_no} + # => [SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT, pad(4)] + + exec.note::build_recipient_hash + # => [RECIPIENT, pad(12)] + + push.{execution_hint} + push.{PUBLIC_NOTE} + push.{aux} + push.{tag} + # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(12)] + + call.output_note::create + # => [note_idx, pad(19)] + + # clean the stack + dropw dropw dropw dropw dropw + end + ", + script_root = input_note_1.script().clone().root(), + output_serial_no = output_serial_no, + PUBLIC_NOTE = NoteType::Public as u8, + tag = tag, + execution_hint = Felt::from(NoteExecutionHint::after_block(2.into()).unwrap()), + aux = aux, + ); + + let process = &tx_context.execute_code(&code)?; + + assert_eq!( + process.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), + Word::from([1, 0, 0, 0u32]), + "number of output notes must increment by 1", + ); + + let recipient_digest = recipient.clone().digest(); + + assert_eq!( + process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_RECIPIENT_OFFSET), + recipient_digest, + "recipient hash not correct", + ); + Ok(()) +} /// This test creates an output note and then adds some assets into it checking the assets info on /// each stage. @@ -72,7 +759,6 @@ fn test_get_asset_info() -> anyhow::Result<()> { let tx_script_src = &format!( r#" - use.miden::tx use.miden::output_note use.std::sys @@ -83,7 +769,7 @@ fn test_get_asset_info() -> anyhow::Result<()> { push.{note_type} push.0 # aux push.{tag} - call.tx::create_note + call.output_note::create # => [note_idx] # move the asset 0 to the note @@ -189,7 +875,6 @@ fn test_get_recipient_and_metadata() -> anyhow::Result<()> { let tx_script_src = &format!( r#" - use.miden::tx use.miden::output_note use.std::sys @@ -304,7 +989,6 @@ fn test_get_assets() -> anyhow::Result<()> { let tx_script_src = &format!( " - use.miden::tx use.miden::output_note use.std::sys @@ -362,7 +1046,7 @@ fn create_output_note(note: &Note) -> String { push.{note_type} push.0 # aux push.{tag} - call.tx::create_note + call.output_note::create # => [note_idx] ", RECIPIENT = note.recipient().digest(), diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 950356a88d..da6f7523d7 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -1,4 +1,3 @@ -use alloc::string::String; use alloc::sync::Arc; use alloc::vec::Vec; @@ -7,21 +6,9 @@ use assert_matches::assert_matches; use miden_lib::AuthScheme; use miden_lib::account::interface::AccountInterface; use miden_lib::account::wallets::BasicWallet; -use miden_lib::errors::tx_kernel_errors::{ - ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS, - ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT, -}; use miden_lib::note::create_p2id_note; use miden_lib::testing::account_component::IncrNonceAuthComponent; use miden_lib::testing::mock_account::MockAccountExt; -use miden_lib::transaction::memory::{ - NOTE_MEM_SIZE, - NUM_OUTPUT_NOTES_PTR, - OUTPUT_NOTE_ASSETS_OFFSET, - OUTPUT_NOTE_METADATA_OFFSET, - OUTPUT_NOTE_RECIPIENT_OFFSET, - OUTPUT_NOTE_SECTION_OFFSET, -}; use miden_lib::transaction::{TransactionEvent, TransactionKernel}; use miden_lib::utils::ScriptBuilder; use miden_objects::account::{ @@ -29,7 +16,6 @@ use miden_objects::account::{ AccountBuilder, AccountCode, AccountComponent, - AccountId, AccountStorage, AccountStorageMode, AccountType, @@ -53,20 +39,13 @@ use miden_objects::note::{ NoteType, }; use miden_objects::testing::account_id::{ - ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET, - ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_SENDER, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, - ACCOUNT_ID_SENDER, -}; -use miden_objects::testing::constants::{ - FUNGIBLE_ASSET_AMOUNT, - NON_FUNGIBLE_ASSET_DATA, - NON_FUNGIBLE_ASSET_DATA_2, }; +use miden_objects::testing::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}; use miden_objects::testing::note::DEFAULT_NOTE_CODE; use miden_objects::transaction::{ InputNotes, @@ -80,10 +59,9 @@ use miden_processor::crypto::RpoRandomCoin; use miden_tx::auth::UnreachableAuth; use miden_tx::{TransactionExecutor, TransactionExecutorError}; -use super::{Felt, ONE, ZERO}; -use crate::kernel_tests::tx::ProcessMemoryExt; +use super::{Felt, ONE}; use crate::utils::{create_p2any_note, create_spawn_note}; -use crate::{Auth, MockChain, TransactionContextBuilder, assert_execution_error}; +use crate::{Auth, MockChain, TransactionContextBuilder}; /// Tests that executing a transaction with a foreign account whose inputs are stale fails. #[test] @@ -185,660 +163,6 @@ async fn consuming_note_created_in_future_block_fails() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_create_note() -> anyhow::Result<()> { - let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - let account_id = tx_context.account().id(); - - let recipient = Word::from([0, 1, 2, 3u32]); - let aux = Felt::new(27); - let tag = NoteTag::from_account_id(account_id); - - let code = format!( - " - use.miden::tx - - use.$kernel::prologue - - begin - exec.prologue::prepare_transaction - - push.{recipient} - push.{note_execution_hint} - push.{PUBLIC_NOTE} - push.{aux} - push.{tag} - - call.tx::create_note - - # truncate the stack - swapdw dropw dropw - end - ", - recipient = recipient, - PUBLIC_NOTE = NoteType::Public as u8, - note_execution_hint = Felt::from(NoteExecutionHint::after_block(23.into()).unwrap()), - tag = tag, - ); - - let process = &tx_context.execute_code(&code)?; - - assert_eq!( - process.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), - Word::from([1, 0, 0, 0u32]), - "number of output notes must increment by 1", - ); - - assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_RECIPIENT_OFFSET), - recipient, - "recipient must be stored at the correct memory location", - ); - - let expected_note_metadata: Word = NoteMetadata::new( - account_id, - NoteType::Public, - tag, - NoteExecutionHint::after_block(23.into())?, - Felt::new(27), - )? - .into(); - - assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET), - expected_note_metadata, - "metadata must be stored at the correct memory location", - ); - - assert_eq!( - process.stack.get(0), - ZERO, - "top item on the stack is the index of the output note" - ); - Ok(()) -} - -#[test] -fn test_create_note_with_invalid_tag() -> anyhow::Result<()> { - let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - - let invalid_tag = Felt::new((NoteType::Public as u64) << 62); - let valid_tag: Felt = NoteTag::for_local_use_case(0, 0).unwrap().into(); - - // Test invalid tag - assert!(tx_context.execute_code(¬e_creation_script(invalid_tag)).is_err()); - - // Test valid tag - assert!(tx_context.execute_code(¬e_creation_script(valid_tag)).is_ok()); - - Ok(()) -} - -fn note_creation_script(tag: Felt) -> String { - format!( - " - use.miden::tx - use.$kernel::prologue - - begin - exec.prologue::prepare_transaction - - push.{recipient} - push.{execution_hint_always} - push.{PUBLIC_NOTE} - push.{aux} - push.{tag} - - call.tx::create_note - - # clean the stack - dropw dropw - end - ", - recipient = Word::from([0, 1, 2, 3u32]), - execution_hint_always = Felt::from(NoteExecutionHint::always()), - PUBLIC_NOTE = NoteType::Public as u8, - aux = Felt::ZERO, - ) -} - -#[test] -fn test_create_note_too_many_notes() -> anyhow::Result<()> { - let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - - let code = format!( - " - use.miden::tx - use.$kernel::constants - use.$kernel::memory - use.$kernel::prologue - - begin - exec.constants::get_max_num_output_notes - exec.memory::set_num_output_notes - exec.prologue::prepare_transaction - - push.{recipient} - push.{execution_hint_always} - push.{PUBLIC_NOTE} - push.{aux} - push.{tag} - - call.tx::create_note - end - ", - tag = NoteTag::for_local_use_case(1234, 5678).unwrap(), - recipient = Word::from([0, 1, 2, 3u32]), - execution_hint_always = Felt::from(NoteExecutionHint::always()), - PUBLIC_NOTE = NoteType::Public as u8, - aux = Felt::ZERO, - ); - - let process = tx_context.execute_code(&code); - - assert_execution_error!(process, ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT); - Ok(()) -} - -#[test] -fn test_get_output_notes_commitment() -> anyhow::Result<()> { - let tx_context = { - let account = - Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); - - let output_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); - - let input_note_1 = - create_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [FungibleAsset::mock(100)]); - - let input_note_2 = - create_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [FungibleAsset::mock(200)]); - - TransactionContextBuilder::new(account) - .extend_input_notes(vec![input_note_1, input_note_2]) - .extend_expected_output_notes(vec![OutputNote::Full(output_note_1)]) - .build()? - }; - - // extract input note data - let input_note_1 = tx_context.tx_inputs().input_notes().get_note(0).note(); - let input_asset_1 = **input_note_1 - .assets() - .iter() - .take(1) - .collect::>() - .first() - .context("getting first expected input asset")?; - let input_note_2 = tx_context.tx_inputs().input_notes().get_note(1).note(); - let input_asset_2 = **input_note_2 - .assets() - .iter() - .take(1) - .collect::>() - .first() - .context("getting second expected input asset")?; - - // Choose random accounts as the target for the note tag. - let network_account = AccountId::try_from(ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET)?; - let local_account = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET)?; - - // create output note 1 - let output_serial_no_1 = Word::from([8u32; 4]); - let output_tag_1 = NoteTag::from_account_id(network_account); - let assets = NoteAssets::new(vec![input_asset_1])?; - let metadata = NoteMetadata::new( - tx_context.tx_inputs().account().id(), - NoteType::Public, - output_tag_1, - NoteExecutionHint::Always, - ZERO, - )?; - let inputs = NoteInputs::new(vec![])?; - let recipient = NoteRecipient::new(output_serial_no_1, input_note_1.script().clone(), inputs); - let output_note_1 = Note::new(assets, metadata, recipient); - - // create output note 2 - let output_serial_no_2 = Word::from([11u32; 4]); - let output_tag_2 = NoteTag::from_account_id(local_account); - let assets = NoteAssets::new(vec![input_asset_2])?; - let metadata = NoteMetadata::new( - tx_context.tx_inputs().account().id(), - NoteType::Public, - output_tag_2, - NoteExecutionHint::after_block(123.into())?, - ZERO, - )?; - let inputs = NoteInputs::new(vec![])?; - let recipient = NoteRecipient::new(output_serial_no_2, input_note_2.script().clone(), inputs); - let output_note_2 = Note::new(assets, metadata, recipient); - - // compute expected output notes commitment - let expected_output_notes_commitment = OutputNotes::new(vec![ - OutputNote::Full(output_note_1.clone()), - OutputNote::Full(output_note_2.clone()), - ])? - .commitment(); - - let code = format!( - " - use.std::sys - - use.miden::tx - - use.$kernel::prologue - use.mock::account - - begin - # => [BH, acct_id, IAH, NC] - exec.prologue::prepare_transaction - # => [] - - # create output note 1 - push.{recipient_1} - push.{NOTE_EXECUTION_HINT_1} - push.{PUBLIC_NOTE} - push.{aux_1} - push.{tag_1} - call.tx::create_note - # => [note_idx] - - push.{asset_1} - call.tx::add_asset_to_note - # => [ASSET, note_idx] - - dropw drop - # => [] - - # create output note 2 - push.{recipient_2} - push.{NOTE_EXECUTION_HINT_2} - push.{PUBLIC_NOTE} - push.{aux_2} - push.{tag_2} - call.tx::create_note - # => [note_idx] - - push.{asset_2} - call.tx::add_asset_to_note - # => [ASSET, note_idx] - - dropw drop - # => [] - - # compute the output notes commitment - exec.tx::get_output_notes_commitment - # => [OUTPUT_NOTES_COMMITMENT] - - # truncate the stack - exec.sys::truncate_stack - # => [OUTPUT_NOTES_COMMITMENT] - end - ", - PUBLIC_NOTE = NoteType::Public as u8, - NOTE_EXECUTION_HINT_1 = Felt::from(output_note_1.metadata().execution_hint()), - recipient_1 = output_note_1.recipient().digest(), - tag_1 = output_note_1.metadata().tag(), - aux_1 = output_note_1.metadata().aux(), - asset_1 = Word::from( - **output_note_1.assets().iter().take(1).collect::>().first().unwrap() - ), - recipient_2 = output_note_2.recipient().digest(), - NOTE_EXECUTION_HINT_2 = Felt::from(output_note_2.metadata().execution_hint()), - tag_2 = output_note_2.metadata().tag(), - aux_2 = output_note_2.metadata().aux(), - asset_2 = Word::from( - **output_note_2.assets().iter().take(1).collect::>().first().unwrap() - ), - ); - - let process = &tx_context.execute_code(&code)?; - - assert_eq!( - process.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), - Word::from([2u32, 0, 0, 0]), - "The test creates two notes", - ); - assert_eq!( - NoteMetadata::try_from( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET) - ) - .unwrap(), - *output_note_1.metadata(), - "Validate the output note 1 metadata", - ); - assert_eq!( - NoteMetadata::try_from(process.get_kernel_mem_word( - OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET + NOTE_MEM_SIZE - )) - .unwrap(), - *output_note_2.metadata(), - "Validate the output note 1 metadata", - ); - - assert_eq!(process.stack.get_word(0), expected_output_notes_commitment); - Ok(()) -} - -#[test] -fn test_create_note_and_add_asset() -> anyhow::Result<()> { - let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - - let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; - let recipient = Word::from([0, 1, 2, 3u32]); - let aux = Felt::new(27); - let tag = NoteTag::from_account_id(faucet_id); - let asset = Word::from(FungibleAsset::new(faucet_id, 10)?); - - let code = format!( - " - use.miden::tx - - use.$kernel::prologue - use.mock::account - - begin - exec.prologue::prepare_transaction - - push.{recipient} - push.{NOTE_EXECUTION_HINT} - push.{PUBLIC_NOTE} - push.{aux} - push.{tag} - - call.tx::create_note - # => [note_idx] - - push.{asset} - call.tx::add_asset_to_note - # => [ASSET, note_idx] - - dropw - # => [note_idx] - - # truncate the stack - swapdw dropw dropw - end - ", - recipient = recipient, - PUBLIC_NOTE = NoteType::Public as u8, - NOTE_EXECUTION_HINT = Felt::from(NoteExecutionHint::always()), - tag = tag, - asset = asset, - ); - - let process = &tx_context.execute_code(&code)?; - - assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), - asset, - "asset must be stored at the correct memory location", - ); - - assert_eq!( - process.stack.get(0), - ZERO, - "top item on the stack is the index to the output note" - ); - Ok(()) -} - -#[test] -fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { - let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - - let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; - let faucet_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2)?; - - let recipient = Word::from([0, 1, 2, 3u32]); - let aux = Felt::new(27); - let tag = NoteTag::from_account_id(faucet_2); - - let asset = Word::from(FungibleAsset::new(faucet, 10)?); - let asset_2 = Word::from(FungibleAsset::new(faucet_2, 20)?); - let asset_3 = Word::from(FungibleAsset::new(faucet_2, 30)?); - let asset_2_and_3 = Word::from(FungibleAsset::new(faucet_2, 50)?); - - let non_fungible_asset = NonFungibleAsset::mock(&NON_FUNGIBLE_ASSET_DATA_2); - let non_fungible_asset_encoded = Word::from(non_fungible_asset); - - let code = format!( - " - use.miden::tx - - use.$kernel::prologue - use.mock::account - - begin - exec.prologue::prepare_transaction - - push.{recipient} - push.{PUBLIC_NOTE} - push.{aux} - push.{tag} - - call.tx::create_note - # => [note_idx] - - push.{asset} - call.tx::add_asset_to_note dropw - # => [note_idx] - - push.{asset_2} - call.tx::add_asset_to_note dropw - # => [note_idx] - - push.{asset_3} - call.tx::add_asset_to_note dropw - # => [note_idx] - - push.{nft} - call.tx::add_asset_to_note dropw - # => [note_idx] - - # truncate the stack - swapdw dropw drop drop drop - end - ", - recipient = recipient, - PUBLIC_NOTE = NoteType::Public as u8, - tag = tag, - asset = asset, - asset_2 = asset_2, - asset_3 = asset_3, - nft = non_fungible_asset_encoded, - ); - - let process = &tx_context.execute_code(&code)?; - - assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), - asset, - "asset must be stored at the correct memory location", - ); - - assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 4), - asset_2_and_3, - "asset_2 and asset_3 must be stored at the same correct memory location", - ); - - assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 8), - non_fungible_asset_encoded, - "non_fungible_asset must be stored at the correct memory location", - ); - - assert_eq!( - process.stack.get(0), - ZERO, - "top item on the stack is the index to the output note" - ); - Ok(()) -} - -#[test] -fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { - let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - - let recipient = Word::from([0, 1, 2, 3u32]); - let tag = NoteTag::for_public_use_case(999, 777, NoteExecutionMode::Local).unwrap(); - let non_fungible_asset = NonFungibleAsset::mock(&[1, 2, 3]); - let encoded = Word::from(non_fungible_asset); - - let code = format!( - " - use.$kernel::prologue - use.mock::account - use.miden::tx - - begin - exec.prologue::prepare_transaction - # => [] - - padw padw - push.{recipient} - push.{execution_hint_always} - push.{PUBLIC_NOTE} - push.{aux} - push.{tag} - - call.tx::create_note - # => [note_idx, pad(15)] - - push.{nft} - call.tx::add_asset_to_note - # => [NFT, note_idx, pad(15)] - dropw - - push.{nft} - call.tx::add_asset_to_note - # => [NFT, note_idx, pad(15)] - - repeat.5 dropw end - end - ", - recipient = recipient, - PUBLIC_NOTE = NoteType::Public as u8, - execution_hint_always = Felt::from(NoteExecutionHint::always()), - aux = Felt::new(0), - tag = tag, - nft = encoded, - ); - - let process = tx_context.execute_code(&code); - - assert_execution_error!(process, ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS); - Ok(()) -} - -/// Tests that creating a note with a fungible asset with amount zero works. -#[test] -fn creating_note_with_fungible_asset_amount_zero_works() -> anyhow::Result<()> { - let mut builder = MockChain::builder(); - let account = builder.add_existing_mock_account(Auth::IncrNonce)?; - let output_note = builder.add_p2id_note( - account.id(), - account.id(), - &[FungibleAsset::mock(0)], - NoteType::Private, - )?; - let input_note = builder.add_spawn_note([&output_note])?; - let chain = builder.build()?; - - chain - .build_tx_context(account, &[input_note.id()], &[])? - .build()? - .execute_blocking()?; - - Ok(()) -} - -#[test] -fn test_build_recipient_hash() -> anyhow::Result<()> { - let tx_context = { - let account = - Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); - - let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); - TransactionContextBuilder::new(account) - .extend_input_notes(vec![input_note_1]) - .build()? - }; - let input_note_1 = tx_context.tx_inputs().input_notes().get_note(0).note(); - - // create output note - let output_serial_no = Word::from([0, 1, 2, 3u32]); - let aux = Felt::new(27); - let tag = NoteTag::for_public_use_case(42, 42, NoteExecutionMode::Network).unwrap(); - let single_input = 2; - let inputs = NoteInputs::new(vec![Felt::new(single_input)]).unwrap(); - let input_commitment = inputs.commitment(); - - let recipient = NoteRecipient::new(output_serial_no, input_note_1.script().clone(), inputs); - let code = format!( - " - use.miden::tx - use.miden::note - use.$kernel::prologue - - begin - exec.prologue::prepare_transaction - - # pad the stack before call - padw - - # input - push.{input_commitment} - # SCRIPT_ROOT - push.{script_root} - # SERIAL_NUM - push.{output_serial_no} - # => [SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT, pad(4)] - - exec.note::build_recipient_hash - # => [RECIPIENT, pad(12)] - - push.{execution_hint} - push.{PUBLIC_NOTE} - push.{aux} - push.{tag} - # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(12)] - - call.tx::create_note - # => [note_idx, pad(19)] - - # clean the stack - dropw dropw dropw dropw dropw - end - ", - script_root = input_note_1.script().clone().root(), - output_serial_no = output_serial_no, - PUBLIC_NOTE = NoteType::Public as u8, - tag = tag, - execution_hint = Felt::from(NoteExecutionHint::after_block(2.into()).unwrap()), - aux = aux, - ); - - let process = &tx_context.execute_code(&code)?; - - assert_eq!( - process.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), - Word::from([1, 0, 0, 0u32]), - "number of output notes must increment by 1", - ); - - let recipient_digest = recipient.clone().digest(); - - assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_RECIPIENT_OFFSET), - recipient_digest, - "recipient hash not correct", - ); - Ok(()) -} - // BLOCK TESTS // ================================================================================================ @@ -960,7 +284,7 @@ fn executed_transaction_output_notes() -> anyhow::Result<()> { let tx_script_src = format!( "\ use.miden::contracts::wallets::basic->wallet - use.miden::tx + use.miden::output_note use.mock::account # Inputs: [tag, aux, note_type, execution_hint, RECIPIENT] @@ -971,7 +295,7 @@ fn executed_transaction_output_notes() -> anyhow::Result<()> { padw padw swapdw # => [tag, aux, execution_hint, note_type, RECIPIENT, pad(8)] - call.tx::create_note + call.output_note::create # => [note_idx, pad(15)] # remove excess PADs from the stack diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 4c8270e2a5..650afa194b 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -181,7 +181,7 @@ fn note_script_that_creates_notes<'note>( sender_id: AccountId, output_notes: impl Iterator, ) -> anyhow::Result { - let mut out = String::from("use.miden::tx\nuse.mock::account\n\nbegin\n"); + let mut out = String::from("use.miden::output_note\n\nbegin\n"); for (idx, note) in output_notes.into_iter().enumerate() { anyhow::ensure!( @@ -214,7 +214,7 @@ fn note_script_that_creates_notes<'note>( push.{note_type} push.{aux} push.{tag} - call.tx::create_note\n", + call.output_note::create\n", recipient = note.recipient().digest(), hint = Felt::from(note.metadata().execution_hint()), note_type = note.metadata().note_type() as u8, diff --git a/crates/miden-testing/tests/scripts/p2id.rs b/crates/miden-testing/tests/scripts/p2id.rs index 87decdbfb3..8b8c866d37 100644 --- a/crates/miden-testing/tests/scripts/p2id.rs +++ b/crates/miden-testing/tests/scripts/p2id.rs @@ -219,14 +219,14 @@ fn test_create_consume_multiple_notes() -> anyhow::Result<()> { let tx_script_src = &format!( " - use.miden::tx + use.miden::output_note begin push.{recipient_1} push.{note_execution_hint_1} push.{note_type_1} push.0 # aux push.{tag_1} - call.tx::create_note + call.output_note::create push.{asset_1} call.::miden::contracts::wallets::basic::move_asset_to_note @@ -237,7 +237,7 @@ fn test_create_consume_multiple_notes() -> anyhow::Result<()> { push.{note_type_2} push.0 # aux push.{tag_2} - call.tx::create_note + call.output_note::create push.{asset_2} call.::miden::contracts::wallets::basic::move_asset_to_note diff --git a/crates/miden-testing/tests/scripts/swap.rs b/crates/miden-testing/tests/scripts/swap.rs index be79f104d2..b179cda620 100644 --- a/crates/miden-testing/tests/scripts/swap.rs +++ b/crates/miden-testing/tests/scripts/swap.rs @@ -40,14 +40,14 @@ pub fn prove_send_swap_note() -> anyhow::Result<()> { let tx_script_src = &format!( " - use.miden::tx + use.miden::output_note begin push.{recipient} push.{note_execution_hint} push.{note_type} push.0 # aux push.{tag} - call.tx::create_note + call.output_note::create push.{asset} call.::miden::contracts::wallets::basic::move_asset_to_note diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index 359688e581..91a1ee88c3 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -63,7 +63,11 @@ Note procedures can be used to fetch data from the note that is currently being | `get_serial_number` | Returns the serial number of the note currently being processed.

Inputs: `[]`
Outputs: `[SERIAL_NUMBER]` | Note | | `get_script_root` | Returns the script root of the note currently being processed.

Inputs: `[]`
Outputs: `[SCRIPT_ROOT]` | Note | | `compute_inputs_commitment` | Computes the commitment to the output note inputs starting at the specified memory address.

Inputs: `[inputs_ptr, num_inputs]`
Outputs: `[INPUTS_COMMITMENT]` | Any | +| `get_max_inputs_per_note` | Returns the max allowed number of input values per note.

Inputs: `[]`
Outputs: `[max_inputs_per_note]` | Any | | `add_assets_to_account` | Adds all assets from the currently executing note to the account vault.

Inputs: `[]`
Outputs: `[]` | Note | +| `write_assets_to_memory` | Writes the assets data stored in the advice map to the memory specified by the provided destination pointer.

Inputs: `[ASSETS_COMMITMENT, num_assets, dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Any | +| `build_recipient_hash` | Returns the `RECIPIENT` for a specified `SERIAL_NUM`, `SCRIPT_ROOT`, and inputs commitment.

Inputs: `[SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT]`
Outputs: `[RECIPIENT]` | Any | +| `build_recipient` | Builds the recipient hash from note inputs, script root, and serial number.

Inputs: `[inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]`
Outputs: `[RECIPIENT]` | Any | ## Input Note Procedures (`miden::input_note`) @@ -85,8 +89,10 @@ Output note procedures can be used to fetch data on output notes created by the | Procedure | Description | Context | | --- | --- | --- | +| `create` | Creates a new output note and returns its index.

Inputs: `[tag, aux, note_type, execution_hint, RECIPIENT]`
Outputs: `[note_idx]` | Native & Account | | `get_assets_info` | Returns the information about assets in the output note with the specified index.

Inputs: `[note_index]`
Outputs: `[ASSETS_COMMITMENT, num_assets]` | Any | | `get_assets` | Writes the assets of the output note with the specified index into memory starting at the specified address.

Inputs: `[dest_ptr, note_index]`
Outputs: `[num_assets, dest_ptr, note_index]` | Any | +| `add_asset` | Adds the `ASSET` to the output note specified by the index.

Inputs: `[ASSET, note_idx]`
Outputs: `[ASSET, note_idx]` | Native | | `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the output note with the specified index.

Inputs: `[note_index]`
Outputs: `[RECIPIENT]` | Any | | `get_metadata` | Returns the [metadata](note.md#metadata) of the output note with the specified index.

Inputs: `[note_index]`
Outputs: `[METADATA]` | Any | @@ -103,9 +109,6 @@ Transaction procedures manage transaction-level operations including note creati | `get_output_notes_commitment` | Returns the output notes commitment hash.

Inputs: `[]`
Outputs: `[OUTPUT_NOTES_COMMITMENT]` | Any | | `get_num_input_notes` | Returns the total number of input notes consumed by this transaction.

Inputs: `[]`
Outputs: `[num_input_notes]` | Any | | `get_num_output_notes` | Returns the current number of output notes created in this transaction.

Inputs: `[]`
Outputs: `[num_output_notes]` | Any | -| `create_note` | Creates a new note and returns the index of the note.

Inputs: `[tag, aux, note_type, execution_hint, RECIPIENT]`
Outputs: `[note_idx]` | Native & Account | -| `add_asset_to_note` | Adds the ASSET to the note specified by the index.

Inputs: `[ASSET, note_idx]`
Outputs: `[ASSET, note_idx]` | Native | -| `build_recipient_hash` | Returns the RECIPIENT for a specified SERIAL_NUM, SCRIPT_ROOT, and inputs commitment.

Inputs: `[SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT]`
Outputs: `[RECIPIENT]` | Any | | `execute_foreign_procedure` | Executes the provided procedure against the foreign account.

Inputs: `[foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, , pad(n)]`
Outputs: `[]` | Any | | `get_expiration_block_delta` | Returns the transaction expiration delta, or 0 if not set.

Inputs: `[]`
Outputs: `[block_height_delta]` | Any | | `update_expiration_block_delta` | Updates the transaction expiration delta.

Inputs: `[block_height_delta]`
Outputs: `[]` | Any | From ab09e025ca074d481306a480648a0aedc705b67e Mon Sep 17 00:00:00 2001 From: Evan Marshall Date: Thu, 11 Sep 2025 16:06:44 -0700 Subject: [PATCH 035/133] feat: add serialize & deserialize traits to SigningInputs (#1858) --- CHANGELOG.md | 1 + crates/miden-tx/src/auth/tx_authenticator.rs | 88 ++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb17456c2e..57101fb752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Enabled lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). - Added lazy loading of the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). - Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). +- Added Serialize and Deserialize Traits on `SigningInputs` ([#1858](https://github.com/0xMiden/miden-base/pull/1858)) ### Changes diff --git a/crates/miden-tx/src/auth/tx_authenticator.rs b/crates/miden-tx/src/auth/tx_authenticator.rs index eaa63d6e6a..609c1fe09a 100644 --- a/crates/miden-tx/src/auth/tx_authenticator.rs +++ b/crates/miden-tx/src/auth/tx_authenticator.rs @@ -14,6 +14,7 @@ use tokio::sync::RwLock; use super::signatures::get_falcon_signature; use crate::errors::AuthenticationError; +use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; // SIGNATURE DATA // ================================================================================================ @@ -70,6 +71,51 @@ impl SigningInputs { } } +// SERIALIZATION +// ================================================================================================ + +impl Serializable for SigningInputs { + fn write_into(&self, target: &mut W) { + match self { + SigningInputs::TransactionSummary(tx_summary) => { + target.write_u8(0); + tx_summary.as_ref().write_into(target); + }, + SigningInputs::Arbitrary(elements) => { + target.write_u8(1); + elements.write_into(target); + }, + SigningInputs::Blind(word) => { + target.write_u8(2); + word.write_into(target); + }, + } + } +} + +impl Deserializable for SigningInputs { + fn read_from(source: &mut R) -> Result { + let discriminant = source.read_u8()?; + match discriminant { + 0 => { + let tx_summary: TransactionSummary = source.read()?; + Ok(SigningInputs::TransactionSummary(Box::new(tx_summary))) + }, + 1 => { + let elements: Vec = source.read()?; + Ok(SigningInputs::Arbitrary(elements)) + }, + 2 => { + let word: Word = source.read()?; + Ok(SigningInputs::Blind(word)) + }, + other => Err(DeserializationError::InvalidValue(format!( + "invalid SigningInputs variant: {other}" + ))), + } + } +} + // TRANSACTION AUTHENTICATOR // ================================================================================================ @@ -216,6 +262,9 @@ mod test { use miden_lib::utils::{Deserializable, Serializable}; use miden_objects::account::AuthSecretKey; use miden_objects::crypto::dsa::rpo_falcon512::SecretKey; + use miden_objects::{Felt, Word}; + + use super::SigningInputs; #[test] fn serialize_auth_key() { @@ -228,4 +277,43 @@ mod test { AuthSecretKey::RpoFalcon512(key) => assert_eq!(secret_key.to_bytes(), key.to_bytes()), } } + + #[test] + fn serialize_deserialize_signing_inputs_arbitrary() { + let elements = vec![ + Felt::new(0), + Felt::new(1), + Felt::new(2), + Felt::new(3), + Felt::new(4), + Felt::new(5), + Felt::new(6), + Felt::new(7), + ]; + let inputs = SigningInputs::Arbitrary(elements.clone()); + let bytes = inputs.to_bytes(); + let decoded = SigningInputs::read_from_bytes(&bytes).unwrap(); + + match decoded { + SigningInputs::Arbitrary(decoded_elements) => { + assert_eq!(decoded_elements, elements); + }, + _ => panic!("expected Arbitrary variant"), + } + } + + #[test] + fn serialize_deserialize_signing_inputs_blind() { + let word = Word::from([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]); + let inputs = SigningInputs::Blind(word); + let bytes = inputs.to_bytes(); + let decoded = SigningInputs::read_from_bytes(&bytes).unwrap(); + + match decoded { + SigningInputs::Blind(w) => { + assert_eq!(w, word); + }, + _ => panic!("expected Blind variant"), + } + } } From fdf7c0e47191277a95f0ce29abb7b2d7f0a9d754 Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Fri, 12 Sep 2025 03:42:01 +0300 Subject: [PATCH 036/133] refactor: switch to "active note" terminology (#1876) --- CHANGELOG.md | 1 + .../asm/kernels/transaction/api.masm | 90 +++++++++---------- .../asm/kernels/transaction/lib/memory.masm | 18 ++-- .../asm/kernels/transaction/lib/note.masm | 30 +++---- .../asm/kernels/transaction/lib/prologue.masm | 6 +- .../asm/kernels/transaction/main.masm | 4 +- crates/miden-lib/asm/miden/input_note.masm | 32 +++---- crates/miden-lib/asm/miden/note.masm | 78 ++++++++-------- crates/miden-lib/asm/note_scripts/P2IDE.masm | 4 +- .../miden-lib/src/errors/tx_kernel_errors.rs | 24 ++--- crates/miden-lib/src/note/well_known_note.rs | 2 +- crates/miden-lib/src/transaction/memory.rs | 4 +- .../src/kernel_tests/tx/test_note.rs | 10 +-- crates/miden-tx/src/host/mod.rs | 16 ++-- docs/src/protocol_library.md | 14 +-- 15 files changed, 164 insertions(+), 169 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57101fb752..5b9b91ddad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ - [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). - [BREAKING] Move `TransactionKernelError` to miden-tx ([#1859](https://github.com/0xMiden/miden-base/pull/1859)). +- Change terminology of "current note" to "active note" ([#1863](https://github.com/0xMiden/miden-base/issues/1863)). - [BREAKING] Move and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). ## 0.11.2 (2025-09-08) diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 5637059a32..52a228529e 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -29,17 +29,17 @@ const.ERR_FAUCET_IS_NF_ASSET_ISSUED_PROC_CAN_ONLY_BE_CALLED_ON_NON_FUNGIBLE_FAUC const.ERR_KERNEL_PROCEDURE_OFFSET_OUT_OF_BOUNDS="provided kernel procedure offset is out of bounds" -const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note assets of current note because no note is currently being processed" +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note assets of active note because no note is currently being processed" -const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_RECIPIENT_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note recipient of current note because no note is currently being processed" +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_RECIPIENT_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note recipient of active note because no note is currently being processed" -const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note metadata of current note because no note is currently being processed" +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note metadata of active note because no note is currently being processed" -const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note inputs of current note because no note is currently being processed" +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note inputs of active note because no note is currently being processed" -const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SCRIPT_ROOT_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note script root of current note because no note is currently being processed" +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SCRIPT_ROOT_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note script root of active note because no note is currently being processed" -const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SERIAL_NUMBER_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note serial number of current note because no note is currently being processed" +const.ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SERIAL_NUMBER_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note serial number of active note because no note is currently being processed" # AUTHENTICATION # ================================================================================================= @@ -744,20 +744,20 @@ end #! Returns the assets information of the specified input note. #! -#! Inputs: [is_current_note, note_index, pad(14)] +#! Inputs: [is_active_note, note_index, pad(14)] #! Outputs: [ASSETS_COMMITMENT, num_assets, pad(11)] #! #! Where: -#! - is_current_note is the boolean flag indicating whether we should return the assets data from -#! the currently processing note or from the note with the specified index. +#! - is_active_note is the boolean flag indicating whether we should return the assets data from +#! the active note or from the note with the specified index. #! - note_index is the index of the input note whose assets info should be returned. Notice that if -#! is_current_note is 1, note_index is ignored. +#! is_active_note is 1, note_index is ignored. #! - ASSETS_COMMITMENT is a sequential hash of the assets in the specified input note. #! - num_assets is the number of assets in the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. -#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! - is_active_note is 1 and no input note is not being processed (attempted to access note inputs #! from incorrect context). #! #! Invocation: dynexec @@ -783,19 +783,19 @@ end #! Returns the recipient of the specified input note. #! -#! Inputs: [is_current_note, note_index, pad(15)] +#! Inputs: [is_active_note, note_index, pad(15)] #! Outputs: [RECIPIENT, pad(12)] #! #! Where: -#! - is_current_note is the boolean flag indicating whether we should return the assets data from -#! the currently processing note or from the note with the specified index. +#! - is_active_note is the boolean flag indicating whether we should return the assets data from +#! the active note or from the note with the specified index. #! - note_index is the index of the input note whose assets info should be returned. Notice that if -#! is_current_note is 1, note_index is ignored. +#! is_active_note is 1, note_index is ignored. #! - RECIPIENT is the commitment to the input note's script, inputs, the serial number. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. -#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! - is_active_note is 1 and no input note is not being processed (attempted to access note inputs #! from incorrect context). #! #! Invocation: dynexec @@ -821,19 +821,19 @@ end #! Returns the metadata of the specified input note. #! -#! Inputs: [is_current_note, note_index, pad(14)] +#! Inputs: [is_active_note, note_index, pad(14)] #! Outputs: [METADATA, pad(12)] #! #! Where: -#! - is_current_note is the boolean flag indicating whether we should return the metadata from -#! the currently processing note or from the note with the specified index. +#! - is_active_note is the boolean flag indicating whether we should return the metadata from +#! the active note or from the note with the specified index. #! - note_index is the index of the input note whose metadata should be returned. Notice that if -#! is_current_note is 1, note_index is ignored. +#! is_active_note is 1, note_index is ignored. #! - METADATA is the metadata of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. -#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! - is_active_note is 1 and no input note is not being processed (attempted to access note inputs #! from incorrect context). #! #! Invocation: dynexec @@ -859,19 +859,19 @@ end #! Returns the serial number of the specified input note. #! -#! Inputs: [is_current_note, note_index, pad(14)] +#! Inputs: [is_active_note, note_index, pad(14)] #! Outputs: [SERIAL_NUMBER, pad(12)] #! #! Where: -#! - is_current_note is the boolean flag indicating whether we should return the serial number -#! of the currently processing note or of the note with the specified index. +#! - is_active_note is the boolean flag indicating whether we should return the serial number +#! of the active note or of the note with the specified index. #! - note_index is the index of the input note whose serial number should be returned. Notice that -#! if is_current_note is 1, note_index is ignored. +#! if is_active_note is 1, note_index is ignored. #! - SERIAL_NUMBER is the serial number of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. -#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! - is_active_note is 1 and no input note is not being processed (attempted to access note inputs #! from incorrect context). #! #! Invocation: dynexec @@ -899,20 +899,20 @@ end #! Returns the inputs commitment and length of the specified input note. #! -#! Inputs: [is_current_note, note_index, pad(14)] +#! Inputs: [is_active_note, note_index, pad(14)] #! Outputs: [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11)] #! #! Where: -#! - is_current_note is the boolean flag indicating whether we should return the inputs commitment -#! and length from the currently processing note or from the note with the specified index. +#! - is_active_note is the boolean flag indicating whether we should return the inputs commitment +#! and length from the active note or from the note with the specified index. #! - note_index is the index of the input note whose data should be returned. Notice that if -#! is_current_note is 1, note_index is ignored. +#! is_active_note is 1, note_index is ignored. #! - NOTE_INPUTS_COMMITMENT is the inputs commitment of the specified input note. #! - num_inputs is the number of inputs of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. -#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! - is_active_note is 1 and no input note is not being processed (attempted to access note inputs #! from incorrect context). #! #! Invocation: dynexec @@ -944,19 +944,19 @@ end #! Returns the script root of the specified input note. #! -#! Inputs: [is_current_note, note_index, pad(14)] +#! Inputs: [is_active_note, note_index, pad(14)] #! Outputs: [SCRIPT_ROOT, pad(12)] #! #! Where: -#! - is_current_note is the boolean flag indicating whether we should return the inputs commitment -#! and length from the currently processing note or from the note with the specified index. +#! - is_active_note is the boolean flag indicating whether we should return the inputs commitment +#! and length from the active note or from the note with the specified index. #! - note_index is the index of the input note whose data should be returned. Notice that if -#! is_current_note is 1, note_index is ignored. +#! is_active_note is 1, note_index is ignored. #! - SCRIPT_ROOT is the script root of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. -#! - is_current_note is 1 and no input note is not being processed (attempted to access note inputs +#! - is_active_note is 1 and no input note is not being processed (attempted to access note inputs #! from incorrect context). #! #! Invocation: dynexec @@ -1472,17 +1472,17 @@ end #! Returns the memory pointer to the input note, depending on whether the requested note is current #! or it was requested by index. #! -#! If the pointer to the currently processing note was requested, but no note is being executed, 0 +#! If the pointer to the active note was requested, but no note is being executed, 0 #! is returned. #! -#! Inputs: [is_current_note, note_index] +#! Inputs: [is_active_note, note_index] #! Outputs: [input_note_ptr] #! #! Where: -#! - is_current_note is the boolean flag indicating whether we should return requested data from -#! the currently processing note or from the note with the specified index. +#! - is_active_note is the boolean flag indicating whether we should return requested data from +#! the active note or from the note with the specified index. #! - note_index is the index of the input note whose data should be returned. Notice that if -#! is_current_note is 1, note_index is ignored. +#! is_active_note is 1, note_index is ignored. #! - input_note_ptr is the pointer to the correct input note. #! #! Panics if: @@ -1490,13 +1490,13 @@ end proc.get_requested_note_ptr # get the memory pointer to the note with the specified index and verify it is valid swap exec.input_note::get_input_note_ptr swap - # => [is_current_note, indexed_input_note_ptr] + # => [is_active_note, indexed_input_note_ptr] # get the memory pointer to the currently processed input note - exec.memory::get_current_input_note_ptr swap - # => [is_current_note, current_input_note_ptr, indexed_input_note_ptr] + exec.memory::get_active_input_note_ptr swap + # => [is_active_note, active_input_note_ptr, indexed_input_note_ptr] - # If is_current_note flag is true (current note processing case), current_input_note_ptr remains + # If is_active_note flag is true (active note processing case), active_input_note_ptr remains # on the stack. If it is false, indexed_input_note_ptr remains instead. cdrop # => [input_note_ptr] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index 42f5a2e4a3..143a6d835e 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -23,8 +23,8 @@ const.ERR_LINK_MAP_MAX_ENTRIES_EXCEEDED="number of link map entries exceeds maxi # BOOK KEEPING # ------------------------------------------------------------------------------------------------- -# The memory address at which a pointer to the input note being executed is stored. -const.CURRENT_INPUT_NOTE_PTR=0 +# The memory address at which a pointer to the currently active input note is stored. +const.ACTIVE_INPUT_NOTE_PTR=0 # The memory address at which the number of output notes is stored. const.NUM_OUTPUT_NOTES_PTR=4 @@ -294,26 +294,26 @@ export.set_num_output_notes mem_store.NUM_OUTPUT_NOTES_PTR end -#! Returns a pointer to the input note being executed. +#! Returns a pointer to the active input note. #! #! Inputs: [] #! Outputs: [note_ptr] #! #! Where: -#! - note_ptr is the memory address of the data segment for the current input note. -export.get_current_input_note_ptr - mem_load.CURRENT_INPUT_NOTE_PTR +#! - note_ptr is the memory address of the data segment for the active note. +export.get_active_input_note_ptr + mem_load.ACTIVE_INPUT_NOTE_PTR end -#! Sets the current input note pointer to the input note being executed. +#! Sets the active note pointer to the specified value. #! #! Inputs: [note_ptr] #! Outputs: [] #! #! Where: #! - note_ptr is the new memory address of the data segment for the input note. -export.set_current_input_note_ptr - mem_store.CURRENT_INPUT_NOTE_PTR +export.set_active_input_note_ptr + mem_store.ACTIVE_INPUT_NOTE_PTR end #! Returns a pointer to the memory address at which the input vault root is stored. diff --git a/crates/miden-lib/asm/kernels/transaction/lib/note.masm b/crates/miden-lib/asm/kernels/transaction/lib/note.masm index 7ef13aa9a9..7737c18ec8 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/note.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/note.masm @@ -15,38 +15,38 @@ const.ERR_NOTE_NUM_OF_ASSETS_EXCEED_LIMIT="number of assets in a note exceed 255 # generating the output notes commitment. Must be NOTE_MEM_SIZE - 8; const.OUTPUT_NOTE_HASHING_MEM_DIFF=2040 -# CURRENTLY EXECUTING NOTE PROCEDURES +# ACTIVE NOTE PROCEDURES # ================================================================================================= -#! Move the current input note pointer to the next note and returns the pointer value. +#! Move the active input note pointer to the next note and returns the pointer value. #! #! Inputs: [] -#! Outputs: [current_input_note_ptr] +#! Outputs: [active_input_note_ptr] #! #! Where: -#! - current_input_note_ptr is the pointer to the next note to be processed. -export.increment_current_input_note_ptr - # get the current input note pointer - exec.memory::get_current_input_note_ptr +#! - active_input_note_ptr is the pointer to the next note to be processed. +export.increment_active_input_note_ptr + # get the active input note pointer + exec.memory::get_active_input_note_ptr # => [orig_input_note_ptr] # increment the pointer exec.constants::get_note_mem_size add - # => [current_input_note_ptr] + # => [active_input_note_ptr] - # set the current input note pointer to the incremented value - dup exec.memory::set_current_input_note_ptr - # => [current_input_note_ptr] + # set the active input note pointer to the incremented value + dup exec.memory::set_active_input_note_ptr + # => [active_input_note_ptr] end -#! Sets the current input note pointer to 0. This should be called after all input notes have +#! Sets the active input note pointer to 0. This should be called after all input notes have #! been processed. #! #! Inputs: [] #! Outputs: [] export.note_processing_teardown - # set the current input note pointer to 0 - push.0 exec.memory::set_current_input_note_ptr + # set the active input note pointer to 0 + push.0 exec.memory::set_active_input_note_ptr # => [] end @@ -64,7 +64,7 @@ export.prepare_note padw padw push.0.0.0 # => [pad(11)] - exec.memory::get_current_input_note_ptr + exec.memory::get_active_input_note_ptr # => [note_ptr, pad(11)] dup exec.memory::get_input_note_args movup.4 diff --git a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm index f9a7e706ea..12b8c5dc2c 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -995,7 +995,7 @@ proc.process_input_notes_data # - On the top of the stack is the hasher state containing the input notes commitment. The # hasher state will be updated by `process_input_note`. After the loop the commitment is # extracted. - # - Below the hasher state in the stack is the current note index. This number is used for two + # - Below the hasher state in the stack is the active note index. This number is used for two # purposes: # 1. Compute the input note's memory addresses, the index works as an offset. # 2. Determine the loop condition. The loop below runs until all input notes have been @@ -1037,10 +1037,10 @@ proc.process_input_notes_data assert_eqw.err=ERR_PROLOGUE_INPUT_NOTES_COMMITMENT_MISMATCH # => [idx+1, num_notes] - # set the current input note ptr to the address of the first input note + # set the active input note ptr to the address of the first input note push.0 exec.memory::get_input_note_ptr - exec.memory::set_current_input_note_ptr + exec.memory::set_active_input_note_ptr # => [idx+1, num_notes] drop drop diff --git a/crates/miden-lib/asm/kernels/transaction/main.masm b/crates/miden-lib/asm/kernels/transaction/main.masm index cfbf5fc3fa..c41603c661 100644 --- a/crates/miden-lib/asm/kernels/transaction/main.masm +++ b/crates/miden-lib/asm/kernels/transaction/main.masm @@ -107,8 +107,8 @@ proc.main.1 dropw dropw dropw dropw # => [pad(16)] - exec.note::increment_current_input_note_ptr - # => [current_input_note_ptr, pad(16)] + exec.note::increment_active_input_note_ptr + # => [active_input_note_ptr, pad(16)] # loop condition, exit when the memory ptr is after all input notes loc_load.0 neq diff --git a/crates/miden-lib/asm/miden/input_note.masm b/crates/miden-lib/asm/miden/input_note.masm index a3a07ecead..f75dda9fd2 100644 --- a/crates/miden-lib/asm/miden/input_note.masm +++ b/crates/miden-lib/asm/miden/input_note.masm @@ -28,10 +28,10 @@ export.get_assets_info # push the flag indicating that we want to request assets info from the note with the specified # index push.0 - # => [is_current_note = 0, note_index, 0] + # => [is_active_note = 0, note_index, 0] exec.kernel_proc_offsets::input_note_get_assets_info_offset - # => [offset, is_current_note = 0, note_index, 0] + # => [offset, is_active_note = 0, note_index, 0] # pad the stack padw swapw padw padw swapdw @@ -102,10 +102,10 @@ export.get_recipient # push the flag indicating that we want to request assets info from the note with the specified # index push.0 - # => [is_current_note = 0, note_index, 0] + # => [is_active_note = 0, note_index, 0] exec.kernel_proc_offsets::input_note_get_recipient_offset - # => [offset, is_current_note = 0, note_index, 0] + # => [offset, is_active_note = 0, note_index, 0] # pad the stack padw swapw padw padw swapdw @@ -140,14 +140,14 @@ export.get_metadata # push the flag indicating that we want to request metadata from the note with the specified # index push.0 - # => [is_current_note = 0, note_index, 0] + # => [is_active_note = 0, note_index, 0] exec.kernel_proc_offsets::input_note_get_metadata_offset - # => [offset, is_current_note = 0, note_index, 0] + # => [offset, is_active_note = 0, note_index, 0] # pad the stack padw swapw padw padw swapdw - # => [offset, is_current_note = 0, note_index, pad(13)] + # => [offset, is_active_note = 0, note_index, pad(13)] syscall.exec_kernel_proc # => [METADATA, pad(12)] @@ -179,14 +179,14 @@ export.get_inputs_info # push the flag indicating that we want to request inputs info from the note with the specified # index push.0 - # => [is_current_note = 0, note_index, 0] + # => [is_active_note = 0, note_index, 0] exec.kernel_proc_offsets::input_note_get_inputs_info_offset - # => [offset, is_current_note = 0, note_index, 0] + # => [offset, is_active_note = 0, note_index, 0] # pad the stack padw swapw padw padw swapdw - # => [offset, is_current_note = 0, note_index, pad(13)] + # => [offset, is_active_note = 0, note_index, pad(13)] syscall.exec_kernel_proc # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11)] @@ -220,14 +220,14 @@ export.get_script_root # push the flag indicating that we want to request script root from the note with the specified # index push.0 - # => [is_current_note = 0, note_index, 0] + # => [is_active_note = 0, note_index, 0] exec.kernel_proc_offsets::input_note_get_script_root_offset - # => [offset, is_current_note = 0, note_index, 0] + # => [offset, is_active_note = 0, note_index, 0] # pad the stack padw swapw padw padw swapdw - # => [offset, is_current_note = 0, note_index, pad(13)] + # => [offset, is_active_note = 0, note_index, pad(13)] syscall.exec_kernel_proc # => [SCRIPT_ROOT, pad(12)] @@ -258,14 +258,14 @@ export.get_serial_number # push the flag indicating that we want to request serial number from the note with the # specified index push.0 - # => [is_current_note = 0, note_index, 0] + # => [is_active_note = 0, note_index, 0] exec.kernel_proc_offsets::input_note_get_serial_number_offset - # => [offset, is_current_note = 0, note_index, 0] + # => [offset, is_active_note = 0, note_index, 0] # pad the stack padw swapw padw padw swapdw - # => [offset, is_current_note = 0, note_index, pad(13)] + # => [offset, is_active_note = 0, note_index, pad(13)] syscall.exec_kernel_proc # => [SERIAL_NUMBER, pad(12)] diff --git a/crates/miden-lib/asm/miden/note.masm b/crates/miden-lib/asm/miden/note.masm index a6761faed3..c276226570 100644 --- a/crates/miden-lib/asm/miden/note.masm +++ b/crates/miden-lib/asm/miden/note.masm @@ -16,17 +16,17 @@ const.ERR_NOTE_INVALID_NUMBER_OF_INPUTS="the specified number of note inputs doe # PROCEDURES # ================================================================================================= -#! Writes the assets of the currently executing note into memory starting at the specified address. +#! Writes the assets of the active note into memory starting at the specified address. #! #! Inputs: [dest_ptr] #! Outputs: [num_assets, dest_ptr] #! #! Where: #! - dest_ptr is the memory address to write the assets. -#! - num_assets is the number of assets in the currently executing note. +#! - num_assets is the number of assets in the active note. #! #! Panics if: -#! - no note is being processed. +#! - no note is currently active. #! #! Invocation: exec export.get_assets @@ -34,13 +34,12 @@ export.get_assets padw padw padw push.0.0 # => [pad(14), dest_ptr] - # push the flag indicating that we want to request assets info from the currently processing - # note + # push the flag indicating that we want to request assets info from the active note push.1 - # => [is_current_note = 1, pad(14), dest_ptr] + # => [is_active_note = 1, pad(14), dest_ptr] exec.kernel_proc_offsets::input_note_get_assets_info_offset - # => [offset, is_current_note = 1, pad(14), dest_ptr] + # => [offset, is_active_note = 1, pad(14), dest_ptr] syscall.exec_kernel_proc # => [ASSETS_COMMITMENT, num_assets, pad(11), dest_ptr] @@ -54,16 +53,16 @@ export.get_assets # => [num_assets, dest_ptr] end -#! Returns the recipient of the note currently being processed. +#! Returns the recipient of the active note. #! #! Inputs: [] #! Outputs: [RECIPIENT] #! #! Where: -#! - RECIPIENT is the commitment to the input note's script, inputs, the serial number. +#! - RECIPIENT is the commitment to the active note's script, inputs, the serial number. #! #! Panics if: -#! - no note is being processed. +#! - no note is currently active. #! #! Invocation: exec export.get_recipient @@ -71,13 +70,12 @@ export.get_recipient padw padw padw push.0.0 # => [pad(14)] - # push the flag indicating that we want to request recipient from the currently processing - # note + # push the flag indicating that we want to request recipient from the active note push.1 - # => [is_current_note = 1, pad(14)] + # => [is_active_note = 1, pad(14)] exec.kernel_proc_offsets::input_note_get_recipient_offset - # => [offset, is_current_note = 1, pad(14)] + # => [offset, is_active_note = 1, pad(14)] syscall.exec_kernel_proc # => [RECIPIENT, pad(12)] @@ -101,7 +99,7 @@ end #! - INPUTS is the data corresponding to the note's inputs. #! #! Panics if: -#! - no note is being processed. +#! - no note is currently active. #! #! Invocation: exec export.get_inputs @@ -109,13 +107,12 @@ export.get_inputs padw padw padw push.0.0 # => [pad(14), dest_ptr] - # push the flag indicating that we want to request inputs info from the currently processing - # note + # push the flag indicating that we want to request inputs info from the active note push.1 - # => [is_current_note = 1, pad(14), dest_ptr] + # => [is_active_note = 1, pad(14), dest_ptr] exec.kernel_proc_offsets::input_note_get_inputs_info_offset - # => [offset, is_current_note = 1, pad(14), dest_ptr] + # => [offset, is_active_note = 1, pad(14), dest_ptr] syscall.exec_kernel_proc # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11), dest_ptr] @@ -130,17 +127,16 @@ export.get_inputs # => [num_inputs, dest_ptr] end -#! Returns the sender of the note currently being processed. +#! Returns the sender of the active note. #! #! Inputs: [] #! Outputs: [sender_id_prefix, sender_id_suffix] #! #! Where: -#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender of the note currently -#! being processed. +#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender of the active note. #! #! Panics if: -#! - no note is being processed. +#! - no note is currently active. #! #! Invocation: exec export.get_sender @@ -148,12 +144,12 @@ export.get_sender padw padw padw push.0.0 # => [pad(14)] - # push the flag indicating that we want to request metadata from the currently processing note + # push the flag indicating that we want to request metadata from the active note push.1 - # => [is_current_note = 1, pad(14)] + # => [is_active_note = 1, pad(14)] exec.kernel_proc_offsets::input_note_get_metadata_offset - # => [offset, is_current_note = 1, pad(14)] + # => [offset, is_active_note = 1, pad(14)] syscall.exec_kernel_proc # => [METADATA, pad(12)] @@ -167,16 +163,16 @@ export.get_sender # => [sender_id_prefix, sender_id_suffix] end -#! Returns the serial number of the note currently being processed. +#! Returns the serial number of the active note. #! #! Inputs: [] #! Outputs: [SERIAL_NUMBER] #! #! Where: -#! - SERIAL_NUMBER is the serial number of the note currently being processed. +#! - SERIAL_NUMBER is the serial number of the active note. #! #! Panics if: -#! - no note is being processed. +#! - no note is currently active. #! #! Invocation: exec export.get_serial_number @@ -184,13 +180,12 @@ export.get_serial_number padw padw padw push.0.0 # => [pad(14)] - # push the flag indicating that we want to request serial number from the currently processing - # note + # push the flag indicating that we want to request serial number from the active note push.1 - # => [is_current_note = 1, pad(14)] + # => [is_active_note = 1, pad(14)] exec.kernel_proc_offsets::input_note_get_serial_number_offset - # => [offset, is_current_note = 1, pad(14)] + # => [offset, is_active_note = 1, pad(14)] syscall.exec_kernel_proc # => [SERIAL_NUMBER, pad(12)] @@ -200,16 +195,16 @@ export.get_serial_number # => [SERIAL_NUMBER] end -#! Returns the script root of the note currently being processed. +#! Returns the script root of the active note. #! #! Inputs: [] #! Outputs: [SCRIPT_ROOT] #! #! Where: -#! - SCRIPT_ROOT is the script root of the note currently being processed. +#! - SCRIPT_ROOT is the script root of the active note. #! #! Panics if: -#! - no note is being processed. +#! - no note is currently active. #! #! Invocation: exec export.get_script_root @@ -217,13 +212,12 @@ export.get_script_root padw padw padw push.0.0 # => [pad(14)] - # push the flag indicating that we want to request script root from the currently processing - # note + # push the flag indicating that we want to request script root from the active note push.1 - # => [is_current_note = 1, pad(14)] + # => [is_active_note = 1, pad(14)] exec.kernel_proc_offsets::input_note_get_script_root_offset - # => [offset, is_current_note = 1, pad(14)] + # => [offset, is_active_note = 1, pad(14)] syscall.exec_kernel_proc # => [SCRIPT_ROOT, pad(12)] @@ -233,7 +227,7 @@ export.get_script_root # => [SCRIPT_ROOT] end -#! Computes the commitment to the output note inputs starting at the specified memory address. +#! Computes the commitment to the note inputs starting at the specified memory address. #! #! This procedure checks that the provided number of note inputs is within limits and then computes #! the commitment. @@ -278,7 +272,7 @@ end #! - max_inputs_per_note is the max inputs per note. export.::miden::util::note::get_max_inputs_per_note -#! Adds all assets from the currently executing note to the account vault. +#! Adds all assets from the active note to the native account's vault. #! #! Inputs: [] #! Outputs: [] diff --git a/crates/miden-lib/asm/note_scripts/P2IDE.masm b/crates/miden-lib/asm/note_scripts/P2IDE.masm index 1771cef85e..4fe6bb72f5 100644 --- a/crates/miden-lib/asm/note_scripts/P2IDE.masm +++ b/crates/miden-lib/asm/note_scripts/P2IDE.masm @@ -40,11 +40,11 @@ end #! Outputs: [] #! #! Panics if: -#! - the reclaim of the current note is disabled. +#! - the reclaim of the active note is disabled. #! - the reclaim block height is not reached yet. #! - the account attempting to reclaim the note is not the sender account. proc.reclaim_note - # check that the reclaim of the current note is enabled + # check that the reclaim of the active note is enabled movup.3 dup neq.0 assert.err=ERR_P2IDE_RECLAIM_DISABLED # => [reclaim_block_height, account_id_prefix, account_id_suffix, current_block_height] diff --git a/crates/miden-lib/src/errors/tx_kernel_errors.rs b/crates/miden-lib/src/errors/tx_kernel_errors.rs index f27b38f709..5c16058e08 100644 --- a/crates/miden-lib/src/errors/tx_kernel_errors.rs +++ b/crates/miden-lib/src/errors/tx_kernel_errors.rs @@ -143,18 +143,18 @@ pub const ERR_NON_FUNGIBLE_ASSET_FORMAT_MOST_SIGNIFICANT_BIT_MUST_BE_ZERO: MasmE /// Error Message: "failed to build the non-fungible asset because the provided faucet id is not from a non-fungible faucet" pub const ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID: MasmError = MasmError::from_static_str("failed to build the non-fungible asset because the provided faucet id is not from a non-fungible faucet"); -/// Error Message: "failed to access note assets of current note because no note is currently being processed" -pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note assets of current note because no note is currently being processed"); -/// Error Message: "failed to access note inputs of current note because no note is currently being processed" -pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note inputs of current note because no note is currently being processed"); -/// Error Message: "failed to access note metadata of current note because no note is currently being processed" -pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note metadata of current note because no note is currently being processed"); -/// Error Message: "failed to access note recipient of current note because no note is currently being processed" -pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_RECIPIENT_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note recipient of current note because no note is currently being processed"); -/// Error Message: "failed to access note script root of current note because no note is currently being processed" -pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SCRIPT_ROOT_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note script root of current note because no note is currently being processed"); -/// Error Message: "failed to access note serial number of current note because no note is currently being processed" -pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SERIAL_NUMBER_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note serial number of current note because no note is currently being processed"); +/// Error Message: "failed to access note assets of active note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note assets of active note because no note is currently being processed"); +/// Error Message: "failed to access note inputs of active note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note inputs of active note because no note is currently being processed"); +/// Error Message: "failed to access note metadata of active note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note metadata of active note because no note is currently being processed"); +/// Error Message: "failed to access note recipient of active note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_RECIPIENT_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note recipient of active note because no note is currently being processed"); +/// Error Message: "failed to access note script root of active note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SCRIPT_ROOT_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note script root of active note because no note is currently being processed"); +/// Error Message: "failed to access note serial number of active note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SERIAL_NUMBER_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note serial number of active note because no note is currently being processed"); /// Error Message: "note data does not match the commitment" pub const ERR_NOTE_DATA_DOES_NOT_MATCH_COMMITMENT: MasmError = MasmError::from_static_str("note data does not match the commitment"); /// Error Message: "adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807" diff --git a/crates/miden-lib/src/note/well_known_note.rs b/crates/miden-lib/src/note/well_known_note.rs index 7224460a6f..eb94770b10 100644 --- a/crates/miden-lib/src/note/well_known_note.rs +++ b/crates/miden-lib/src/note/well_known_note.rs @@ -108,7 +108,7 @@ impl WellKnownNote { // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns the expected inputs number of the current note. + /// Returns the expected inputs number of the active note. pub fn num_expected_inputs(&self) -> usize { match self { Self::P2ID => Self::P2ID_NUM_INPUTS, diff --git a/crates/miden-lib/src/transaction/memory.rs b/crates/miden-lib/src/transaction/memory.rs index 7c0619b0cd..8b3705271e 100644 --- a/crates/miden-lib/src/transaction/memory.rs +++ b/crates/miden-lib/src/transaction/memory.rs @@ -73,8 +73,8 @@ pub const FAUCET_STORAGE_DATA_SLOT: StorageSlot = 0; // BOOKKEEPING // ------------------------------------------------------------------------------------------------ -/// The memory address at which a pointer to the input note being executed is stored. -pub const CURRENT_INPUT_NOTE_PTR: MemoryAddress = 0; +/// The memory address at which a pointer to the currently active input note is stored. +pub const ACTIVE_INPUT_NOTE_PTR: MemoryAddress = 0; /// The memory address at which the number of output notes is stored. pub const NUM_OUTPUT_NOTES_PTR: MemoryAddress = 4; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index e8dff741bb..1928734219 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -10,7 +10,7 @@ use miden_lib::errors::tx_kernel_errors::ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADAT use miden_lib::testing::mock_account::MockAccountExt; use miden_lib::testing::note::NoteBuilder; use miden_lib::transaction::TransactionKernel; -use miden_lib::transaction::memory::CURRENT_INPUT_NOTE_PTR; +use miden_lib::transaction::memory::ACTIVE_INPUT_NOTE_PTR; use miden_lib::utils::ScriptBuilder; use miden_objects::account::{Account, AccountBuilder, AccountId}; use miden_objects::assembly::DefaultSourceManager; @@ -243,8 +243,8 @@ fn test_get_assets() -> anyhow::Result<()> { # process note 0 call.process_note_0 - # increment current input note pointer - exec.note_internal::increment_current_input_note_ptr + # increment active input note pointer + exec.note_internal::increment_active_input_note_ptr # prepare note 1 exec.note_internal::prepare_note @@ -523,7 +523,7 @@ fn test_note_script_and_note_args() -> miette::Result<()> { repeat.11 movup.4 drop end # => [NOTE_ARGS0, pad(16)] - exec.note::increment_current_input_note_ptr drop + exec.note::increment_active_input_note_ptr drop # => [NOTE_ARGS0, pad(16)] exec.note::prepare_note drop @@ -572,7 +572,7 @@ fn note_setup_stack_assertions(process: &Process, inputs: &TransactionContext) { fn note_setup_memory_assertions(process: &Process) { // assert that the correct pointer is stored in bookkeeping memory assert_eq!( - process.get_kernel_mem_word(CURRENT_INPUT_NOTE_PTR)[0], + process.get_kernel_mem_word(ACTIVE_INPUT_NOTE_PTR)[0], Felt::from(input_note_data_ptr(0)) ); } diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 9a9dcdbf4b..571e76c674 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -24,7 +24,7 @@ use alloc::vec::Vec; use miden_lib::transaction::memory::{ ACCOUNT_STACK_TOP_PTR, - CURRENT_INPUT_NOTE_PTR, + ACTIVE_INPUT_NOTE_PTR, NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR, }; use miden_lib::transaction::{TransactionEvent, TransactionEventError}; @@ -284,7 +284,7 @@ where }, TransactionEvent::NoteExecutionStart => { - let note_id = Self::get_current_note_id(process)?.expect( + let note_id = Self::get_active_note_id(process)?.expect( "Note execution interval measurement is incorrect: check the placement of the start and the end of the interval", ); self.tx_progress.start_note_execution(process.clk(), note_id); @@ -779,16 +779,16 @@ where // HELPER FUNCTIONS // -------------------------------------------------------------------------------------------- - /// Returns the ID of the currently executing input note, or None if the note execution hasn't - /// started yet or has already ended. + /// Returns the ID of the active note, or None if the note execution hasn't started yet or has + /// already ended. /// /// # Errors - /// Returns an error if the address of the currently executing input note is invalid (e.g., - /// greater than `u32::MAX`). - fn get_current_note_id(process: &ProcessState) -> Result, EventError> { + /// Returns an error if the address of the active note is invalid (e.g., greater than + /// `u32::MAX`). + fn get_active_note_id(process: &ProcessState) -> Result, EventError> { // get the note address in `Felt` or return `None` if the address hasn't been accessed // previously. - let note_address_felt = match process.get_mem_value(process.ctx(), CURRENT_INPUT_NOTE_PTR) { + let note_address_felt = match process.get_mem_value(process.ctx(), ACTIVE_INPUT_NOTE_PTR) { Some(addr) => addr, None => return Ok(None), }; diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index 91a1ee88c3..6348acfb85 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -52,19 +52,19 @@ Account procedures can be used to read and write to account storage, add or remo ## Note Procedures (`miden::note`) -Note procedures can be used to fetch data from the note that is currently being processed. +Note procedures can be used to fetch data from the active note. | Procedure | Description | Context | | --- | --- | --- | -| `get_assets` | Writes the assets of the currently executing note into memory starting at the specified address.

Inputs: `[dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Note | -| `get_recipient` | Returns the recipient of the note currently being processed.

Inputs: `[]`
Outputs: `[RECIPIENT]` | Note | +| `get_assets` | Writes the assets of the active note into memory starting at the specified address.

Inputs: `[dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Note | +| `get_recipient` | Returns the recipient of the active note.

Inputs: `[]`
Outputs: `[RECIPIENT]` | Note | | `get_inputs` | Writes the note's inputs to the specified memory address.

Inputs: `[dest_ptr]`
Outputs: `[num_inputs, dest_ptr]` | Note | -| `get_sender` | Returns the sender of the note currently being processed.

Inputs: `[]`
Outputs: `[sender_id_prefix, sender_id_suffix]` | Note | -| `get_serial_number` | Returns the serial number of the note currently being processed.

Inputs: `[]`
Outputs: `[SERIAL_NUMBER]` | Note | -| `get_script_root` | Returns the script root of the note currently being processed.

Inputs: `[]`
Outputs: `[SCRIPT_ROOT]` | Note | +| `get_sender` | Returns the sender of the active note.

Inputs: `[]`
Outputs: `[sender_id_prefix, sender_id_suffix]` | Note | +| `get_serial_number` | Returns the serial number of the active note.

Inputs: `[]`
Outputs: `[SERIAL_NUMBER]` | Note | +| `get_script_root` | Returns the script root of the active note.

Inputs: `[]`
Outputs: `[SCRIPT_ROOT]` | Note | | `compute_inputs_commitment` | Computes the commitment to the output note inputs starting at the specified memory address.

Inputs: `[inputs_ptr, num_inputs]`
Outputs: `[INPUTS_COMMITMENT]` | Any | | `get_max_inputs_per_note` | Returns the max allowed number of input values per note.

Inputs: `[]`
Outputs: `[max_inputs_per_note]` | Any | -| `add_assets_to_account` | Adds all assets from the currently executing note to the account vault.

Inputs: `[]`
Outputs: `[]` | Note | +| `add_assets_to_account` | Adds all assets from the active note to the account vault.

Inputs: `[]`
Outputs: `[]` | Note | | `write_assets_to_memory` | Writes the assets data stored in the advice map to the memory specified by the provided destination pointer.

Inputs: `[ASSETS_COMMITMENT, num_assets, dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Any | | `build_recipient_hash` | Returns the `RECIPIENT` for a specified `SERIAL_NUM`, `SCRIPT_ROOT`, and inputs commitment.

Inputs: `[SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT]`
Outputs: `[RECIPIENT]` | Any | | `build_recipient` | Builds the recipient hash from note inputs, script root, and serial number.

Inputs: `[inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]`
Outputs: `[RECIPIENT]` | Any | From a8a0f513f39dcac86a738bcd8d95978f759cb15d Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:07:04 +0300 Subject: [PATCH 037/133] refactor: change to auth_* prefix (#1892) --- CHANGELOG.md | 1 + .../asm/account_components/multisig_rpo_falcon_512.masm | 2 +- crates/miden-lib/asm/account_components/no_auth.masm | 2 +- crates/miden-lib/asm/account_components/rpo_falcon_512.masm | 2 +- .../miden-lib/asm/account_components/rpo_falcon_512_acl.masm | 2 +- crates/miden-lib/src/account/auth/no_auth.rs | 2 +- crates/miden-lib/src/account/auth/rpo_falcon_512.rs | 4 ++-- .../src/testing/account_component/conditional_auth.rs | 4 ++-- crates/miden-lib/src/testing/account_component/incr_nonce.rs | 4 ++-- crates/miden-objects/src/account/builder/mod.rs | 2 +- crates/miden-objects/src/account/code/mod.rs | 4 ++-- crates/miden-objects/src/account/component/mod.rs | 2 +- crates/miden-objects/src/testing/noop_auth_component.rs | 4 ++-- crates/miden-testing/src/kernel_tests/tx/test_account.rs | 2 +- crates/miden-testing/src/kernel_tests/tx/test_auth.rs | 2 +- crates/miden-testing/src/kernel_tests/tx/test_tx.rs | 2 +- docs/src/transaction.md | 4 ++-- 17 files changed, 23 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b9b91ddad..218a549fbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -186,6 +186,7 @@ - Add a new auth component `RpoFalcon512Acl` ([#1531](https://github.com/0xMiden/miden-base/pull/1531)). - [BREAKING] Change `BasicFungibleFaucet` to use `RpoFalcon512Acl` for authentication ([#1531](https://github.com/0xMiden/miden-base/pull/1531)). - Introduce `MockChain` methods for executing at an older block (#1541). +- [BREAKING] Change authentication component procedure name prefix from `auth__*` to `auth_*` ([#1861](https://github.com/0xMiden/miden-base/issues/1861)). ### Fixes diff --git a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm index 9f4ba1d512..b9c120a3a8 100644 --- a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm @@ -101,7 +101,7 @@ end #! - the same transaction has already been executed (replay protection). #! #! Invocation: call -export.auth__tx_rpo_falcon512_multisig +export.auth_tx_rpo_falcon512_multisig exec.account::incr_nonce drop # => [SALT] diff --git a/crates/miden-lib/asm/account_components/no_auth.masm b/crates/miden-lib/asm/account_components/no_auth.masm index b1476fb769..b97b662eba 100644 --- a/crates/miden-lib/asm/account_components/no_auth.masm +++ b/crates/miden-lib/asm/account_components/no_auth.masm @@ -11,7 +11,7 @@ use.std::word #! #! Inputs: [pad(16)] #! Outputs: [pad(16)] -export.auth__no_auth +export.auth_no_auth # check if the account state has changed by comparing initial and final commitments exec.account::get_initial_commitment diff --git a/crates/miden-lib/asm/account_components/rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/rpo_falcon_512.masm index 3bd833aca5..efa5e0df44 100644 --- a/crates/miden-lib/asm/account_components/rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/rpo_falcon_512.masm @@ -26,7 +26,7 @@ const.PUBLIC_KEY_SLOT=0 #! Outputs: [pad(16)] #! #! Invocation: call -export.auth__tx_rpo_falcon512 +export.auth_tx_rpo_falcon512 dropw # => [pad(16)] diff --git a/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm b/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm index 609f9b3f4b..fb564f1675 100644 --- a/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm +++ b/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm @@ -32,7 +32,7 @@ const.AUTH_TRIGGER_PROCS_MAP_SLOT=2 #! Outputs: [pad(16)] #! #! Invocation: call -export.auth__tx_rpo_falcon512_acl.2 +export.auth_tx_rpo_falcon512_acl.2 dropw # => [pad(16)] diff --git a/crates/miden-lib/src/account/auth/no_auth.rs b/crates/miden-lib/src/account/auth/no_auth.rs index 746db7f0c3..8838060884 100644 --- a/crates/miden-lib/src/account/auth/no_auth.rs +++ b/crates/miden-lib/src/account/auth/no_auth.rs @@ -10,7 +10,7 @@ use crate::account::components::no_auth_library; /// they differ. This avoids unnecessary nonce increments for transactions that don't /// modify the account state. /// -/// It exports the procedure `auth__no_auth`, which: +/// It exports the procedure `auth_no_auth`, which: /// - Checks if the account state has changed by comparing initial and final commitments /// - Only increments the nonce if the account state has actually changed /// - Provides no cryptographic authentication diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512.rs index b1d7c54cc2..50636000b6 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512.rs @@ -10,8 +10,8 @@ use crate::account::components::rpo_falcon_512_library; /// component, the `miden` library (i.e. [`MidenLib`](crate::MidenLib)) must be available to the /// assembler which is the case when using [`TransactionKernel::assembler()`][kasm]. The procedures /// of this component are: -/// - `auth__tx_rpo_falcon512`, which can be used to verify a signature provided via the advice -/// stack to authenticate a transaction. +/// - `auth_tx_rpo_falcon512`, which can be used to verify a signature provided via the advice stack +/// to authenticate a transaction. /// /// This component supports all account types. /// diff --git a/crates/miden-lib/src/testing/account_component/conditional_auth.rs b/crates/miden-lib/src/testing/account_component/conditional_auth.rs index dfb4c626e7..4cbdff0ced 100644 --- a/crates/miden-lib/src/testing/account_component/conditional_auth.rs +++ b/crates/miden-lib/src/testing/account_component/conditional_auth.rs @@ -15,7 +15,7 @@ static CONDITIONAL_AUTH_CODE: LazyLock = LazyLock::new(|| { const.WRONG_ARGS="{ERR_WRONG_ARGS_MSG}" - export.auth__conditional + export.auth_conditional # => [AUTH_ARGS] # If [97, 98, 99] is passed as an argument, all good. @@ -42,7 +42,7 @@ static CONDITIONAL_AUTH_LIBRARY: LazyLock = LazyLock::new(|| { /// Creates a mock authentication [`AccountComponent`] for testing purposes. /// -/// The component defines an `auth__conditional` procedure that conditionally succeeds and +/// The component defines an `auth_conditional` procedure that conditionally succeeds and /// conditionally increments the nonce based on the authentication arguments. /// /// The auth procedure expects the first three arguments as [99, 98, 97] to succeed. diff --git a/crates/miden-lib/src/testing/account_component/incr_nonce.rs b/crates/miden-lib/src/testing/account_component/incr_nonce.rs index 3cc85304fb..c2973f9d63 100644 --- a/crates/miden-lib/src/testing/account_component/incr_nonce.rs +++ b/crates/miden-lib/src/testing/account_component/incr_nonce.rs @@ -7,7 +7,7 @@ use crate::transaction::TransactionKernel; const INCR_NONCE_AUTH_CODE: &str = " use.miden::account - export.auth__incr_nonce + export.auth_incr_nonce exec.account::incr_nonce drop end "; @@ -20,7 +20,7 @@ static INCR_NONCE_AUTH_LIBRARY: LazyLock = LazyLock::new(|| { /// Creates a mock authentication [`AccountComponent`] for testing purposes. /// -/// The component defines an `auth__incr_nonce` procedure that always increments the nonce by 1. +/// The component defines an `auth_incr_nonce` procedure that always increments the nonce by 1. pub struct IncrNonceAuthComponent; impl From for AccountComponent { diff --git a/crates/miden-objects/src/account/builder/mod.rs b/crates/miden-objects/src/account/builder/mod.rs index 70f0b39020..2da160a424 100644 --- a/crates/miden-objects/src/account/builder/mod.rs +++ b/crates/miden-objects/src/account/builder/mod.rs @@ -116,7 +116,7 @@ impl AccountBuilder { /// Adds a designated authentication [`AccountComponent`] to the builder. /// /// This component may contain multiple procedures, but is expected to contain exactly one - /// authentication procedure (named `auth__*`). + /// authentication procedure (named `auth_*`). /// Calling this method multiple times will override the previous auth component. /// /// Procedures from this component will be placed at the beginning of the account procedure diff --git a/crates/miden-objects/src/account/code/mod.rs b/crates/miden-objects/src/account/code/mod.rs index 944e029f6d..d72690d689 100644 --- a/crates/miden-objects/src/account/code/mod.rs +++ b/crates/miden-objects/src/account/code/mod.rs @@ -553,11 +553,11 @@ mod tests { let code_with_multiple_auth = " use.miden::account - export.auth__basic + export.auth_basic push.1 drop end - export.auth__secondary + export.auth_secondary push.0 drop end "; diff --git a/crates/miden-objects/src/account/component/mod.rs b/crates/miden-objects/src/account/component/mod.rs index a982eb5cc4..ea3287e503 100644 --- a/crates/miden-objects/src/account/component/mod.rs +++ b/crates/miden-objects/src/account/component/mod.rs @@ -207,7 +207,7 @@ impl AccountComponent { let mut procedures = Vec::new(); for module in self.library.module_infos() { for (_, procedure_info) in module.procedures() { - let is_auth = procedure_info.name.contains("auth__"); + let is_auth = procedure_info.name.starts_with("auth_"); procedures.push((procedure_info.digest, is_auth)); } } diff --git a/crates/miden-objects/src/testing/noop_auth_component.rs b/crates/miden-objects/src/testing/noop_auth_component.rs index 2139f87b66..8ebc9fb800 100644 --- a/crates/miden-objects/src/testing/noop_auth_component.rs +++ b/crates/miden-objects/src/testing/noop_auth_component.rs @@ -6,7 +6,7 @@ use crate::utils::sync::LazyLock; // ================================================================================================ const NOOP_AUTH_CODE: &str = " - export.auth__noop + export.auth_noop push.0 drop end "; @@ -19,7 +19,7 @@ static NOOP_AUTH_LIBRARY: LazyLock = LazyLock::new(|| { /// Creates a mock authentication [`AccountComponent`] for testing purposes. /// -/// The component defines an `auth__noop` procedure that does nothing (always succeeds). +/// The component defines an `auth_noop` procedure that does nothing (always succeeds). pub struct NoopAuthComponent; impl From for AccountComponent { diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 56ed82bb06..128cee158d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -1240,7 +1240,7 @@ fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { let source_code = " use.miden::account - export.auth__incr_nonce_twice + export.auth_incr_nonce_twice exec.account::incr_nonce drop exec.account::incr_nonce drop end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs index bdd7772506..8c31802f10 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs @@ -77,7 +77,7 @@ fn test_auth_procedure_called_from_wrong_context() -> anyhow::Result<()> { // Create a transaction script that calls the auth procedure let tx_script_source = " begin - call.::auth__incr_nonce + call.::auth_incr_nonce end "; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index da6f7523d7..67e191f749 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -458,7 +458,7 @@ fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { use.miden::tx #! Inputs: [AUTH_ARGS, pad(12)] #! Outputs: [pad(16)] - export.auth__abort_tx + export.auth_abort_tx dropw # => [pad(16)] diff --git a/docs/src/transaction.md b/docs/src/transaction.md index 97d16510d5..8911e702c4 100644 --- a/docs/src/transaction.md +++ b/docs/src/transaction.md @@ -61,7 +61,7 @@ To illustrate the `Transaction` protocol, we provide two examples for a basic `T Let's assume account A wants to create a P2ID note. P2ID notes are pay-to-ID notes that can only be consumed by a specified target account ID. Note creators can provide the target account ID using the [note inputs](note.md#inputs). -In this example, account A uses the basic wallet and the authentication component provided by `miden-lib`. The basic wallet component defines the methods `wallets::basic::create_note` and `wallets::basic::move_asset_to_note` to create notes with assets, and `wallets::basic::receive_asset` to receive assets. The authentication component exposes `auth::basic::auth__tx_rpo_falcon512` which allows for signing a transaction. Some account methods like `account::get_id` are always exposed. +In this example, account A uses the basic wallet and the authentication component provided by `miden-lib`. The basic wallet component defines the methods `wallets::basic::create_note` and `wallets::basic::move_asset_to_note` to create notes with assets, and `wallets::basic::receive_asset` to receive assets. The authentication component exposes `auth::basic::auth_tx_rpo_falcon512` which allows for signing a transaction. Some account methods like `account::get_id` are always exposed. The executor inputs to the Miden VM a `Transaction` script in which he places on the stack the data (tag, aux, note_type, execution_hint, RECIPIENT) of the note(s) that he wants to create using `wallets::basic::create_note` during the said `Transaction`. The [`NoteRecipient`](https://github.com/0xMiden/miden-base/blob/main/crates/miden-objects/src/note/recipient.rs) is a value that describes under which condition a note can be consumed and is built using a `serial_number`, the `note_script` (in this case P2ID script) and the `note_inputs`. The Miden VM will execute the `Transaction` script and create the note(s). After having been created, the executor can use `wallets::basic::move_asset_to_note` to move assets from the account's vault to the notes vault. @@ -79,7 +79,7 @@ Then the P2ID note script is being executed. The script starts by reading the no If the check passes, the note script pushes the assets it holds into the account's vault. For every asset the note contains, the script calls the `wallets::basic::receive_asset` method exposed by the account's wallet component. The `wallets::basic::receive_asset` procedure calls `account::add_asset`, which cannot be called from the note itself. This allows accounts to control what functionality to expose, e.g. whether the account supports receiving assets or not, and the note cannot bypass that. -After the assets are stored in the account's vault, the transaction script is being executed. The script calls `auth::basic::auth__tx_rpo_falcon512` which is explicitly exposed in the account interface. The method is used to verify a provided signature against a public key stored in the account's storage and a commitment to this specific transaction. If the signature can be verified, the method increments the nonce. +After the assets are stored in the account's vault, the transaction script is being executed. The script calls `auth::basic::auth_tx_rpo_falcon512` which is explicitly exposed in the account interface. The method is used to verify a provided signature against a public key stored in the account's storage and a commitment to this specific transaction. If the signature can be verified, the method increments the nonce. The Epilogue finalizes the transaction by computing the final account hash, asserting the nonce increment and checking that no assets were created or destroyed in the transaction — that means the net sum of all assets must stay the same. From b35f8e6cedb9d41f6f2c2dc66ca4247f4e998f48 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 16 Sep 2025 08:17:01 +0200 Subject: [PATCH 038/133] feat: load foreign accounts lazily on first access (#1873) * feat: Move account loading to account module * feat: Add `ACCOUNT_BEFORE_LOAD` event * feat: Add lazy loading of foreign accounts * chore: Check if account is already loaded * feat: Pass foreign account code to prover * chore: add standard derives for `ScriptMastForestStore` * chore: add changelog * chore: Rename `enable_lazy_loading` * chore: Rename to `get_foreign_account_inputs` * chore: adjust order of comments * chore: improve naming * chore: specify order of adv mutations * chore: Rename event to `ACCOUNT_BEFORE_LOAD_FOREIGN` * feat: Simplify `AccountProcedureIndexMap` to take `AccountCode` * chore: Refactor to `load_foreign_account_code` * chore: Remove advice map account ID check * fix: doc build * fix: procedure index map `insert_code` function docs * chore: rename event to `ACCOUNT_BEFORE_FOREIGN_LOAD` * chore: Remove unnecessary `dropw`s * fix: typo --- CHANGELOG.md | 1 + .../asm/kernels/transaction/api.masm | 46 +------ .../asm/kernels/transaction/lib/account.masm | 91 ++++++++++++- crates/miden-lib/src/transaction/events.rs | 6 + crates/miden-lib/src/transaction/inputs.rs | 68 ++++++---- .../src/transaction/kernel_procedures.rs | 2 +- .../src/transaction/executed_tx.rs | 9 +- .../src/transaction/tx_witness.rs | 21 ++- .../src/kernel_tests/tx/test_fpi.rs | 29 +++-- .../src/kernel_tests/tx/test_lazy_loading.rs | 10 +- crates/miden-testing/src/mock_host.rs | 23 ++-- .../miden-testing/src/tx_context/builder.rs | 35 +++-- .../miden-testing/src/tx_context/context.rs | 25 +++- crates/miden-tx/src/errors/mod.rs | 15 ++- crates/miden-tx/src/executor/data_store.rs | 10 +- crates/miden-tx/src/executor/exec_host.rs | 82 +++++++++++- crates/miden-tx/src/executor/mod.rs | 24 +++- .../miden-tx/src/host/account_procedures.rs | 122 +++++------------- crates/miden-tx/src/host/mod.rs | 45 ++++++- .../src/host/script_mast_forest_store.rs | 1 + crates/miden-tx/src/prover/mod.rs | 20 ++- crates/miden-tx/src/prover/prover_host.rs | 5 +- 22 files changed, 461 insertions(+), 229 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 218a549fbd..41e8e67904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). - Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). - Enabled lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). +- [BREAKING] Enabled lazy loading of foreign accounts during transaction execution ([#1873](https://github.com/0xMiden/miden-base/pull/1873)). - Added lazy loading of the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). - Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). - Added Serialize and Deserialize Traits on `SigningInputs` ([#1858](https://github.com/0xMiden/miden-base/pull/1858)) diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 52a228529e..92787d8251 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -1310,50 +1310,8 @@ export.tx_start_foreign_context exec.memory::push_ptr_to_account_stack # OS => [foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] - # construct the word with account ID to load the core account data from the advice map - push.0.0 - # OS => [0, 0, foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] - - # move the core account data to the advice stack - adv.push_mapval - # OS => [0, 0, foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] - # AS => [[foreign_account_id_prefix, foreign_account_id_suffix, 0, account_nonce], VAULT_ROOT, STORAGE_ROOT, CODE_ROOT] - - # store the id and nonce of the foreign account to the memory - dropw adv_loadw - exec.memory::set_account_id_and_nonce dropw - # OS => [pad(16)] - # AS => [VAULT_ROOT, STORAGE_ROOT, CODE_ROOT] - - # store the vault root of the foreign account to the memory - adv_loadw exec.memory::set_account_vault_root dropw - # OS => [pad(16)] - # AS => [STORAGE_ROOT, CODE_ROOT] - - # move the storage root and the code root to the operand stack - adv_loadw padw adv_loadw - # OS => [CODE_ROOT, STORAGE_ROOT, pad(16)] - # AS => [] - - # store the code root into the memory - exec.memory::set_account_code_commitment - # OS => [CODE_ROOT, STORAGE_ROOT, pad(16)] - # AS => [] - - # save the account procedure data into the memory - exec.account::save_account_procedure_data - # OS => [STORAGE_ROOT, pad(16)] - # AS => [] - - # store the storage root to the memory - exec.memory::set_account_storage_commitment - # OS => [STORAGE_ROOT, pad(16)] - # AS => [] - - # save the storage slots data into the memory - exec.account::save_account_storage_data - # OS => [pad(16)] - # AS => [] + # load the advice data into the current account memory section + exec.account::load_foreign_account end # make sure that the state of the loaded foreign account corresponds to this commitment in the diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 2d06ac6078..b1cb0d44e6 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -5,7 +5,6 @@ use.std::mem use.$kernel::account_id use.$kernel::asset_vault -use.$kernel::asset use.$kernel::account_delta use.$kernel::constants use.$kernel::memory @@ -122,6 +121,9 @@ const.ACCOUNT_PROCEDURE_DATA_LENGTH=8 # EVENTS # ================================================================================================= +# Event emitted before a foreign account is loaded from the advice inputs. +const.ACCOUNT_BEFORE_FOREIGN_LOAD=131104 + # Event emitted before an asset is added to the account vault. const.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT=131072 # Event emitted after an asset is added to the account vault. @@ -922,6 +924,87 @@ end # DATA LOADERS # ================================================================================================= +#! Loads account data from the advice inputs into the _current_ account's memory section. +#! +#! Inputs: +#! Operand stack: [account_id_prefix, account_id_suffix] +#! Advice map: { +#! ACCOUNT_ID: [[account_id_suffix, account_id_prefix, 0, account_nonce]], +#! VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT], +#! STORAGE_COMMITMENT: [[STORAGE_SLOT_DATA]], +#! CODE_COMMITMENT: [[ACCOUNT_PROCEDURE_DATA]], +#! } +#! Outputs: +#! Operand stack: [] +#! +#! Where: +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the account. +#! - ACCOUNT_ID is the word constructed from the account_id as follows: +#! [account_id_suffix, account_id_prefix, 0, 0]. +#! - account_nonce is the nonce of the account. +#! - VAULT_ROOT is the commitment of the account's vault. +#! - STORAGE_COMMITMENT is the commitment to the account's storage. +#! - STORAGE_SLOT_DATA is the data contained in the storage slot which is constructed as follows: +#! [SLOT_VALUE, slot_type, 0, 0, 0]. +#! - CODE_COMMITMENT is the commitment to the account's code. +#! - ACCOUNT_PROCEDURE_DATA is the information about account procedures which is constructed as +#! follows: [PROCEDURE_MAST_ROOT, storage_offset, 0, 0, 0]. +#! +#! Panics if: +#! - the number of account procedures exceeded the maximum limit of 256. +#! - the computed account code commitment does not match the provided account code commitment. +#! - the number of account storage slots exceeded the maximum limit of 255. +#! - the computed account storage commitment does not match the provided account storage commitment. +export.load_foreign_account + emit.ACCOUNT_BEFORE_FOREIGN_LOAD + # => [account_id_prefix, account_id_suffix] + + # construct the word with account ID to load the core account data from the advice map + push.0.0 + # OS => [0, 0, account_id_prefix, account_id_suffix] + + # move the core account data to the advice stack + adv.push_mapval + # OS => [0, 0, account_id_prefix, account_id_suffix] + # AS => [[account_id_prefix, account_id_suffix, 0, account_nonce], VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT] + + # store the id and nonce of the foreign account to the memory + adv_loadw + exec.memory::set_account_id_and_nonce + # OS => [] + # AS => [VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT] + + # store the vault root of the foreign account to the memory + adv_loadw exec.memory::set_account_vault_root + # OS => [] + # AS => [STORAGE_COMMITMENT, CODE_COMMITMENT] + + # move the storage root and the code root to the operand stack + adv_loadw padw adv_loadw + # OS => [CODE_COMMITMENT, STORAGE_COMMITMENT] + # AS => [] + + # store the code root into the memory + exec.memory::set_account_code_commitment + # OS => [CODE_COMMITMENT, STORAGE_COMMITMENT] + # AS => [] + + # save the account procedure data into the memory + exec.save_account_procedure_data + # OS => [STORAGE_COMMITMENT] + # AS => [] + + # store the storage root to the memory + exec.memory::set_account_storage_commitment + # OS => [STORAGE_COMMITMENT] + # AS => [] + + # save the storage slots data into the memory + exec.save_account_storage_data + # OS => [] + # AS => [] +end + #! Saves storage slots data into memory and validates that the storage commitment matches the #! sequential storage hash. #! @@ -940,7 +1023,7 @@ end #! #! Panics if: #! - the number of account storage slots exceeded the maximum limit of 255. -#! - the computed account storage commitment does not match the provided account storage commitment +#! - the computed account storage commitment does not match the provided account storage commitment. export.save_account_storage_data # move storage slot data from the advice map to the advice stack adv.push_mapvaln @@ -1013,8 +1096,8 @@ end #! follows: [PROCEDURE_MAST_ROOT, storage_offset, storage_size, 0, 0] #! #! Panics if: -#! - the number of account procedures exceeded the maximum limit of 256 -#! - the computed account code commitment does not match the provided account code commitment +#! - the number of account procedures exceeded the maximum limit of 256. +#! - the computed account code commitment does not match the provided account code commitment. export.save_account_procedure_data # move procedure data from the advice map to the advice stack adv.push_mapvaln diff --git a/crates/miden-lib/src/transaction/events.rs b/crates/miden-lib/src/transaction/events.rs index 53030e7665..d9f0a5d940 100644 --- a/crates/miden-lib/src/transaction/events.rs +++ b/crates/miden-lib/src/transaction/events.rs @@ -8,6 +8,8 @@ use super::TransactionEventError; // TRANSACTION EVENT // ================================================================================================ +const ACCOUNT_BEFORE_FOREIGN_LOAD: u32 = 0x2_0020; // 131104 + const ACCOUNT_VAULT_BEFORE_ADD_ASSET: u32 = 0x2_0000; // 131072 const ACCOUNT_VAULT_AFTER_ADD_ASSET: u32 = 0x2_0001; // 131073 @@ -67,6 +69,8 @@ const UNAUTHORIZED_EVENT: u32 = 0x2_001e; // 131102 #[repr(u32)] #[derive(Debug, Clone, Eq, PartialEq)] pub enum TransactionEvent { + AccountBeforeForeignLoad = ACCOUNT_BEFORE_FOREIGN_LOAD, + AccountVaultBeforeAddAsset = ACCOUNT_VAULT_BEFORE_ADD_ASSET, AccountVaultAfterAddAsset = ACCOUNT_VAULT_AFTER_ADD_ASSET, @@ -144,6 +148,8 @@ impl TryFrom for TransactionEvent { } match value { + ACCOUNT_BEFORE_FOREIGN_LOAD => Ok(TransactionEvent::AccountBeforeForeignLoad), + ACCOUNT_VAULT_BEFORE_ADD_ASSET => Ok(TransactionEvent::AccountVaultBeforeAddAsset), ACCOUNT_VAULT_AFTER_ADD_ASSET => Ok(TransactionEvent::AccountVaultAfterAddAsset), diff --git a/crates/miden-lib/src/transaction/inputs.rs b/crates/miden-lib/src/transaction/inputs.rs index 3c3eb24c44..7a24eecf5f 100644 --- a/crates/miden-lib/src/transaction/inputs.rs +++ b/crates/miden-lib/src/transaction/inputs.rs @@ -5,6 +5,7 @@ use miden_objects::block::AccountWitness; use miden_objects::crypto::SequentialCommit; use miden_objects::crypto::merkle::InnerNodeInfo; use miden_objects::transaction::{ + AccountInputs, InputNote, PartialBlockchain, TransactionArgs, @@ -12,6 +13,7 @@ use miden_objects::transaction::{ }; use miden_objects::vm::AdviceInputs; use miden_objects::{EMPTY_WORD, Felt, FieldElement, Word, ZERO}; +use miden_processor::AdviceMutation; use thiserror::Error; use super::TransactionKernel; @@ -21,7 +23,7 @@ use super::TransactionKernel; /// Advice inputs wrapper for inputs that are meant to be used exclusively in the transaction /// kernel. -#[derive(Default, Clone, Debug)] +#[derive(Debug, Clone, Default)] pub struct TransactionAdviceInputs(AdviceInputs); impl TransactionAdviceInputs { @@ -62,24 +64,12 @@ impl TransactionAdviceInputs { // if a seed was provided, extend the map appropriately if let Some(seed) = tx_inputs.account_seed() { // ACCOUNT_ID |-> ACCOUNT_SEED - let account_id_key = build_account_id_key(partial_native_acc.id()); + let account_id_key = Self::account_id_map_key(partial_native_acc.id()); inputs.add_map_entry(account_id_key, seed.to_vec()); } // --- foreign account injection -------------------------------------- - - for foreign_acc in tx_args.foreign_account_inputs() { - inputs.add_account(foreign_acc.account())?; - inputs.add_account_witness(foreign_acc.witness()); - - // for foreign accounts, we need to insert the id to state mapping - // NOTE: keep this in sync with the start_foreign_context kernel procedure - let account_id_key = build_account_id_key(foreign_acc.id()); - let header = AccountHeader::from(foreign_acc.account()); - - // ACCOUNT_ID |-> [ID_AND_NONCE, VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT] - inputs.add_map_entry(account_id_key, header.as_elements()); - } + inputs.add_foreign_accounts(tx_args.foreign_account_inputs())?; // any extra user-supplied advice inputs.extend(tx_args.advice_inputs().clone()); @@ -97,6 +87,18 @@ impl TransactionAdviceInputs { self.0 } + /// Consumes self and returns an iterator of [`AdviceMutation`]s in arbitrary order. + pub fn into_advice_mutations(self) -> impl Iterator { + [ + AdviceMutation::ExtendMap { other: self.0.map }, + AdviceMutation::ExtendMerkleStore { + infos: self.0.store.inner_nodes().collect(), + }, + AdviceMutation::ExtendStack { values: self.0.stack }, + ] + .into_iter() + } + // MUTATORS // -------------------------------------------------------------------------------------------- @@ -105,6 +107,34 @@ impl TransactionAdviceInputs { self.0.extend(adv_inputs); } + /// Adds the provided account inputs into the advice inputs. + pub fn add_foreign_accounts<'inputs>( + &mut self, + foreign_account_inputs: impl IntoIterator, + ) -> Result<(), TransactionAdviceMapMismatch> { + for foreign_acc in foreign_account_inputs { + self.add_account(foreign_acc.account())?; + self.add_account_witness(foreign_acc.witness()); + + // for foreign accounts, we need to insert the id to state mapping + // NOTE: keep this in sync with the account::load_from_advice procedure + let account_id_key = Self::account_id_map_key(foreign_acc.id()); + let header = AccountHeader::from(foreign_acc.account()); + + // ACCOUNT_ID |-> [ID_AND_NONCE, VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT] + self.add_map_entry(account_id_key, header.as_elements()); + } + + Ok(()) + } + + /// Returns the advice map key where: + /// - the seed for native accounts is stored. + /// - the account header for foreign accounts is stored. + pub fn account_id_map_key(id: AccountId) -> Word { + Word::from([id.suffix(), id.prefix().as_felt(), ZERO, ZERO]) + } + /// Extend the advice stack with the transaction inputs. /// /// The following data is pushed to the advice stack: @@ -381,6 +411,7 @@ impl TransactionAdviceInputs { } self.add_map_entry(tx_inputs.input_notes().commitment(), note_data); + Ok(()) } @@ -423,13 +454,6 @@ impl From for TransactionAdviceInputs { } } -// HELPER FUNCTIONS -// ================================================================================================ - -fn build_account_id_key(id: AccountId) -> Word { - Word::from([id.suffix(), id.prefix().as_felt(), ZERO, ZERO]) -} - // CONFLICT ERROR // ================================================================================================ diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index a46ef3afd1..b080026d09 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -96,7 +96,7 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ // tx_get_block_timestamp word!("0x7903185b847517debb6c2072364e3e757b99ee623e97c2bd0a4661316c5c5418"), // tx_start_foreign_context - word!("0x447889987e3a8b589b7852c32752df4e2a855e67076496195d40364c0b0730b0"), + word!("0x7fac8c3ab1af62226616bd157d01238ce3252f006bf4004e1b2ac6bc38f6c6f1"), // tx_end_foreign_context word!("0xaa0018aa8da890b73511879487f65553753fb7df22de380dd84c11e6f77eec6f"), // tx_get_expiration_delta diff --git a/crates/miden-objects/src/transaction/executed_tx.rs b/crates/miden-objects/src/transaction/executed_tx.rs index 2396646363..3906dd7cf6 100644 --- a/crates/miden-objects/src/transaction/executed_tx.rs +++ b/crates/miden-objects/src/transaction/executed_tx.rs @@ -16,7 +16,7 @@ use super::{ TransactionOutputs, TransactionWitness, }; -use crate::account::PartialAccount; +use crate::account::{AccountCode, PartialAccount}; use crate::asset::FungibleAsset; use crate::block::BlockNumber; use crate::utils::serde::{ @@ -47,6 +47,7 @@ pub struct ExecutedTransaction { tx_outputs: TransactionOutputs, account_delta: AccountDelta, tx_args: TransactionArgs, + foreign_account_code: Vec, advice_witness: AdviceInputs, tx_measurements: TransactionMeasurements, } @@ -64,6 +65,7 @@ impl ExecutedTransaction { tx_outputs: TransactionOutputs, account_delta: AccountDelta, tx_args: TransactionArgs, + foreign_account_code: Vec, advice_witness: AdviceInputs, tx_measurements: TransactionMeasurements, ) -> Self { @@ -85,6 +87,7 @@ impl ExecutedTransaction { tx_outputs, account_delta, tx_args, + foreign_account_code, advice_witness, tx_measurements, } @@ -175,6 +178,7 @@ impl ExecutedTransaction { let tx_witness = TransactionWitness { tx_inputs: self.tx_inputs, tx_args: self.tx_args, + foreign_account_code: self.foreign_account_code, advice_witness: self.advice_witness, }; @@ -202,6 +206,7 @@ impl Serializable for ExecutedTransaction { self.tx_outputs.write_into(target); self.account_delta.write_into(target); self.tx_args.write_into(target); + self.foreign_account_code.write_into(target); self.advice_witness.write_into(target); self.tx_measurements.write_into(target); } @@ -213,6 +218,7 @@ impl Deserializable for ExecutedTransaction { let tx_outputs = TransactionOutputs::read_from(source)?; let account_delta = AccountDelta::read_from(source)?; let tx_args = TransactionArgs::read_from(source)?; + let foreign_account_code = >::read_from(source)?; let advice_witness = AdviceInputs::read_from(source)?; let tx_measurements = TransactionMeasurements::read_from(source)?; @@ -221,6 +227,7 @@ impl Deserializable for ExecutedTransaction { tx_outputs, account_delta, tx_args, + foreign_account_code, advice_witness, tx_measurements, )) diff --git a/crates/miden-objects/src/transaction/tx_witness.rs b/crates/miden-objects/src/transaction/tx_witness.rs index 1bfd070ac6..d9bd501a0a 100644 --- a/crates/miden-objects/src/transaction/tx_witness.rs +++ b/crates/miden-objects/src/transaction/tx_witness.rs @@ -1,4 +1,7 @@ +use alloc::vec::Vec; + use super::{AdviceInputs, TransactionArgs, TransactionInputs}; +use crate::account::AccountCode; use crate::utils::serde::{ByteReader, Deserializable, DeserializationError, Serializable}; // TRANSACTION WITNESS @@ -16,8 +19,9 @@ use crate::utils::serde::{ByteReader, Deserializable, DeserializationError, Seri /// - Optional transaction arguments which may contain a transaction script, note arguments, /// transaction script arguments and any additional advice data to initialize the advice provider /// with prior to transaction execution. -/// - Advice witness which contains all data requested by the VM from the advice provider while -/// executing the transaction program. +/// - Account code needed for invoking procedures on foreign accounts. +/// - Advice witness which contains all data that is in the advice provider by the end of the +/// transaction execution. /// /// TODO: currently, the advice witness contains redundant and irrelevant data (e.g., tx inputs /// and tx outputs; account codes and a subset of that data in advice inputs). @@ -27,6 +31,7 @@ use crate::utils::serde::{ByteReader, Deserializable, DeserializationError, Seri pub struct TransactionWitness { pub tx_inputs: TransactionInputs, pub tx_args: TransactionArgs, + pub foreign_account_code: Vec, pub advice_witness: AdviceInputs, } @@ -37,6 +42,7 @@ impl Serializable for TransactionWitness { fn write_into(&self, target: &mut W) { self.tx_inputs.write_into(target); self.tx_args.write_into(target); + self.foreign_account_code.write_into(target); self.advice_witness.write_into(target); } } @@ -45,9 +51,15 @@ impl Deserializable for TransactionWitness { fn read_from(source: &mut R) -> Result { let tx_inputs = TransactionInputs::read_from(source)?; let tx_args = TransactionArgs::read_from(source)?; + let foreign_account_code = >::read_from(source)?; let advice_witness = AdviceInputs::read_from(source)?; - Ok(Self { tx_inputs, tx_args, advice_witness }) + Ok(Self { + tx_inputs, + tx_args, + foreign_account_code, + advice_witness, + }) } } @@ -97,7 +109,7 @@ mod tests { ); let tx_inputs = TransactionInputs::new( - account, + account.clone(), None, block_header.clone(), partial_blockchain.clone(), @@ -108,6 +120,7 @@ mod tests { let witness = TransactionWitness { tx_inputs, tx_args: TransactionArgs::default(), + foreign_account_code: vec![account.code().clone()], advice_witness: AdviceInputs::default(), }; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index e6e6e8dd46..2453663f91 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -53,7 +53,7 @@ use crate::{Auth, MockChainBuilder, assert_execution_error, assert_transaction_e // ================================================================================================ #[test] -fn test_fpi_memory() -> anyhow::Result<()> { +fn test_fpi_memory_single_account() -> anyhow::Result<()> { // Prepare the test data let storage_slots = vec![AccountStorage::mock_item_0().slot, AccountStorage::mock_item_2().slot]; @@ -78,9 +78,10 @@ fn test_fpi_memory() -> anyhow::Result<()> { end "; + let source_manager = Arc::new(DefaultSourceManager::default()); let foreign_account_component = AccountComponent::compile( foreign_account_code_source, - TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), + TransactionKernel::with_kernel_library(source_manager.clone()), storage_slots.clone(), )? .with_supports_all_types(); @@ -100,6 +101,7 @@ fn test_fpi_memory() -> anyhow::Result<()> { MockChainBuilder::with_accounts([native_account.clone(), foreign_account.clone()])? .build()?; mock_chain.prove_next_block()?; + let fpi_inputs = mock_chain .get_foreign_account_inputs(foreign_account.id()) .expect("failed to get foreign account inputs"); @@ -108,6 +110,7 @@ fn test_fpi_memory() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(vec![fpi_inputs]) + .with_source_manager(source_manager) .build()?; // GET ITEM @@ -531,9 +534,10 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { end "; + let source_manager = Arc::new(DefaultSourceManager::default()); let foreign_account_component = AccountComponent::compile( NamedSource::new("foreign_account", foreign_account_code_source), - TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), + TransactionKernel::with_kernel_library(source_manager.clone()), storage_slots, )? .with_supports_all_types(); @@ -618,7 +622,7 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { map_key = STORAGE_LEAVES_2[0].0, ); - let tx_script = ScriptBuilder::default() + let tx_script = ScriptBuilder::with_source_manager(source_manager.clone()) .with_dynamically_linked_library(foreign_account_component.library())? .compile_tx_script(code)?; @@ -629,8 +633,10 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { mock_chain .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") - .foreign_accounts(vec![foreign_account_inputs]) + .foreign_accounts([foreign_account_inputs]) + .enable_lazy_loading() .tx_script(tx_script) + .with_source_manager(source_manager) .build()? .execute_blocking()?; @@ -691,9 +697,10 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { end "#; + let source_manager = Arc::new(DefaultSourceManager::default()); let second_foreign_account_component = AccountComponent::compile( second_foreign_account_code_source, - TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), + TransactionKernel::with_kernel_library(source_manager.clone()), storage_slots, )? .with_supports_all_types(); @@ -751,7 +758,7 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { let first_foreign_account_component = AccountComponent::compile( NamedSource::new("first_foreign_account", first_foreign_account_code_source), - TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), + TransactionKernel::with_kernel_library(source_manager.clone()), storage_slots, )? .with_supports_all_types(); @@ -839,7 +846,7 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { foreign_suffix = first_foreign_account.id().suffix(), ); - let tx_script = ScriptBuilder::default() + let tx_script = ScriptBuilder::with_source_manager(source_manager.clone()) .with_dynamically_linked_library(first_foreign_account_component.library())? .compile_tx_script(code)?; @@ -847,8 +854,10 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(foreign_account_inputs) + .enable_lazy_loading() .extend_advice_inputs(advice_inputs) .tx_script(tx_script) + .with_source_manager(source_manager) .build()? .execute_blocking()?; @@ -1014,6 +1023,7 @@ fn test_nested_fpi_stack_overflow() { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(foreign_accounts) + .enable_lazy_loading() .tx_script(tx_script) .build().unwrap(); @@ -1127,6 +1137,7 @@ fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(vec![foreign_account_inputs]) + .enable_lazy_loading() .extend_advice_inputs(advice_inputs) .tx_script(tx_script) .build()? @@ -1352,6 +1363,7 @@ fn test_fpi_get_account_id() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(vec![foreign_account_inputs]) + .enable_lazy_loading() .tx_script(tx_script) .build()? .execute_blocking()?; @@ -1462,6 +1474,7 @@ fn test_fpi_get_account_nonce() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(vec![foreign_account_inputs]) + .enable_lazy_loading() .tx_script(tx_script) .build()? .execute_blocking()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index d59599a288..3139adb5a2 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -59,7 +59,7 @@ fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account() .tx_script(tx_script) .extend_input_notes(vec![asset_note]) - .enable_partial_loading() + .enable_lazy_loading() .with_source_manager(source_manager) .build()?; let account = tx_context.account().clone(); @@ -125,7 +125,7 @@ fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { .build()? .build_tx_context(account, &[], &[])? .tx_script(tx_script) - .enable_partial_loading() + .enable_lazy_loading() .with_source_manager(source_manager) .build()?; let account = tx_context.account().clone(); @@ -159,7 +159,7 @@ fn loading_fee_asset_succeeds() -> anyhow::Result<()> { builder .build()? .build_tx_context(account, &[], &[])? - .enable_partial_loading() + .enable_lazy_loading() .build()? .execute_blocking()?; @@ -219,7 +219,7 @@ fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { let tx = TransactionContextBuilder::with_existing_mock_account() .tx_script(tx_script) - .enable_partial_loading() + .enable_lazy_loading() .with_source_manager(source_manager) .build()? .execute_blocking()?; @@ -281,7 +281,7 @@ fn getting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { TransactionContextBuilder::with_existing_mock_account() .tx_script(tx_script) - .enable_partial_loading() + .enable_lazy_loading() .with_source_manager(source_manager) .build()? .execute_blocking()?; diff --git a/crates/miden-testing/src/mock_host.rs b/crates/miden-testing/src/mock_host.rs index 7c74b2a2ba..288c495371 100644 --- a/crates/miden-testing/src/mock_host.rs +++ b/crates/miden-testing/src/mock_host.rs @@ -1,16 +1,15 @@ use alloc::boxed::Box; -use alloc::collections::BTreeSet; use alloc::rc::Rc; use alloc::sync::Arc; use alloc::vec::Vec; use miden_lib::transaction::{TransactionEvent, TransactionEventError}; -use miden_objects::account::{AccountHeader, AccountVaultDelta}; +use miden_objects::account::{AccountCode, AccountVaultDelta}; use miden_objects::assembly::debuginfo::SourceManagerSync; use miden_objects::assembly::{DefaultSourceManager, SourceManager}; +use miden_objects::transaction::AccountInputs; use miden_objects::{Felt, Word}; use miden_processor::{ - AdviceInputs, AdviceMutation, BaseHost, ContextId, @@ -36,17 +35,19 @@ pub struct MockHost { } impl MockHost { - /// Returns a new [`MockHost`] instance with the provided [`AdviceInputs`]. + /// Returns a new [`MockHost`] instance with the provided inputs. pub fn new( - account: AccountHeader, - advice_inputs: &AdviceInputs, + native_account_code: &AccountCode, mast_store: Rc, - mut foreign_code_commitments: BTreeSet, + foreign_account_inputs: &[AccountInputs], ) -> Self { - foreign_code_commitments.insert(account.code_commitment()); - let account_procedure_index_map = - AccountProcedureIndexMap::new(foreign_code_commitments, advice_inputs) - .expect("account procedure index map should be valid"); + let account_procedure_index_map = AccountProcedureIndexMap::new( + foreign_account_inputs + .iter() + .map(AccountInputs::code) + .chain([native_account_code]), + ) + .expect("account procedure index map should be valid"); Self { acct_procedure_index_map: account_procedure_index_map, diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 221cbfb0fa..36ae3e8bbd 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -12,6 +12,7 @@ use miden_objects::EMPTY_WORD; use miden_objects::account::{ Account, AccountHeader, + AccountId, PartialAccount, PartialStorage, PartialStorageMap, @@ -79,7 +80,7 @@ pub struct TransactionContextBuilder { advice_inputs: AdviceInputs, authenticator: Option, expected_output_notes: Vec, - foreign_account_inputs: Vec, + foreign_account_inputs: BTreeMap, input_notes: Vec, tx_script: Option, tx_script_args: Word, @@ -87,7 +88,7 @@ pub struct TransactionContextBuilder { transaction_inputs: Option, auth_args: Word, signatures: Vec<(PublicKey, Word, Vec)>, - load_partial_account: bool, + is_lazy_loading_enabled: bool, } impl TransactionContextBuilder { @@ -104,10 +105,10 @@ impl TransactionContextBuilder { advice_inputs: Default::default(), transaction_inputs: None, note_args: BTreeMap::new(), - foreign_account_inputs: vec![], + foreign_account_inputs: BTreeMap::new(), auth_args: EMPTY_WORD, signatures: Vec::new(), - load_partial_account: false, + is_lazy_loading_enabled: false, } } @@ -175,8 +176,9 @@ impl TransactionContextBuilder { } /// Set foreign account codes that are used by the transaction - pub fn foreign_accounts(mut self, inputs: Vec) -> Self { - self.foreign_account_inputs = inputs; + pub fn foreign_accounts(mut self, inputs: impl IntoIterator) -> Self { + self.foreign_account_inputs + .extend(inputs.into_iter().map(|account_inputs| (account_inputs.id(), account_inputs))); self } @@ -216,11 +218,13 @@ impl TransactionContextBuilder { } /// Causes the transaction to only construct a minimal partial account as the transaction - /// input, causing lazy loading of assets throughout transaction execution. + /// input, causing lazy loading of assets and storage map items throughout transaction + /// execution. Additionally, foreign accounts aren't provided via the transaction args but are + /// lazy loaded as well. /// /// This exists to test lazy loading selectively and should go away in the future. - pub fn enable_partial_loading(mut self) -> Self { - self.load_partial_account = true; + pub fn enable_lazy_loading(mut self) -> Self { + self.is_lazy_loading_enabled = true; self } @@ -294,7 +298,7 @@ impl TransactionContextBuilder { // If partial loading is enabled, construct an account that doesn't contain all // merkle paths of assets and storage maps, in order to test lazy loading. // Otherwise, load the full account. - let tx_inputs = if self.load_partial_account { + let tx_inputs = if self.is_lazy_loading_enabled { let (account, account_seed, block_header, partial_blockchain, input_notes) = tx_inputs.into_parts(); // Construct a partial vault that tracks the empty word, but none of the assets @@ -344,7 +348,13 @@ impl TransactionContextBuilder { tx_inputs }; - let tx_args = TransactionArgs::new(AdviceMap::default(), self.foreign_account_inputs) + let foreign_account_inputs = if self.is_lazy_loading_enabled { + Vec::new() + } else { + self.foreign_account_inputs.values().cloned().collect() + }; + + let tx_args = TransactionArgs::new(AdviceMap::default(), foreign_account_inputs) .with_note_args(self.note_args); let mut tx_args = if let Some(tx_script) = self.tx_script { @@ -366,7 +376,7 @@ impl TransactionContextBuilder { let mast_forest_store = TransactionMastStore::new(); mast_forest_store.load_account_code(tx_inputs.account().code()); - for acc_inputs in tx_args.foreign_account_inputs() { + for acc_inputs in self.foreign_account_inputs.values() { mast_forest_store.insert(acc_inputs.code().mast()); } @@ -376,6 +386,7 @@ impl TransactionContextBuilder { Ok(TransactionContext { account: self.account, expected_output_notes: self.expected_output_notes, + foreign_account_inputs: self.foreign_account_inputs, tx_args, tx_inputs, mast_store, diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index 20c5754f9a..44f11e4619 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -1,5 +1,5 @@ use alloc::borrow::ToOwned; -use alloc::collections::BTreeSet; +use alloc::collections::{BTreeMap, BTreeSet}; use alloc::rc::Rc; use alloc::sync::Arc; use alloc::vec::Vec; @@ -12,6 +12,7 @@ use miden_objects::asset::AssetWitness; use miden_objects::block::{BlockHeader, BlockNumber}; use miden_objects::note::Note; use miden_objects::transaction::{ + AccountInputs, ExecutedTransaction, InputNote, InputNotes, @@ -52,6 +53,7 @@ use crate::tx_context::builder::MockAuthenticator; pub struct TransactionContext { pub(super) account: Account, pub(super) expected_output_notes: Vec, + pub(super) foreign_account_inputs: BTreeMap, pub(super) tx_args: TransactionArgs, pub(super) tx_inputs: TransactionInputs, pub(super) mast_store: TransactionMastStore, @@ -114,10 +116,9 @@ impl TransactionContext { let advice_inputs = advice_inputs.into_advice_inputs(); CodeExecutor::new( MockHost::new( - self.tx_inputs.account().into(), - &advice_inputs, + self.tx_inputs().account().code(), mast_store, - self.tx_args.to_foreign_account_code_commitments(), + self.tx_args.foreign_account_inputs(), ) .with_source_manager(self.source_manager()), ) @@ -205,6 +206,22 @@ impl DataStore for TransactionContext { async move { Ok((partial_account, seed, header, mmr)) } } + fn get_foreign_account_inputs( + &self, + foreign_account_id: AccountId, + _ref_block: BlockNumber, + ) -> impl FutureMaybeSend> { + // Note that we cannot validate that the foreign account inputs are valid for the + // transaction's reference block. + async move { + self.foreign_account_inputs.get(&foreign_account_id).cloned().ok_or_else(|| { + DataStoreError::other(format!( + "failed to find foreign account {foreign_account_id}" + )) + }) + } + } + fn get_vault_asset_witness( &self, account_id: AccountId, diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index 321f3862ce..f9600ec86b 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -149,6 +149,8 @@ pub enum TransactionProverError { // case, the diagnostic is lost if the execution error is not explicitly unwrapped. #[error("failed to execute transaction kernel program:\n{}", PrintDiagnostic::new(.0))] TransactionProgramExecutionFailed(ExecutionError), + #[error("failed to create account procedure index map")] + CreateAccountProcedureIndexMap(#[source] TransactionHostError), #[error("failed to create transaction host")] TransactionHostCreationFailed(#[source] TransactionHostError), /// Custom error variant for errors not covered by the other variants. @@ -261,9 +263,9 @@ pub enum TransactionKernelError { "note input data in advice provider contains fewer elements ({actual}) than specified ({specified}) by its inputs length" )] TooFewElementsForNoteInputs { specified: u64, actual: u64 }, - #[error("account procedure with procedure root {0} is not in the advice provider")] + #[error("account procedure with procedure root {0} is not in the account procedure index map")] UnknownAccountProcedure(Word), - #[error("code commitment {0} is not in the advice provider")] + #[error("code commitment {0} is not in the account procedure index map")] UnknownCodeCommitment(Word), #[error("account storage slots number is missing in memory at address {0}")] AccountStorageSlotsNumMissing(u32), @@ -271,6 +273,15 @@ pub enum TransactionKernelError { NonceCanOnlyIncrementOnce, #[error("failed to convert fee asset into fungible asset")] FailedToConvertFeeAsset(#[source] AssetError), + #[error( + "failed to get inputs for foreign account {foreign_account_id} from data store at reference block {ref_block}" + )] + GetForeignAccountInputs { + foreign_account_id: AccountId, + ref_block: BlockNumber, + // thiserror will return this when calling Error::source on TransactionKernelError. + source: DataStoreError, + }, #[error( "failed to get vault asset witness from data store for vault root {vault_root} and vault_key {vault_key}" )] diff --git a/crates/miden-tx/src/executor/data_store.rs b/crates/miden-tx/src/executor/data_store.rs index 354400607c..e5e32a748d 100644 --- a/crates/miden-tx/src/executor/data_store.rs +++ b/crates/miden-tx/src/executor/data_store.rs @@ -3,7 +3,7 @@ use alloc::collections::BTreeSet; use miden_objects::account::{AccountId, PartialAccount, StorageMapWitness}; use miden_objects::asset::AssetWitness; use miden_objects::block::{BlockHeader, BlockNumber}; -use miden_objects::transaction::PartialBlockchain; +use miden_objects::transaction::{AccountInputs, PartialBlockchain}; use miden_processor::{FutureMaybeSend, MastForestStore, Word}; use crate::DataStoreError; @@ -35,6 +35,14 @@ pub trait DataStore: MastForestStore { Result<(PartialAccount, Option, BlockHeader, PartialBlockchain), DataStoreError>, >; + /// Returns a partial foreign account state together with a witness, proving its validity in the + /// specified transaction reference block. + fn get_foreign_account_inputs( + &self, + foreign_account_id: AccountId, + ref_block: BlockNumber, + ) -> impl FutureMaybeSend>; + /// Returns a witness for an asset in the requested account's vault with the requested vault /// root. /// diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 11d25fc682..9406292bcc 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -2,11 +2,18 @@ use alloc::collections::BTreeMap; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_lib::transaction::TransactionEvent; -use miden_objects::account::{AccountDelta, AccountId, PartialAccount, StorageSlotType}; +use miden_lib::transaction::{TransactionAdviceInputs, TransactionEvent}; +use miden_objects::account::{ + AccountCode, + AccountDelta, + AccountId, + PartialAccount, + StorageSlotType, +}; use miden_objects::assembly::debuginfo::Location; use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; use miden_objects::asset::{Asset, AssetWitness, FungibleAsset}; +use miden_objects::block::BlockNumber; use miden_objects::crypto::merkle::SmtProof; use miden_objects::transaction::{InputNote, InputNotes, OutputNote}; use miden_objects::vm::AdviceMap; @@ -54,6 +61,14 @@ where /// not present in the `generated_signatures` field. authenticator: Option<&'auth AUTH>, + /// The reference block of the transaction. + ref_block: BlockNumber, + + /// The foreign account code that was lazy loaded during transaction execution. + /// + /// This is required for re-executing the transaction, e.g. as part of transaction proving. + accessed_foreign_account_code: Vec, + /// Contains generated signatures (as a message |-> signature map) required for transaction /// execution. Once a signature was created for a given message, it is inserted into this map. /// After transaction execution, these can be inserted into the advice inputs to re-execute the @@ -82,6 +97,7 @@ where scripts_mast_store: ScriptMastForestStore, acct_procedure_index_map: AccountProcedureIndexMap, authenticator: Option<&'auth AUTH>, + ref_block: BlockNumber, source_manager: Arc, ) -> Self { let base_host = TransactionBaseHost::new( @@ -95,6 +111,8 @@ where Self { base_host, authenticator, + ref_block, + accessed_foreign_account_code: Vec::new(), generated_signatures: BTreeMap::new(), source_manager, } @@ -111,6 +129,53 @@ where // EVENT HANDLERS // -------------------------------------------------------------------------------------------- + /// Handles a request for a foreign account by querying the data store for its account inputs. + async fn on_foreign_account_requested( + &mut self, + foreign_account_id: AccountId, + ) -> Result, TransactionKernelError> { + let foreign_account_inputs = self + .base_host + .store() + .get_foreign_account_inputs(foreign_account_id, self.ref_block) + .await + .map_err(|err| TransactionKernelError::GetForeignAccountInputs { + foreign_account_id, + ref_block: self.ref_block, + source: err, + })?; + + let mut tx_advice_inputs = TransactionAdviceInputs::default(); + tx_advice_inputs + .add_foreign_accounts([&foreign_account_inputs]) + .map_err(|err| { + TransactionKernelError::other_with_source( + format!( + "failed to construct advice inputs for foreign account {}", + foreign_account_inputs.id() + ), + err, + ) + })?; + + self.base_host + .load_foreign_account_code(foreign_account_inputs.code()) + .map_err(|err| { + TransactionKernelError::other_with_source( + format!( + "failed to insert account procedures for foreign account {}", + foreign_account_inputs.id() + ), + err, + ) + })?; + + // Add the foreign account's code to the list of accessed code. + self.accessed_foreign_account_code.push(foreign_account_inputs.code().clone()); + + Ok(tx_advice_inputs.into_advice_mutations().collect()) + } + /// Pushes a signature to the advice stack as a response to the `AuthRequest` event. /// /// The signature is requested from the host's authenticator. @@ -333,12 +398,20 @@ where AccountDelta, InputNotes, Vec, + Vec, BTreeMap>, TransactionProgress, ) { let (account_delta, input_notes, output_notes, tx_progress) = self.base_host.into_parts(); - (account_delta, input_notes, output_notes, self.generated_signatures, tx_progress) + ( + account_delta, + input_notes, + output_notes, + self.accessed_foreign_account_code, + self.generated_signatures, + tx_progress, + ) } } @@ -399,6 +472,9 @@ where .on_before_tx_fee_removed_from_account(fee_asset) .await .map_err(EventError::from), + TransactionEventData::ForeignAccount { account_id } => { + self.on_foreign_account_requested(account_id).await.map_err(EventError::from) + }, TransactionEventData::AccountVaultAssetWitness { current_account_id, vault_root, diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index bb5c50d0c5..c06595eda7 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -280,7 +280,7 @@ where let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes) .map_err(TransactionExecutorError::InvalidTransactionInputs)?; - let (stack_inputs, advice_inputs) = + let (stack_inputs, tx_advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs, tx_args, init_advice_inputs) .map_err(TransactionExecutorError::ConflictingAdviceMapEntry)?; @@ -298,8 +298,10 @@ where input_notes.iter().map(|n| n.note().script()), ); - let acct_procedure_index_map = - AccountProcedureIndexMap::from_transaction_params(&tx_inputs, tx_args, &advice_inputs) + // To start executing the transaction, the procedure index map only needs to contain the + // native account's procedures. Foreign accounts are inserted into the map on first access. + let account_procedure_index_map = + AccountProcedureIndexMap::new([tx_inputs.account().code()]) .map_err(TransactionExecutorError::TransactionHostCreationFailed)?; let host = TransactionExecutorHost::new( @@ -307,12 +309,13 @@ where input_notes.clone(), self.data_store, script_mast_store, - acct_procedure_index_map, + account_procedure_index_map, self.authenticator, + tx_inputs.block_header().block_num(), self.source_manager.clone(), ); - let advice_inputs = advice_inputs.into_advice_inputs(); + let advice_inputs = tx_advice_inputs.into_advice_inputs(); Ok((host, tx_inputs, stack_inputs, advice_inputs)) } @@ -331,8 +334,14 @@ fn build_executed_transaction Result { // Note that the account delta does not contain the removed transaction fee, so it is the // "pre-fee" delta of the transaction. - let (pre_fee_account_delta, _input_notes, output_notes, generated_signatures, tx_progress) = - host.into_parts(); + let ( + pre_fee_account_delta, + _input_notes, + output_notes, + accessed_foreign_account_code, + generated_signatures, + tx_progress, + ) = host.into_parts(); let tx_outputs = TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes) @@ -380,6 +389,7 @@ fn build_executed_transaction { proc_root |-> proc_index } } for all known /// procedures of account interfaces for all accounts expected to be invoked during transaction /// execution. +#[derive(Debug, Clone, Default)] pub struct AccountProcedureIndexMap(BTreeMap>); impl AccountProcedureIndexMap { - /// Returns a new [AccountProcedureIndexMap] instantiated with account procedures present in - /// the provided advice provider. - /// - /// Note: `account_code_commitments` iterator should include both native account code and - /// foreign account codes commitments - pub fn new( - account_code_commitments: impl IntoIterator, - adv_provider: &AdviceInputs, + /// Returns a new [`AccountProcedureIndexMap`] instantiated with account procedures from the + /// provided iterator of [`AccountCode`]. + pub fn new<'code>( + account_codes: impl IntoIterator, ) -> Result { - let mut result = BTreeMap::new(); + let mut index_map = Self::default(); - for code_commitment in account_code_commitments { - let account_procs_map = build_account_procedure_map(code_commitment, adv_provider)?; - result.insert(code_commitment, account_procs_map); + for account_code in account_codes { + // Insert each account procedures only once. + if !index_map.0.contains_key(&account_code.commitment()) { + index_map.insert_code(account_code)?; + } } - Ok(Self(result)) + Ok(index_map) } - /// Builds an [`AccountProcedureIndexMap`] for the specified transaction inputs and arguments. + /// Inserts the procedures from the provided [`AccountCode`] into the advice inputs, using + /// [`AccountCode::commitment`] as the key. /// - /// The resulting instance will map all account code commmitments to a mapping of + /// The resulting instance will map the account code commitment to a mapping of /// `proc_root |-> proc_index` for any account that is expected to be involved in the - /// transaction, enabling easy procedure index lookups on runtime. - pub fn from_transaction_params( - tx_inputs: &TransactionInputs, - tx_args: &TransactionArgs, - tx_advice_inputs: &TransactionAdviceInputs, - ) -> Result { - let mut account_code_commitments = tx_args.to_foreign_account_code_commitments(); - account_code_commitments.insert(tx_inputs.account().code().commitment()); + /// transaction, enabling fast procedure index lookups at runtime. + pub fn insert_code(&mut self, code: &AccountCode) -> Result<(), TransactionHostError> { + let mut procedure_map = BTreeMap::new(); + for (proc_idx, proc_info) in code.procedures().iter().enumerate() { + let proc_idx = u8::try_from(proc_idx).map_err(|_| { + TransactionHostError::AccountProcedureIndexMapError( + "procedure index out of bounds".into(), + ) + })?; + + procedure_map.insert(*proc_info.mast_root(), proc_idx); + } + + self.0.insert(code.commitment(), procedure_map); - Self::new(account_code_commitments, tx_advice_inputs.as_advice_inputs()) + Ok(()) } /// Returns index of the procedure whose root is currently at the top of the operand stack in @@ -91,62 +92,3 @@ impl AccountProcedureIndexMap { .ok_or(TransactionKernelError::UnknownAccountProcedure(proc_root)) } } - -// HELPER FUNCTIONS -// ================================================================================================ - -fn build_account_procedure_map( - code_commitment: Word, - advice_inputs: &AdviceInputs, -) -> Result, TransactionHostError> { - // get the account procedures from the advice_map - let proc_data = advice_inputs.map.get(&code_commitment).ok_or_else(|| { - TransactionHostError::AccountProcedureIndexMapError( - "failed to read account procedure data from the advice provider".to_string(), - ) - })?; - - let mut account_procs_map = BTreeMap::new(); - - // sanity checks - - // check that there are procedures in the account code - if proc_data.is_empty() { - return Err(TransactionHostError::AccountProcedureIndexMapError( - "account code does not contain any procedures.".to_string(), - )); - } - - // check that procedure data have a correct length - if proc_data.len() % AccountProcedureInfo::NUM_ELEMENTS_PER_PROC != 0 { - return Err(TransactionHostError::AccountProcedureIndexMapError( - "account procedure data has invalid length.".to_string(), - )); - } - - // One procedure requires 8 values to represent - let num_procs = proc_data.len() / AccountProcedureInfo::NUM_ELEMENTS_PER_PROC; - - // check that the account code does not contain too many procedures - if num_procs > AccountCode::MAX_NUM_PROCEDURES { - return Err(TransactionHostError::AccountProcedureIndexMapError( - "account code contains too many procedures.".to_string(), - )); - } - - for (proc_idx, proc_info) in - proc_data.chunks_exact(AccountProcedureInfo::NUM_ELEMENTS_PER_PROC).enumerate() - { - let proc_info_array: [Felt; AccountProcedureInfo::NUM_ELEMENTS_PER_PROC] = - proc_info.try_into().expect("Failed conversion into procedure info array."); - - let procedure = AccountProcedureInfo::try_from(proc_info_array) - .map_err(TransactionHostError::AccountProcedureInfoCreationFailed)?; - - let proc_idx = u8::try_from(proc_idx).expect("Invalid procedure index."); - - account_procs_map.insert(*procedure.mast_root(), proc_idx); - } - - Ok(account_procs_map) -} diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 571e76c674..a78cb66526 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -29,6 +29,7 @@ use miden_lib::transaction::memory::{ }; use miden_lib::transaction::{TransactionEvent, TransactionEventError}; use miden_objects::account::{ + AccountCode, AccountDelta, AccountHeader, AccountId, @@ -62,7 +63,7 @@ use miden_processor::{ pub use tx_progress::TransactionProgress; use crate::auth::SigningInputs; -use crate::errors::TransactionKernelError; +use crate::errors::{TransactionHostError, TransactionKernelError}; // TRANSACTION BASE HOST // ================================================================================================ @@ -200,6 +201,17 @@ where ) } + // MUTATORS + // -------------------------------------------------------------------------------------------- + + /// Returns a mutable reference to the [`AccountProcedureIndexMap`]. + pub fn load_foreign_account_code( + &mut self, + account_code: &AccountCode, + ) -> Result<(), TransactionHostError> { + self.acct_procedure_index_map.insert_code(account_code) + } + // EVENT HANDLERS // -------------------------------------------------------------------------------------------- @@ -216,6 +228,10 @@ where } let advice_mutations = match transaction_event { + TransactionEvent::AccountBeforeForeignLoad => { + self.on_account_before_foreign_load(process) + } + TransactionEvent::AccountVaultBeforeAddAsset => { Self::on_account_vault_before_add_or_remove_asset(process) }, @@ -333,6 +349,28 @@ where Ok(advice_mutations) } + /// Extract all necessary data for requesting the data to access the foreign account that is + /// being loaded. + /// + /// Expected stack state: `[account_id_prefix, account_id_suffix]` + pub fn on_account_before_foreign_load( + &self, + process: &ProcessState, + ) -> Result { + let account_id_word = process.get_stack_word(0); + let account_id = + AccountId::try_from([account_id_word[3], account_id_word[2]]).map_err(|err| { + TransactionKernelError::other_with_source( + "failed to convert account ID word into account ID", + err, + ) + })?; + + Ok(TransactionEventHandling::Unhandled(TransactionEventData::ForeignAccount { + account_id, + })) + } + /// Pushes a signature to the advice stack as a response to the `AuthRequest` event. /// /// Expected stack state: `[MESSAGE, PUB_KEY]` @@ -989,6 +1027,11 @@ pub(super) enum TransactionEventData { /// The fee asset extracted from the stack. fee_asset: FungibleAsset, }, + /// The data necessary to request a foreign account's data from the data store. + ForeignAccount { + /// The foreign account's ID. + account_id: AccountId, + }, /// The data necessary to request an asset witness from the data store. AccountVaultAssetWitness { /// The account ID for whose vault a witness is requested. diff --git a/crates/miden-tx/src/host/script_mast_forest_store.rs b/crates/miden-tx/src/host/script_mast_forest_store.rs index c6949dd0a3..071f84208b 100644 --- a/crates/miden-tx/src/host/script_mast_forest_store.rs +++ b/crates/miden-tx/src/host/script_mast_forest_store.rs @@ -12,6 +12,7 @@ use miden_processor::MastForestStore; /// /// A [ScriptMastForestStore] is meant to exclusively store MAST forests related to both /// transaction and input note scripts. +#[derive(Debug, Clone, Default)] pub struct ScriptMastForestStore { mast_forests: BTreeMap>, advice_map: AdviceMap, diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index 7d5aae4bd3..7ae3283e51 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -120,15 +120,20 @@ impl LocalTransactionProver { &self, tx_witness: TransactionWitness, ) -> Result { - let TransactionWitness { tx_inputs, tx_args, advice_witness } = tx_witness; + let TransactionWitness { + tx_inputs, + tx_args, + foreign_account_code, + advice_witness, + } = tx_witness; let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_witness)) .map_err(TransactionProverError::ConflictingAdviceMapEntry)?; self.mast_store.load_account_code(tx_inputs.account().code()); - for account_inputs in tx_args.foreign_account_inputs() { - self.mast_store.load_account_code(account_inputs.code()); + for account_code in &foreign_account_code { + self.mast_store.load_account_code(account_code); } let script_mast_store = ScriptMastForestStore::new( @@ -136,9 +141,10 @@ impl LocalTransactionProver { tx_inputs.input_notes().iter().map(|n| n.note().script()), ); - let acct_procedure_index_map = - AccountProcedureIndexMap::from_transaction_params(&tx_inputs, &tx_args, &advice_inputs) - .map_err(TransactionProverError::TransactionHostCreationFailed)?; + let account_procedure_index_map = AccountProcedureIndexMap::new( + foreign_account_code.iter().chain([tx_inputs.account().code()]), + ) + .map_err(TransactionProverError::CreateAccountProcedureIndexMap)?; let (partial_account, _, ref_block, _, input_notes) = tx_inputs.into_parts(); let mut host = TransactionProverHost::new( @@ -146,7 +152,7 @@ impl LocalTransactionProver { input_notes, self.mast_store.as_ref(), script_mast_store, - acct_procedure_index_map, + account_procedure_index_map, ); let advice_inputs = advice_inputs.into_advice_inputs(); diff --git a/crates/miden-tx/src/prover/prover_host.rs b/crates/miden-tx/src/prover/prover_host.rs index 611ebc460f..6c460a828a 100644 --- a/crates/miden-tx/src/prover/prover_host.rs +++ b/crates/miden-tx/src/prover/prover_host.rs @@ -123,8 +123,9 @@ where TransactionEventData::AuthRequest { .. } => { Err(EventError::from("base host should have handled auth request event")) }, - // Witnesses should be in the advice provider at proving time, so there is - // nothing to do. + // Foreign account data and witnesses should be in the advice provider at + // proving time, so there is nothing to do. + TransactionEventData::ForeignAccount { .. } => Ok(Vec::new()), TransactionEventData::AccountVaultAssetWitness { .. } => Ok(Vec::new()), TransactionEventData::AccountStorageMapWitness { .. } => Ok(Vec::new()), // We don't track enough information to handle this event. Since this just From 4ebec54a027572013da07bde30f07b8c1bbf2d8f Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 16 Sep 2025 08:25:08 +0200 Subject: [PATCH 039/133] feat: Move account seed into `Account` and `PartialAccount` (#1875) * feat: Include account seed in `PartialAccount` * feat: Ensure seed validity in `PartialAccount` * chore: Remove seed validation from tx inputs * chore: Document partial account constructors and new fields * chore: `block_chain` -> `blockchain` * chore: add changelog * feat: Add account seed to `Account` * feat: Validate `Account` seed correctness * chore: Rename `AccountCredentials` to `AccountAuthenticator` * feat: Only remove seed if delta was non-empty * chore: Clarify seed return value * feat: Test account ID seed validation * chore: rustfmt * fix: error message for account error --- CHANGELOG.md | 1 + bin/bench-prover/src/bench_functions.rs | 2 +- crates/miden-lib/src/account/auth/no_auth.rs | 2 +- .../src/account/auth/rpo_falcon_512_acl.rs | 2 +- .../account/auth/rpo_falcon_512_multisig.rs | 4 +- crates/miden-lib/src/account/faucets/mod.rs | 8 +- crates/miden-lib/src/account/wallets/mod.rs | 9 +- crates/miden-lib/src/testing/mock_account.rs | 12 +- crates/miden-lib/src/transaction/inputs.rs | 2 +- .../miden-objects/src/account/builder/mod.rs | 15 +- crates/miden-objects/src/account/delta/mod.rs | 9 +- crates/miden-objects/src/account/file.rs | 4 +- crates/miden-objects/src/account/mod.rs | 178 +++++++++++++++++- crates/miden-objects/src/account/partial.rs | 108 +++++++---- crates/miden-objects/src/errors.rs | 18 +- crates/miden-objects/src/testing/account.rs | 25 ++- .../src/testing/add_component.rs | 31 +++ crates/miden-objects/src/testing/mod.rs | 1 + .../src/transaction/inputs/account.rs | 6 +- .../src/transaction/inputs/mod.rs | 91 ++------- .../src/transaction/tx_witness.rs | 3 +- .../kernel_tests/block/proven_block_error.rs | 11 +- .../src/kernel_tests/tx/test_account.rs | 24 +-- .../src/kernel_tests/tx/test_fpi.rs | 3 +- .../src/kernel_tests/tx/test_note.rs | 6 +- .../src/kernel_tests/tx/test_prologue.rs | 58 +++--- .../src/kernel_tests/tx/test_tx.rs | 2 +- crates/miden-testing/src/mock_chain/chain.rs | 85 ++++----- .../src/mock_chain/chain_builder.rs | 49 +++-- .../miden-testing/src/tx_context/builder.rs | 27 +-- .../miden-testing/src/tx_context/context.rs | 9 +- crates/miden-testing/tests/scripts/p2id.rs | 4 +- crates/miden-testing/tests/scripts/p2ide.rs | 12 +- crates/miden-testing/tests/wallet/mod.rs | 3 +- crates/miden-tx/src/executor/data_store.rs | 4 +- crates/miden-tx/src/executor/mod.rs | 4 +- crates/miden-tx/src/prover/mod.rs | 9 +- 37 files changed, 493 insertions(+), 348 deletions(-) create mode 100644 crates/miden-objects/src/testing/add_component.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 41e8e67904..86b69d7d8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Added lazy loading of the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). - Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). - Added Serialize and Deserialize Traits on `SigningInputs` ([#1858](https://github.com/0xMiden/miden-base/pull/1858)) +- [BREAKING] Move account seed into `PartialAccount` ([#1875](https://github.com/0xMiden/miden-base/pull/1875)). ### Changes diff --git a/bin/bench-prover/src/bench_functions.rs b/bin/bench-prover/src/bench_functions.rs index 4c547849a3..ca6f999106 100644 --- a/bin/bench-prover/src/bench_functions.rs +++ b/bin/bench-prover/src/bench_functions.rs @@ -39,7 +39,7 @@ pub fn setup_consume_note_with_new_account() -> Result { .execute_blocking()?; // Apply delta to the target account to verify it is no longer new - let target_account_after: Account = Account::from_parts( + let target_account_after: Account = Account::new_existing( target_account.id(), AssetVault::new(&[fungible_asset]).unwrap(), target_account.storage().clone(), diff --git a/crates/miden-lib/src/account/auth/no_auth.rs b/crates/miden-lib/src/account/auth/no_auth.rs index 8838060884..21d9d2fb7e 100644 --- a/crates/miden-lib/src/account/auth/no_auth.rs +++ b/crates/miden-lib/src/account/auth/no_auth.rs @@ -49,7 +49,7 @@ mod tests { #[test] fn test_no_auth_component() { // Create an account using the NoAuth component - let (_account, _) = AccountBuilder::new([0; 32]) + let _account = AccountBuilder::new([0; 32]) .with_auth_component(NoAuth) .with_component(BasicWallet) .build() diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs index 77eeeac10a..14a9482a13 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs @@ -224,7 +224,7 @@ mod tests { let component = AuthRpoFalcon512Acl::new(public_key, acl_config).expect("component creation failed"); - let (account, _) = AccountBuilder::new([0; 32]) + let account = AccountBuilder::new([0; 32]) .with_auth_component(component) .with_component(BasicWallet) .build() diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs index ebba574882..36976359be 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs @@ -101,7 +101,7 @@ mod tests { .expect("multisig component creation failed"); // Build account with multisig component - let (account, _) = AccountBuilder::new([0; 32]) + let account = AccountBuilder::new([0; 32]) .with_auth_component(multisig_component) .with_component(BasicWallet) .build() @@ -131,7 +131,7 @@ mod tests { let multisig_component = AuthRpoFalcon512Multisig::new(threshold, approvers.clone()) .expect("multisig component creation failed"); - let (account, _) = AccountBuilder::new([0; 32]) + let account = AccountBuilder::new([0; 32]) .with_auth_component(multisig_component) .with_component(BasicWallet) .build() diff --git a/crates/miden-lib/src/account/faucets/mod.rs b/crates/miden-lib/src/account/faucets/mod.rs index 3c22447412..d5ae247446 100644 --- a/crates/miden-lib/src/account/faucets/mod.rs +++ b/crates/miden-lib/src/account/faucets/mod.rs @@ -281,7 +281,7 @@ pub fn create_basic_fungible_faucet( max_supply: Felt, account_storage_mode: AccountStorageMode, auth_scheme: AuthScheme, -) -> Result<(Account, Word), FungibleFaucetError> { +) -> Result { let distribute_proc_root = BasicFungibleFaucet::distribute_digest(); let auth_component: AccountComponent = match auth_scheme { @@ -311,7 +311,7 @@ pub fn create_basic_fungible_faucet( }, }; - let (account, account_seed) = AccountBuilder::new(init_seed) + let account = AccountBuilder::new(init_seed) .account_type(AccountType::FungibleFaucet) .storage_mode(account_storage_mode) .with_auth_component(auth_component) @@ -319,7 +319,7 @@ pub fn create_basic_fungible_faucet( .build() .map_err(FungibleFaucetError::AccountError)?; - Ok((account, account_seed)) + Ok(account) } // FUNGIBLE FAUCET ERROR @@ -388,7 +388,7 @@ mod tests { let decimals = 2u8; let storage_mode = AccountStorageMode::Private; - let (faucet_account, _) = create_basic_fungible_faucet( + let faucet_account = create_basic_fungible_faucet( init_seed, token_symbol, decimals, diff --git a/crates/miden-lib/src/account/wallets/mod.rs b/crates/miden-lib/src/account/wallets/mod.rs index 1cdb367208..087b235eb4 100644 --- a/crates/miden-lib/src/account/wallets/mod.rs +++ b/crates/miden-lib/src/account/wallets/mod.rs @@ -116,7 +116,7 @@ pub fn create_basic_wallet( auth_scheme: AuthScheme, account_type: AccountType, account_storage_mode: AccountStorageMode, -) -> Result<(Account, Word), BasicWalletError> { +) -> Result { if matches!(account_type, AccountType::FungibleFaucet | AccountType::NonFungibleFaucet) { return Err(BasicWalletError::AccountError(AccountError::other( "basic wallet accounts cannot have a faucet account type", @@ -142,7 +142,7 @@ pub fn create_basic_wallet( }, }; - let (account, account_seed) = AccountBuilder::new(init_seed) + let account = AccountBuilder::new(init_seed) .account_type(account_type) .storage_mode(account_storage_mode) .with_auth_component(auth_component) @@ -150,7 +150,7 @@ pub fn create_basic_wallet( .build() .map_err(BasicWalletError::AccountError)?; - Ok((account, account_seed)) + Ok(account) } // TESTS @@ -190,8 +190,7 @@ mod tests { AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, ) - .unwrap() - .0; + .unwrap(); let bytes = wallet.to_bytes(); let deserialized_wallet = Account::read_from_bytes(&bytes).unwrap(); diff --git a/crates/miden-lib/src/testing/mock_account.rs b/crates/miden-lib/src/testing/mock_account.rs index 01bf53baa6..54564bc29e 100644 --- a/crates/miden-lib/src/testing/mock_account.rs +++ b/crates/miden-lib/src/testing/mock_account.rs @@ -31,9 +31,9 @@ pub trait MockAccountExt { .with_assets(AssetVault::mock().assets()) .build_existing() .expect("account should be valid"); - let (_id, vault, storage, code, nonce) = account.into_parts(); + let (_id, vault, storage, code, nonce, _seed) = account.into_parts(); - Account::from_parts(account_id, vault, storage, code, nonce) + Account::new_existing(account_id, vault, storage, code, nonce) } /// Creates a mock account with fungible faucet storage and the given account ID. @@ -47,12 +47,12 @@ pub trait MockAccountExt { .with_component(MockFaucetComponent) .build_existing() .expect("account should be valid"); - let (_id, vault, mut storage, code, nonce) = account.into_parts(); + let (_id, vault, mut storage, code, nonce, _seed) = account.into_parts(); let faucet_data_slot = Word::from([ZERO, ZERO, ZERO, initial_balance]); storage.set_item(FAUCET_STORAGE_DATA_SLOT, faucet_data_slot).unwrap(); - Account::from_parts(account_id, vault, storage, code, nonce) + Account::new_existing(account_id, vault, storage, code, nonce) } /// Creates a mock account with non-fungible faucet storage and the given account ID. @@ -66,7 +66,7 @@ pub trait MockAccountExt { .with_component(MockFaucetComponent) .build_existing() .expect("account should be valid"); - let (_id, vault, _storage, code, nonce) = account.into_parts(); + let (_id, vault, _storage, code, nonce, _seed) = account.into_parts(); let asset = NonFungibleAsset::mock(&constants::NON_FUNGIBLE_ASSET_DATA_2); let non_fungible_storage_map = @@ -74,7 +74,7 @@ pub trait MockAccountExt { let storage = AccountStorage::new(vec![StorageSlot::Map(non_fungible_storage_map)]).unwrap(); - Account::from_parts(account_id, vault, storage, code, nonce) + Account::new_existing(account_id, vault, storage, code, nonce) } } diff --git a/crates/miden-lib/src/transaction/inputs.rs b/crates/miden-lib/src/transaction/inputs.rs index 7a24eecf5f..1a30c3d15e 100644 --- a/crates/miden-lib/src/transaction/inputs.rs +++ b/crates/miden-lib/src/transaction/inputs.rs @@ -62,7 +62,7 @@ impl TransactionAdviceInputs { inputs.add_account(partial_native_acc)?; // if a seed was provided, extend the map appropriately - if let Some(seed) = tx_inputs.account_seed() { + if let Some(seed) = tx_inputs.account().seed() { // ACCOUNT_ID |-> ACCOUNT_SEED let account_id_key = Self::account_id_map_key(partial_native_acc.id()); inputs.add_map_entry(account_id_key, seed.to_vec()); diff --git a/crates/miden-objects/src/account/builder/mod.rs b/crates/miden-objects/src/account/builder/mod.rs index 2da160a424..eb285576c7 100644 --- a/crates/miden-objects/src/account/builder/mod.rs +++ b/crates/miden-objects/src/account/builder/mod.rs @@ -194,7 +194,7 @@ impl AccountBuilder { /// - [`MastForest::merge`](miden_processor::MastForest::merge) fails on the given components. /// - If duplicate assets were added to the builder (only under the `testing` feature). /// - If the vault is not empty on new accounts (only under the `testing` feature). - pub fn build(mut self) -> Result<(Account, Word), AccountError> { + pub fn build(mut self) -> Result { let (vault, code, storage) = self.build_inner()?; #[cfg(any(feature = "testing", test))] @@ -223,9 +223,12 @@ impl AccountBuilder { debug_assert_eq!(account_id.account_type(), self.account_type); debug_assert_eq!(account_id.storage_mode(), self.storage_mode); - let account = Account::from_parts(account_id, vault, storage, code, Felt::ZERO); + // SAFETY: The account ID was derived from the seed and the seed is provided, so it is safe + // to bypass the checks of `Account::new`. + let account = + Account::new_unchecked(account_id, vault, storage, code, Felt::ZERO, Some(seed)); - Ok((account, seed)) + Ok(account) } } @@ -271,7 +274,7 @@ impl AccountBuilder { // Use the nonce value set by the Self::nonce method or Felt::ONE as a default. let nonce = self.nonce.unwrap_or(Felt::ONE); - Ok(Account::from_parts(account_id, vault, storage, code, nonce)) + Ok(Account::new_existing(account_id, vault, storage, code, nonce)) } } @@ -352,7 +355,7 @@ mod tests { let storage_slot1 = 12; let storage_slot2 = 42; - let (account, seed) = Account::builder([5; 32]) + let account = Account::builder([5; 32]) .with_auth_component(NoopAuthComponent) .with_component(CustomComponent1 { slot0: storage_slot0 }) .with_component(CustomComponent2 { @@ -366,7 +369,7 @@ mod tests { assert_eq!(account.nonce(), Felt::ZERO); let computed_id = AccountId::new( - seed, + account.seed().unwrap(), AccountIdVersion::Version0, account.code.commitment(), account.storage.commitment(), diff --git a/crates/miden-objects/src/account/delta/mod.rs b/crates/miden-objects/src/account/delta/mod.rs index e33600735f..9a4088f39c 100644 --- a/crates/miden-objects/src/account/delta/mod.rs +++ b/crates/miden-objects/src/account/delta/mod.rs @@ -616,8 +616,13 @@ mod tests { let account_code = AccountCode::mock(); assert_eq!(account_code.to_bytes().len(), account_code.get_size_hint()); - let account = - Account::from_parts(account_id, asset_vault, account_storage, account_code, Felt::ZERO); + let account = Account::new_existing( + account_id, + asset_vault, + account_storage, + account_code, + Felt::ONE, + ); assert_eq!(account.to_bytes().len(), account.get_size_hint()); // AccountUpdateDetails diff --git a/crates/miden-objects/src/account/file.rs b/crates/miden-objects/src/account/file.rs index 2c23b0f242..532d2c4508 100644 --- a/crates/miden-objects/src/account/file.rs +++ b/crates/miden-objects/src/account/file.rs @@ -131,8 +131,8 @@ mod tests { // create account and auth let vault = AssetVault::new(&[]).unwrap(); let storage = AccountStorage::new(vec![]).unwrap(); - let nonce = Felt::new(0); - let account = Account::from_parts(id, vault, storage, code, nonce); + let nonce = Felt::new(1); + let account = Account::new_existing(id, vault, storage, code, nonce); let account_seed = Some(Word::empty()); let auth_secret_key = AuthSecretKey::RpoFalcon512(SecretKey::new()); let auth_secret_key_2 = AuthSecretKey::RpoFalcon512(SecretKey::new()); diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index 0c9035f846..dffd8e1491 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -1,3 +1,5 @@ +use alloc::string::ToString; + use crate::asset::AssetVault; use crate::utils::serde::{ ByteReader, @@ -108,21 +110,50 @@ pub struct Account { storage: AccountStorage, code: AccountCode, nonce: Felt, + seed: Option, } impl Account { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns an [Account] instantiated with the provided components. - pub fn from_parts( + /// Returns an [`Account`] instantiated with the provided components. + /// + /// # Errors + /// + /// Returns an error if: + /// - an account seed is provided but the account's nonce indicates the account already exists. + /// - an account seed is not provided but the account's nonce indicates the account is new. + /// - an account seed is provided but the account ID derived from it is invalid or does not + /// match the provided account's ID. + pub fn new( + id: AccountId, + vault: AssetVault, + storage: AccountStorage, + code: AccountCode, + nonce: Felt, + seed: Option, + ) -> Result { + validate_account_seed(id, code.commitment(), storage.commitment(), seed, nonce)?; + + Ok(Self::new_unchecked(id, vault, storage, code, nonce, seed)) + } + + /// Returns an [`Account`] instantiated with the provided components. + /// + /// # Warning + /// + /// This does not check that the provided seed is valid with respect to the provided components. + /// Prefer using [`Account::new`] whenever possible. + pub fn new_unchecked( id: AccountId, vault: AssetVault, storage: AccountStorage, code: AccountCode, nonce: Felt, + seed: Option, ) -> Self { - Self { id, vault, storage, code, nonce } + Self { id, vault, storage, code, nonce, seed } } /// Creates an account's [`AccountCode`] and [`AccountStorage`] from the provided components. @@ -251,6 +282,13 @@ impl Account { self.nonce } + /// Returns the seed of the account's ID if the account is new. + /// + /// That is, if [`Account::is_new`] returns `true`, the seed will be `Some`. + pub fn seed(&self) -> Option { + self.seed + } + /// Returns true if this account can issue assets. pub fn is_faucet(&self) -> bool { self.id.is_faucet() @@ -282,14 +320,19 @@ impl Account { self.id().is_network() } - /// Returns true if the account is new (i.e. it has not been initialized yet). + /// Returns `true` if the account is new, `false` otherwise. + /// + /// An account is considered new if the account's nonce is zero and it hasn't been registered on + /// chain yet. pub fn is_new(&self) -> bool { self.nonce == ZERO } /// Decomposes the account into the underlying account components. - pub fn into_parts(self) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt) { - (self.id, self.vault, self.storage, self.code, self.nonce) + pub fn into_parts( + self, + ) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt, Option) { + (self.id, self.vault, self.storage, self.code, self.nonce, self.seed) } // DATA MUTATORS @@ -317,6 +360,14 @@ impl Account { // update nonce self.increment_nonce(delta.nonce_delta())?; + // Maintain internal consistency of the account, i.e. the seed should not be present for + // existing accounts, where existing accounts are defined as having a nonce > 0. + // If we've incremented the nonce, then we should remove the seed (if it was present at + // all). + if delta.nonce_delta().as_int() > 0 { + self.seed = None; + } + Ok(()) } @@ -363,13 +414,14 @@ impl Account { impl Serializable for Account { fn write_into(&self, target: &mut W) { - let Account { id, vault, storage, code, nonce } = self; + let Account { id, vault, storage, code, nonce, seed } = self; id.write_into(target); vault.write_into(target); storage.write_into(target); code.write_into(target); nonce.write_into(target); + seed.write_into(target); } fn get_size_hint(&self) -> usize { @@ -378,6 +430,7 @@ impl Serializable for Account { + self.storage.get_size_hint() + self.code.get_size_hint() + self.nonce.get_size_hint() + + self.seed.get_size_hint() } } @@ -388,8 +441,10 @@ impl Deserializable for Account { let storage = AccountStorage::read_from(source)?; let code = AccountCode::read_from(source)?; let nonce = Felt::read_from(source)?; + let seed = >::read_from(source)?; - Ok(Self::from_parts(id, vault, storage, code, nonce)) + Self::new(id, vault, storage, code, nonce, seed) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } @@ -418,6 +473,40 @@ pub fn hash_account( Hasher::hash_elements(&elements) } +// HELPER FUNCTIONS +// ================================================================================================ + +/// Validates that the provided seed is valid for the provided account components. +pub(super) fn validate_account_seed( + id: AccountId, + code_commitment: Word, + storage_commitment: Word, + seed: Option, + nonce: Felt, +) -> Result<(), AccountError> { + let account_is_new = nonce == ZERO; + + match (account_is_new, seed) { + (true, Some(seed)) => { + let account_id = + AccountId::new(seed, id.version(), code_commitment, storage_commitment) + .map_err(AccountError::SeedConvertsToInvalidAccountId)?; + + if account_id != id { + return Err(AccountError::AccountIdSeedMismatch { + expected: id, + actual: account_id, + }); + } + + Ok(()) + }, + (true, None) => Err(AccountError::NewAccountMissingSeed), + (false, Some(_)) => Err(AccountError::ExistingAccountWithSeed), + (false, None) => Ok(()), + } +} + /// Validates that all `components` support the given `account_type`. fn validate_components_support_account_type( components: &[AccountComponent], @@ -444,6 +533,7 @@ mod tests { use assert_matches::assert_matches; use miden_assembly::Assembler; + use miden_core::FieldElement; use miden_crypto::utils::{Deserializable, Serializable}; use miden_crypto::{Felt, Word}; @@ -456,9 +546,12 @@ mod tests { AccountVaultDelta, }; use crate::AccountError; + use crate::account::AccountStorageMode::Network; use crate::account::{ Account, + AccountBuilder, AccountComponent, + AccountIdVersion, AccountType, StorageMap, StorageMapDelta, @@ -469,6 +562,7 @@ mod tests { ACCOUNT_ID_PRIVATE_SENDER, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, }; + use crate::testing::add_component::AddComponent; use crate::testing::noop_auth_component::NoopAuthComponent; use crate::testing::storage::AccountStorageDeltaBuilder; @@ -676,7 +770,7 @@ mod tests { let storage = AccountStorage::new(slots).unwrap(); - Account::from_parts(id, vault, storage, code, nonce) + Account::new_existing(id, vault, storage, code, nonce) } /// Tests that initializing code and storage from a component which does not support the given @@ -729,4 +823,70 @@ mod tests { assert_matches!(err, AccountError::AccountComponentDuplicateProcedureRoot(_)) } + + /// Tests all cases of account ID seed validation. + #[test] + fn seed_validation() -> anyhow::Result<()> { + let account = AccountBuilder::new([5; 32]) + .with_auth_component(NoopAuthComponent) + .with_component(AddComponent) + .build()?; + let (id, vault, storage, code, _nonce, seed) = account.into_parts(); + assert!(seed.is_some()); + + let other_seed = AccountId::compute_account_seed( + [9; 32], + AccountType::FungibleFaucet, + Network, + AccountIdVersion::Version0, + code.commitment(), + storage.commitment(), + )?; + + // Set nonce to 1 so the account is considered existing and provide the seed. + let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, seed) + .unwrap_err(); + assert_matches!(err, AccountError::ExistingAccountWithSeed); + + // Set nonce to 0 so the account is considered new but don't provide the seed. + let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, None) + .unwrap_err(); + assert_matches!(err, AccountError::NewAccountMissingSeed); + + // Set nonce to 0 so the account is considered new and provide a valid seed that results in + // a different ID than the provided one. + let err = Account::new( + id, + vault.clone(), + storage.clone(), + code.clone(), + Felt::ZERO, + Some(other_seed), + ) + .unwrap_err(); + assert_matches!(err, AccountError::AccountIdSeedMismatch { .. }); + + // Set nonce to 0 so the account is considered new and provide a seed that results in an + // invalid ID. + let err = Account::new( + id, + vault.clone(), + storage.clone(), + code.clone(), + Felt::ZERO, + Some(Word::from([1, 2, 3, 4u32])), + ) + .unwrap_err(); + assert_matches!(err, AccountError::SeedConvertsToInvalidAccountId(_)); + + // Set nonce to 1 so the account is considered existing and don't provide the seed, which + // should be valid. + Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, None)?; + + // Set nonce to 0 so the account is considered new and provide the original seed, which + // should be valid. + Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, seed)?; + + Ok(()) + } } diff --git a/crates/miden-objects/src/account/partial.rs b/crates/miden-objects/src/account/partial.rs index 0f77155d9b..caa498d02c 100644 --- a/crates/miden-objects/src/account/partial.rs +++ b/crates/miden-objects/src/account/partial.rs @@ -1,10 +1,13 @@ +use alloc::string::ToString; + use miden_core::utils::{Deserializable, Serializable}; use miden_core::{Felt, ZERO}; use super::{Account, AccountCode, AccountId, PartialStorage}; -use crate::Word; -use crate::account::hash_account; +use crate::account::{hash_account, validate_account_seed}; use crate::asset::PartialVault; +use crate::utils::serde::DeserializationError; +use crate::{AccountError, Word}; /// A partial representation of an account. /// @@ -17,36 +20,58 @@ use crate::asset::PartialVault; pub struct PartialAccount { /// The ID for the partial account id: AccountId, - /// The current transaction nonce of the account - nonce: Felt, - /// Account code - account_code: AccountCode, - /// Partial representation of the account's storage, containing the storage commitment and - /// proofs for specific storage slots that need to be accessed - partial_storage: PartialStorage, /// Partial representation of the account's vault, containing the vault root and necessary /// proof information for asset verification partial_vault: PartialVault, + /// Partial representation of the account's storage, containing the storage commitment and + /// proofs for specific storage slots that need to be accessed + partial_storage: PartialStorage, + /// Account code + code: AccountCode, + /// The current transaction nonce of the account + nonce: Felt, + /// The seed of the account ID, if any. + seed: Option, } impl PartialAccount { - /// Creates a new instance of a partial account with the specified components. + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`PartialAccount`] with the provided account parts and seed. + /// + /// # Errors + /// + /// Returns an error if: + /// - an account seed is provided but the account's nonce indicates the account already exists. + /// - an account seed is not provided but the account's nonce indicates the account is new. + /// - an account seed is provided but the account ID derived from it is invalid or does not + /// match the provided ID. pub fn new( id: AccountId, nonce: Felt, - account_code: AccountCode, + code: AccountCode, partial_storage: PartialStorage, partial_vault: PartialVault, - ) -> Self { - Self { + seed: Option, + ) -> Result { + validate_account_seed(id, code.commitment(), partial_storage.commitment(), seed, nonce)?; + + let account = Self { id, nonce, - account_code, + code, partial_storage, partial_vault, - } + seed, + }; + + Ok(account) } + // ACCESSORS + // -------------------------------------------------------------------------------------------- + /// Returns the account's unique identifier. pub fn id(&self) -> AccountId { self.id @@ -59,7 +84,7 @@ impl PartialAccount { /// Returns a reference to the account code. pub fn code(&self) -> &AccountCode { - &self.account_code + &self.code } /// Returns a reference to the partial storage representation of the account. @@ -72,8 +97,17 @@ impl PartialAccount { &self.partial_vault } - /// Returns true if the account is new (i.e. its nonce is zero and it hasn't been registered on - /// chain yet). + /// Returns the seed of the account's ID if the account is new. + /// + /// That is, if [`PartialAccount::is_new`] returns `true`, the seed will be `Some`. + pub fn seed(&self) -> Option { + self.seed + } + + /// Returns `true` if the account is new, `false` otherwise. + /// + /// An account is considered new if the account's nonce is zero and it hasn't been registered on + /// chain yet. pub fn is_new(&self) -> bool { self.nonce == ZERO } @@ -118,26 +152,33 @@ impl PartialAccount { } /// Consumes self and returns the underlying parts of the partial account. - pub fn into_parts(self) -> (AccountId, Felt, AccountCode, PartialStorage, PartialVault) { - (self.id, self.nonce, self.account_code, self.partial_storage, self.partial_vault) - } -} - -impl From for PartialAccount { - fn from(account: Account) -> Self { - PartialAccount::from(&account) + pub fn into_parts( + self, + ) -> (AccountId, PartialVault, PartialStorage, AccountCode, Felt, Option) { + ( + self.id, + self.partial_vault, + self.partial_storage, + self.code, + self.nonce, + self.seed, + ) } } impl From<&Account> for PartialAccount { + /// Constructs a [`PartialAccount`] from the provided account with an empty seed, assuming it is + /// an existing account. fn from(account: &Account) -> Self { - PartialAccount::new( + Self::new( account.id(), account.nonce(), account.code().clone(), account.storage().into(), account.vault().into(), + account.seed(), ) + .expect("account should ensure that seed is valid for account") } } @@ -145,9 +186,10 @@ impl Serializable for PartialAccount { fn write_into(&self, target: &mut W) { target.write(self.id); target.write(self.nonce); - target.write(&self.account_code); + target.write(&self.code); target.write(&self.partial_storage); target.write(&self.partial_vault); + target.write(self.seed); } } @@ -160,13 +202,9 @@ impl Deserializable for PartialAccount { let account_code = source.read()?; let partial_storage = source.read()?; let partial_vault = source.read()?; + let seed: Option = source.read()?; - Ok(PartialAccount { - id: account_id, - nonce, - account_code, - partial_storage, - partial_vault, - }) + PartialAccount::new(account_id, nonce, account_code, partial_storage, partial_vault, seed) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index cd7a9512d2..0d2c90c145 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -128,6 +128,14 @@ pub enum AccountError { "digest of the seed has {actual} trailing zeroes but must have at least {expected} trailing zeroes" )] SeedDigestTooFewTrailingZeros { expected: u32, actual: u32 }, + #[error("account ID {actual} computed from seed does not match ID {expected} on account")] + AccountIdSeedMismatch { actual: AccountId, expected: AccountId }, + #[error("account ID seed was provided for an existing account")] + ExistingAccountWithSeed, + #[error("account ID seed was not provided for a new account")] + NewAccountMissingSeed, + #[error("seed converts to an invalid account ID")] + SeedConvertsToInvalidAccountId(#[source] AccountIdError), #[error("storage map root {0} not found in the account storage")] StorageMapRootNotFound(Word), #[error("storage slot at index {0} is not of type map")] @@ -578,16 +586,8 @@ pub enum TransactionScriptError { #[derive(Debug, Error)] pub enum TransactionInputError { - #[error("account seed must be provided for new accounts")] - AccountSeedNotProvidedForNewAccount, - #[error("account seed must not be provided for existing accounts")] - AccountSeedProvidedForExistingAccount, #[error("transaction input note with nullifier {0} is a duplicate")] DuplicateInputNote(Nullifier), - #[error( - "ID {expected} of the new account does not match the ID {actual} computed from the provided seed" - )] - InconsistentAccountSeed { expected: AccountId, actual: AccountId }, #[error("partial blockchain has length {actual} which does not match block number {expected}")] InconsistentChainLength { expected: BlockNumber, @@ -601,8 +601,6 @@ pub enum TransactionInputError { InputNoteBlockNotInPartialBlockchain(NoteId), #[error("input note with id {0} was not created in block {1}")] InputNoteNotInBlock(NoteId, BlockNumber), - #[error("account ID computed from seed is invalid")] - InvalidAccountIdSeed(#[source] AccountIdError), #[error( "total number of input notes is {0} which exceeds the maximum of {MAX_INPUT_NOTES_PER_TX}" )] diff --git a/crates/miden-objects/src/testing/account.rs b/crates/miden-objects/src/testing/account.rs index 7098880f57..036ce0513f 100644 --- a/crates/miden-objects/src/testing/account.rs +++ b/crates/miden-objects/src/testing/account.rs @@ -1,5 +1,6 @@ use super::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}; -use crate::account::AccountId; +use crate::Felt; +use crate::account::{Account, AccountCode, AccountId, AccountStorage}; use crate::asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset}; use crate::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, @@ -7,6 +8,28 @@ use crate::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, }; +impl Account { + /// Returns an [`Account`] instantiated with the provided components. + /// + /// This is a thin wrapper around [`Account::new`] that assumes the provided components are for + /// an existing account. See that method's docs for details on when this function panics. + /// + /// # Panics + /// + /// Panics if: + /// - the provided components are not for an existing account. + pub fn new_existing( + id: AccountId, + vault: AssetVault, + storage: AccountStorage, + code: AccountCode, + nonce: Felt, + ) -> Self { + Self::new(id, vault, storage, code, nonce, None) + .expect("account seed is invalid for provided account") + } +} + impl AssetVault { /// Creates an [AssetVault] with 4 default assets. /// diff --git a/crates/miden-objects/src/testing/add_component.rs b/crates/miden-objects/src/testing/add_component.rs new file mode 100644 index 0000000000..eb4b79d79f --- /dev/null +++ b/crates/miden-objects/src/testing/add_component.rs @@ -0,0 +1,31 @@ +use crate::account::AccountComponent; +use crate::assembly::{Assembler, Library}; +use crate::utils::sync::LazyLock; + +// ADD COMPONENT +// ================================================================================================ + +const ADD_CODE: &str = " + export.add5 + add.5 + end +"; + +static ADD_LIBRARY: LazyLock = LazyLock::new(|| { + Assembler::default() + .assemble_library([ADD_CODE]) + .expect("add code should be valid") +}); + +/// Creates a mock authentication [`AccountComponent`] for testing purposes. +/// +/// The component defines an `add5` procedure that adds 5 to its input. +pub struct AddComponent; + +impl From for AccountComponent { + fn from(_: AddComponent) -> Self { + AccountComponent::new(ADD_LIBRARY.clone(), vec![]) + .expect("component should be valid") + .with_supports_all_types() + } +} diff --git a/crates/miden-objects/src/testing/mod.rs b/crates/miden-objects/src/testing/mod.rs index 29a8192f92..7bf6c65286 100644 --- a/crates/miden-objects/src/testing/mod.rs +++ b/crates/miden-objects/src/testing/mod.rs @@ -1,6 +1,7 @@ pub mod account; pub mod account_code; pub mod account_id; +pub mod add_component; pub mod asset; pub mod block; pub mod block_note_tree; diff --git a/crates/miden-objects/src/transaction/inputs/account.rs b/crates/miden-objects/src/transaction/inputs/account.rs index 9389ff4d66..f06cdd3ab4 100644 --- a/crates/miden-objects/src/transaction/inputs/account.rs +++ b/crates/miden-objects/src/transaction/inputs/account.rs @@ -101,7 +101,7 @@ mod tests { use miden_crypto::merkle::MerklePath; use miden_processor::SMT_DEPTH; - use crate::account::{Account, AccountCode, AccountId, AccountStorage}; + use crate::account::{Account, AccountCode, AccountId, AccountStorage, PartialAccount}; use crate::asset::AssetVault; use crate::block::AccountWitness; use crate::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE; @@ -113,7 +113,7 @@ mod tests { let code = AccountCode::mock(); let vault = AssetVault::new(&[]).unwrap(); let storage = AccountStorage::new(vec![]).unwrap(); - let account = Account::from_parts(id, vault, storage, code, Felt::new(10)); + let account = Account::new_existing(id, vault, storage, code, Felt::new(10)); let commitment = account.commitment(); @@ -124,7 +124,7 @@ mod tests { let merkle_path = MerklePath::new(merkle_nodes); let fpi_inputs = AccountInputs::new( - account.into(), + PartialAccount::from(&account), AccountWitness::new(id, commitment, merkle_path).unwrap(), ); diff --git a/crates/miden-objects/src/transaction/inputs/mod.rs b/crates/miden-objects/src/transaction/inputs/mod.rs index 170681826c..baf44af7f5 100644 --- a/crates/miden-objects/src/transaction/inputs/mod.rs +++ b/crates/miden-objects/src/transaction/inputs/mod.rs @@ -1,7 +1,8 @@ use core::fmt::Debug; use super::PartialBlockchain; -use crate::account::{AccountId, PartialAccount}; +use crate::TransactionInputError; +use crate::account::PartialAccount; use crate::block::BlockHeader; use crate::note::{Note, NoteInclusionProof}; use crate::utils::serde::{ @@ -11,7 +12,6 @@ use crate::utils::serde::{ DeserializationError, Serializable, }; -use crate::{TransactionInputError, Word}; mod account; pub use account::AccountInputs; @@ -26,33 +26,29 @@ pub use notes::{InputNote, InputNotes, ToInputNoteCommitments}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct TransactionInputs { account: PartialAccount, - account_seed: Option, block_header: BlockHeader, - block_chain: PartialBlockchain, + blockchain: PartialBlockchain, input_notes: InputNotes, } impl TransactionInputs { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - /// Returns new [TransactionInputs] instantiated with the specified parameters. + + /// Returns new [`TransactionInputs`] instantiated with the specified parameters. /// /// # Errors + /// /// Returns an error if: - /// - For a new account, account seed is not provided or the provided seed is invalid. - /// - For an existing account, account seed was provided. + /// - The partial blockchain's length is not the number of the reference block. + /// - The partial blockchain's commitment does not match the reference block's chain commitment. + /// - The partial blockchain does not proof inclusion of an authenticated input note. pub fn new( - account: impl Into, - account_seed: Option, + partial_account: impl Into, block_header: BlockHeader, block_chain: PartialBlockchain, input_notes: InputNotes, ) -> Result { - let partial_account: PartialAccount = account.into(); - - // validate the seed - validate_account_seed(&partial_account, account_seed)?; - // check the block_chain and block_header are consistent let block_num = block_header.block_num(); if block_chain.chain_length() != block_header.block_num() { @@ -87,10 +83,9 @@ impl TransactionInputs { } Ok(Self { - account: partial_account, - account_seed, + account: partial_account.into(), block_header, - block_chain, + blockchain: block_chain, input_notes, }) } @@ -103,11 +98,6 @@ impl TransactionInputs { &self.account } - /// For newly-created accounts, returns the account seed; for existing accounts, returns None. - pub fn account_seed(&self) -> Option { - self.account_seed - } - /// Returns block header for the block referenced by the transaction. pub fn block_header(&self) -> &BlockHeader { &self.block_header @@ -116,7 +106,7 @@ impl TransactionInputs { /// Returns partial blockchain containing authentication paths for all notes consumed by the /// transaction. pub fn blockchain(&self) -> &PartialBlockchain { - &self.block_chain + &self.blockchain } /// Returns the notes to be consumed in the transaction. @@ -130,29 +120,16 @@ impl TransactionInputs { /// Consumes these transaction inputs and returns their underlying components. pub fn into_parts( self, - ) -> ( - PartialAccount, - Option, - BlockHeader, - PartialBlockchain, - InputNotes, - ) { - ( - self.account, - self.account_seed, - self.block_header, - self.block_chain, - self.input_notes, - ) + ) -> (PartialAccount, BlockHeader, PartialBlockchain, InputNotes) { + (self.account, self.block_header, self.blockchain, self.input_notes) } } impl Serializable for TransactionInputs { fn write_into(&self, target: &mut W) { self.account.write_into(target); - self.account_seed.write_into(target); self.block_header.write_into(target); - self.block_chain.write_into(target); + self.blockchain.write_into(target); self.input_notes.write_into(target); } } @@ -160,11 +137,11 @@ impl Serializable for TransactionInputs { impl Deserializable for TransactionInputs { fn read_from(source: &mut R) -> Result { let partial_account = PartialAccount::read_from(source)?; - let account_seed = source.read()?; let block_header = BlockHeader::read_from(source)?; - let block_chain = PartialBlockchain::read_from(source)?; + let blockchain = PartialBlockchain::read_from(source)?; let input_notes = InputNotes::read_from(source)?; - Self::new(partial_account, account_seed, block_header, block_chain, input_notes) + + Self::new(partial_account, block_header, blockchain, input_notes) .map_err(|err| DeserializationError::InvalidValue(format!("{err}"))) } } @@ -172,36 +149,6 @@ impl Deserializable for TransactionInputs { // HELPER FUNCTIONS // ================================================================================================ -/// Validates that the provided seed is valid for this account. -fn validate_account_seed( - account: &PartialAccount, - account_seed: Option, -) -> Result<(), TransactionInputError> { - match (account.is_new(), account_seed) { - (true, Some(seed)) => { - let account_id = AccountId::new( - seed, - account.id().version(), - account.code().commitment(), - account.storage().commitment(), - ) - .map_err(TransactionInputError::InvalidAccountIdSeed)?; - - if account_id != account.id() { - return Err(TransactionInputError::InconsistentAccountSeed { - expected: account.id(), - actual: account_id, - }); - } - - Ok(()) - }, - (true, None) => Err(TransactionInputError::AccountSeedNotProvidedForNewAccount), - (false, Some(_)) => Err(TransactionInputError::AccountSeedProvidedForExistingAccount), - (false, None) => Ok(()), - } -} - /// Validates whether the provided note belongs to the note tree of the specified block. fn validate_is_in_block( note: &Note, diff --git a/crates/miden-objects/src/transaction/tx_witness.rs b/crates/miden-objects/src/transaction/tx_witness.rs index d9bd501a0a..938656a253 100644 --- a/crates/miden-objects/src/transaction/tx_witness.rs +++ b/crates/miden-objects/src/transaction/tx_witness.rs @@ -109,8 +109,7 @@ mod tests { ); let tx_inputs = TransactionInputs::new( - account.clone(), - None, + &account, block_header.clone(), partial_blockchain.clone(), InputNotes::default(), diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs index 723bf6b330..76d362e342 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs @@ -249,7 +249,7 @@ fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> a let auth_component: AccountComponent = IncrNonceAuthComponent.into(); - let (account, seed) = AccountBuilder::new([5; 32]) + let account = AccountBuilder::new([5; 32]) .with_auth_component(auth_component.clone()) .with_component(MockAccountComponent::with_slots(vec![StorageSlot::Value(Word::from( [5u32; 4], @@ -288,11 +288,8 @@ fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> a // Execute the account-creating transaction. // -------------------------------------------------------------------------------------------- - let tx_inputs = mock_chain.get_transaction_inputs(account.clone(), Some(seed), &[], &[])?; - let tx_context = TransactionContextBuilder::new(account) - .account_seed(Some(seed)) - .tx_inputs(tx_inputs) - .build()?; + let tx_inputs = mock_chain.get_transaction_inputs(&account, &[], &[])?; + let tx_context = TransactionContextBuilder::new(account).tx_inputs(tx_inputs).build()?; let tx = tx_context.execute_blocking().context("failed to execute account creating tx")?; let tx = LocalTransactionProver::default().prove_dummy(tx)?; @@ -342,7 +339,7 @@ fn proven_block_fails_on_creating_account_with_duplicate_account_id_prefix() -> // Construct a new account. // -------------------------------------------------------------------------------------------- let mut mock_chain = MockChain::new(); - let (account, _) = AccountBuilder::new([5; 32]) + let account = AccountBuilder::new([5; 32]) .with_auth_component(Auth::IncrNonce) .with_component(MockAccountComponent::with_slots(vec![StorageSlot::Value(Word::from( [5u32; 4], diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 128cee158d..f7624f14b7 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -746,17 +746,14 @@ fn test_account_component_storage_offset() -> miette::Result<()> { #[test] fn create_account_with_empty_storage_slots() -> anyhow::Result<()> { for account_type in [AccountType::FungibleFaucet, AccountType::RegularAccountUpdatableCode] { - let (account, seed) = AccountBuilder::new([5; 32]) + let account = AccountBuilder::new([5; 32]) .account_type(account_type) .with_auth_component(Auth::IncrNonce) .with_component(MockAccountComponent::with_empty_slots()) .build() .context("failed to build account")?; - TransactionContextBuilder::new(account) - .account_seed(Some(seed)) - .build()? - .execute_blocking()?; + TransactionContextBuilder::new(account).build()?.execute_blocking()?; } Ok(()) @@ -797,13 +794,11 @@ fn create_procedure_metadata_test_account( let id = AccountId::new(seed, version, code.commitment(), storage.commitment()) .context("failed to compute ID")?; - let account = Account::from_parts(id, AssetVault::default(), storage, code, Felt::from(0u32)); + let account = + Account::new(id, AssetVault::default(), storage, code, Felt::from(0u32), Some(seed))?; - let tx_inputs = mock_chain.get_transaction_inputs(account.clone(), Some(seed), &[], &[])?; - let tx_context = TransactionContextBuilder::new(account) - .account_seed(Some(seed)) - .tx_inputs(tx_inputs) - .build()?; + let tx_inputs = mock_chain.get_transaction_inputs(&account, &[], &[])?; + let tx_context = TransactionContextBuilder::new(account).tx_inputs(tx_inputs).build()?; let result = tx_context.execute_blocking().map_err(|err| { let TransactionExecutorError::TransactionProgramExecutionFailed(exec_err) = err else { @@ -1249,16 +1244,13 @@ fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { let faulty_auth_component = AccountComponent::compile(source_code, TransactionKernel::assembler(), vec![])? .with_supports_all_types(); - let (account, seed) = AccountBuilder::new([5; 32]) + let account = AccountBuilder::new([5; 32]) .with_auth_component(faulty_auth_component) .with_component(MockAccountComponent::with_empty_slots()) .build() .context("failed to build account")?; - let result = TransactionContextBuilder::new(account) - .account_seed(Some(seed)) - .build()? - .execute_blocking(); + let result = TransactionContextBuilder::new(account).build()?.execute_blocking(); assert_transaction_executor_error!(result, ERR_ACCOUNT_NONCE_CAN_ONLY_BE_INCREMENTED_ONCE); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index 2453663f91..121cc0a48f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -1209,7 +1209,8 @@ fn test_fpi_stale_account() -> anyhow::Result<()> { foreign_account.code().clone(), foreign_account.storage().into(), foreign_account.vault().into(), - ); + None, + )?; let overridden_foreign_account_inputs = AccountInputs::new(overridden_partial_accounts, foreign_account_inputs.witness().clone()); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 1928734219..261eff9bee 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -954,8 +954,7 @@ pub fn test_timelock() -> anyhow::Result<()> { // Attempt to consume note too early. // ---------------------------------------------------------------------------------------- - let tx_inputs = - mock_chain.get_transaction_inputs(account.clone(), None, &[timelock_note.id()], &[])?; + let tx_inputs = mock_chain.get_transaction_inputs(&account, &[timelock_note.id()], &[])?; let tx_context = TransactionContextBuilder::new(account.clone()) .with_source_manager(source_manager.clone()) .tx_inputs(tx_inputs.clone()) @@ -969,8 +968,7 @@ pub fn test_timelock() -> anyhow::Result<()> { .prove_next_block_at(lock_timestamp) .context("failed to prove next block at lock timestamp")?; - let tx_inputs = - mock_chain.get_transaction_inputs(account.clone(), None, &[timelock_note.id()], &[])?; + let tx_inputs = mock_chain.get_transaction_inputs(&account, &[timelock_note.id()], &[])?; let tx_context = TransactionContextBuilder::new(account).tx_inputs(tx_inputs).build()?; tx_context.execute_blocking()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index ded557fe01..39cad1ae48 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -87,7 +87,7 @@ use miden_objects::transaction::{ TransactionArgs, TransactionScript, }; -use miden_objects::{EMPTY_WORD, WORD_SIZE}; +use miden_objects::{EMPTY_WORD, ONE, WORD_SIZE}; use miden_processor::{AdviceInputs, Process, Word}; use miden_tx::TransactionExecutorError; use rand::{Rng, SeedableRng}; @@ -544,14 +544,13 @@ fn input_notes_memory_assertions( /// [`TransactionContext::execute_code`]). #[test] fn create_simple_account() -> anyhow::Result<()> { - let (account, seed) = AccountBuilder::new([6; 32]) + let account = AccountBuilder::new([6; 32]) .storage_mode(AccountStorageMode::Public) .with_auth_component(Auth::IncrNonce) .with_component(MockAccountComponent::with_empty_slots()) .build()?; let tx = TransactionContextBuilder::new(account) - .account_seed(Some(seed)) .build()? .execute_blocking() .context("failed to execute account-creating transaction")?; @@ -571,13 +570,8 @@ fn create_simple_account() -> anyhow::Result<()> { /// `seed` is valid in the context of the given `mock_chain`. pub fn create_account_test( account: Account, - seed: Word, ) -> Result { - TransactionContextBuilder::new(account) - .account_seed(Some(seed)) - .build() - .unwrap() - .execute_blocking() + TransactionContextBuilder::new(account).build().unwrap().execute_blocking() } pub fn create_multiple_accounts_test(storage_mode: AccountStorageMode) -> anyhow::Result<()> { @@ -589,7 +583,7 @@ pub fn create_multiple_accounts_test(storage_mode: AccountStorageMode) -> anyhow AccountType::FungibleFaucet, AccountType::NonFungibleFaucet, ] { - let (account, seed) = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .account_type(account_type) .storage_mode(storage_mode) .with_auth_component(Auth::IncrNonce) @@ -599,12 +593,12 @@ pub fn create_multiple_accounts_test(storage_mode: AccountStorageMode) -> anyhow .build() .context("account build failed")?; - accounts.push((account, seed)); + accounts.push(account); } - for (account, seed) in accounts { + for account in accounts { let account_type = account.account_type(); - create_account_test(account, seed).context(format!( + create_account_test(account).context(format!( "create_multiple_accounts_test test failed for account type {account_type}" ))?; } @@ -624,7 +618,7 @@ pub fn create_accounts_with_all_storage_modes() -> anyhow::Result<()> { /// Takes an account with a placeholder ID and returns the same account but with its ID replaced /// with a newly generated one. -fn compute_valid_account_id(account: Account) -> (Account, Word) { +fn compute_valid_account_id(account: Account) -> Account { let init_seed: [u8; 32] = [5; 32]; let seed = AccountId::compute_account_seed( init_seed, @@ -645,10 +639,9 @@ fn compute_valid_account_id(account: Account) -> (Account, Word) { .unwrap(); // Overwrite old ID with generated ID. - let (_, vault, storage, code, nonce) = account.into_parts(); - let account = Account::from_parts(account_id, vault, storage, code, nonce); - - (account, seed) + let (_, vault, storage, code, _nonce, _seed) = account.into_parts(); + // Set nonce to zero so this is considered a new account. + Account::new(account_id, vault, storage, code, ZERO, Some(seed)).unwrap() } /// Tests that creating a fungible faucet account with a non-empty initial balance in its reserved @@ -661,18 +654,19 @@ pub fn create_account_fungible_faucet_invalid_initial_balance() -> anyhow::Resul .with_component(MockAccountComponent::with_empty_slots()) .build_existing() .expect("account should be valid"); - let (id, vault, mut storage, code, _nonce) = account.into_parts(); + let (id, vault, mut storage, code, _nonce, _seed) = account.into_parts(); // Set the initial balance to a non-zero value manually, since the builder would not allow us to // do that. let faucet_data_slot = Word::from([0, 0, 0, 100u32]); storage.set_item(FAUCET_STORAGE_DATA_SLOT, faucet_data_slot).unwrap(); - // Set the nonce to zero so this is considered a new account. - let account = Account::from_parts(id, vault, storage, code, ZERO); - let (account, account_seed) = compute_valid_account_id(account); + // The compute account ID function will set the nonce to zero so this is considered a new + // account. + let account = Account::new(id, vault, storage, code, ONE, None)?; + let account = compute_valid_account_id(account); - let result = create_account_test(account, account_seed); + let result = create_account_test(account); assert_transaction_executor_error!( result, @@ -692,19 +686,20 @@ pub fn create_account_non_fungible_faucet_invalid_initial_reserved_slot() -> any StorageMap::with_entries([(asset.vault_key(), asset.into())]).unwrap(); let storage = AccountStorage::new(vec![StorageSlot::Map(non_fungible_storage_map)]).unwrap(); - let (account, _seed) = AccountBuilder::new([1; 32]) + let account = AccountBuilder::new([1; 32]) .account_type(AccountType::NonFungibleFaucet) .with_auth_component(NoopAuthComponent) .with_component(MockAccountComponent::with_empty_slots()) .build() .expect("account should be valid"); - let (id, vault, _storage, code, nonce) = account.into_parts(); + let (id, vault, _storage, code, _nonce, _seed) = account.into_parts(); - // Set the nonce to zero so this is considered a new account. - let account = Account::from_parts(id, vault, storage, code, nonce); - let (account, account_seed) = compute_valid_account_id(account); + // The compute account ID function will set the nonce to zero so this is considered a new + // account. + let account = Account::new(id, vault, storage, code, ONE, None)?; + let account = compute_valid_account_id(account); - let result = create_account_test(account, account_seed); + let result = create_account_test(account); assert_transaction_executor_error!( result, @@ -720,14 +715,14 @@ pub fn create_account_invalid_seed() -> anyhow::Result<()> { let mut mock_chain = MockChain::new(); mock_chain.prove_next_block()?; - let (account, seed) = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .account_type(AccountType::RegularAccountUpdatableCode) .with_auth_component(Auth::IncrNonce) .with_component(BasicWallet) .build()?; let tx_inputs = mock_chain - .get_transaction_inputs(account.clone(), Some(seed), &[], &[]) + .get_transaction_inputs(&account, &[], &[]) .expect("failed to get transaction inputs from mock chain"); // override the seed with an invalid seed to ensure the kernel fails @@ -736,7 +731,6 @@ pub fn create_account_invalid_seed() -> anyhow::Result<()> { AdviceInputs::default().with_map([(Word::from(account_seed_key), vec![ZERO; WORD_SIZE])]); let tx_context = TransactionContextBuilder::new(account) - .account_seed(Some(seed)) .tx_inputs(tx_inputs) .extend_advice_inputs(adv_inputs) .build()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 67e191f749..308c1a8f4d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -782,7 +782,7 @@ fn inputs_created_correctly() -> anyhow::Result<()> { .is_some() ); - let account = Account::from_parts( + let account = Account::new_existing( ACCOUNT_ID_PRIVATE_SENDER.try_into()?, AssetVault::mock(), AccountStorage::mock(), diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 6a03de85ff..fe45bf0fa7 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -180,9 +180,9 @@ pub struct MockChain { /// remain at the last observed state. committed_accounts: BTreeMap, - /// AccountId |-> AccountCredentials mapping to store the seed and authenticator for accounts - /// to simplify transaction creation. - account_credentials: BTreeMap, + /// AccountId |-> AccountAuthenticator mapping to store the authenticator for accounts to + /// simplify transaction creation. + account_authenticators: BTreeMap, // The RNG used to generate note serial numbers, account seeds or cryptographic keys. rng: ChaCha20Rng, @@ -216,7 +216,7 @@ impl MockChain { pub(super) fn from_genesis_block( genesis_block: ProvenBlock, account_tree: AccountTree, - account_credentials: BTreeMap, + account_authenticators: BTreeMap, ) -> anyhow::Result { let mut chain = MockChain { chain: Blockchain::default(), @@ -227,7 +227,7 @@ impl MockChain { pending_transactions: Vec::new(), committed_notes: BTreeMap::new(), committed_accounts: BTreeMap::new(), - account_credentials, + account_authenticators, // Initialize RNG with default seed. rng: ChaCha20Rng::from_seed(Default::default()), }; @@ -538,8 +538,8 @@ impl MockChain { /// - [`TxContextInput::Account`]: Initialize the builder with [`TransactionInputs`] where the /// account is passed as-is to the inputs. /// - /// In all cases, if the chain contains a seed or authenticator for the account, they are added - /// to the builder. + /// In all cases, if the chain contains authenticator for the account, they are added to the + /// builder. /// /// [`TxContextInput::Account`] can be used to build a chain of transactions against the same /// account that build on top of each other. For example, transaction A modifies an account @@ -554,10 +554,9 @@ impl MockChain { let input = input.into(); let reference_block = reference_block.into(); - let credentials = self.account_credentials.get(&input.id()); + let authenticator = self.account_authenticators.get(&input.id()); let authenticator = - credentials.and_then(|credentials| credentials.authenticator().cloned()); - let seed = credentials.and_then(|credentials| credentials.seed()); + authenticator.and_then(|authenticator| authenticator.authenticator().cloned()); anyhow::ensure!( reference_block.as_usize() < self.blocks.len(), @@ -584,18 +583,11 @@ impl MockChain { }; let tx_inputs = self - .get_transaction_inputs_at( - reference_block, - account.clone(), - seed, - note_ids, - unauthenticated_notes, - ) + .get_transaction_inputs_at(reference_block, &account, note_ids, unauthenticated_notes) .context("failed to gather transaction inputs")?; let tx_context_builder = TransactionContextBuilder::new(account) .authenticator(authenticator) - .account_seed(seed) .tx_inputs(tx_inputs); Ok(tx_context_builder) @@ -624,7 +616,6 @@ impl MockChain { &self, reference_block: BlockNumber, account: impl Into, - account_seed: Option, notes: &[NoteId], unauthenticated_notes: &[Note], ) -> anyhow::Result { @@ -682,7 +673,6 @@ impl MockChain { Ok(TransactionInputs::new( account, - account_seed, ref_block.clone(), partial_blockchain, input_notes, @@ -693,18 +683,11 @@ impl MockChain { pub fn get_transaction_inputs( &self, account: impl Into, - account_seed: Option, notes: &[NoteId], unauthenticated_notes: &[Note], ) -> anyhow::Result { let latest_block_num = self.latest_block_header().block_num(); - self.get_transaction_inputs_at( - latest_block_num, - account, - account_seed, - notes, - unauthenticated_notes, - ) + self.get_transaction_inputs_at(latest_block_num, account, notes, unauthenticated_notes) } /// Returns inputs for a transaction batch for all the reference blocks of the provided @@ -742,7 +725,9 @@ impl MockChain { let account_witness = self.account_tree().open(account_id); assert_eq!(account_witness.state_commitment(), account.commitment()); - Ok(AccountInputs::new(account.into(), account_witness)) + let partial_account = PartialAccount::from(account); + + Ok(AccountInputs::new(partial_account, account_witness)) } /// Gets the inputs for a block for the provided batches. @@ -1097,7 +1082,7 @@ impl Serializable for MockChain { self.pending_transactions.write_into(target); self.committed_accounts.write_into(target); self.committed_notes.write_into(target); - self.account_credentials.write_into(target); + self.account_authenticators.write_into(target); } } @@ -1111,7 +1096,8 @@ impl Deserializable for MockChain { let pending_transactions = Vec::::read_from(source)?; let committed_accounts = BTreeMap::::read_from(source)?; let committed_notes = BTreeMap::::read_from(source)?; - let account_credentials = BTreeMap::::read_from(source)?; + let account_authenticators = + BTreeMap::::read_from(source)?; Ok(Self { chain, @@ -1122,7 +1108,7 @@ impl Deserializable for MockChain { pending_transactions, committed_notes, committed_accounts, - account_credentials, + account_authenticators, rng: ChaCha20Rng::from_os_rng(), }) } @@ -1138,49 +1124,42 @@ pub enum AccountState { Exists, } -// ACCOUNT CREDENTIALS +// ACCOUNT AUTHENTICATOR // ================================================================================================ -/// A wrapper around the seed and authenticator of an account. +/// A wrapper around the authenticator of an account. #[derive(Debug, Clone)] -pub(super) struct AccountCredentials { - seed: Option, +pub(super) struct AccountAuthenticator { authenticator: Option>, } -impl AccountCredentials { - pub fn new(seed: Option, authenticator: Option>) -> Self { - Self { seed, authenticator } +impl AccountAuthenticator { + pub fn new(authenticator: Option>) -> Self { + Self { authenticator } } pub fn authenticator(&self) -> Option<&BasicAuthenticator> { self.authenticator.as_ref() } - - pub fn seed(&self) -> Option { - self.seed - } } -impl PartialEq for AccountCredentials { +impl PartialEq for AccountAuthenticator { fn eq(&self, other: &Self) -> bool { - let authenticator_eq = match (&self.authenticator, &other.authenticator) { + match (&self.authenticator, &other.authenticator) { (Some(a), Some(b)) => { a.keys().keys().zip(b.keys().keys()).all(|(a_key, b_key)| a_key == b_key) }, (None, None) => true, _ => false, - }; - authenticator_eq && self.seed == other.seed + } } } // SERIALIZATION // ================================================================================================ -impl Serializable for AccountCredentials { +impl Serializable for AccountAuthenticator { fn write_into(&self, target: &mut W) { - self.seed.write_into(target); self.authenticator .as_ref() .map(|auth| auth.keys().iter().collect::>()) @@ -1188,15 +1167,14 @@ impl Serializable for AccountCredentials { } } -impl Deserializable for AccountCredentials { +impl Deserializable for AccountAuthenticator { fn read_from(source: &mut R) -> Result { - let seed = Option::::read_from(source)?; let authenticator = Option::>::read_from(source)?; let authenticator = authenticator .map(|keys| BasicAuthenticator::new_with_rng(&keys, ChaCha20Rng::from_os_rng())); - Ok(Self { seed, authenticator }) + Ok(Self { authenticator }) } } @@ -1205,6 +1183,7 @@ impl Deserializable for AccountCredentials { /// Helper type to abstract over the inputs to [`MockChain::build_tx_context`]. See that method's /// docs for details. +#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone)] pub enum TxContextInput { AccountId(AccountId), @@ -1360,6 +1339,6 @@ mod tests { assert_eq!(chain.pending_transactions, deserialized.pending_transactions); assert_eq!(chain.committed_accounts, deserialized.committed_accounts); assert_eq!(chain.committed_notes, deserialized.committed_notes); - assert_eq!(chain.account_credentials, deserialized.account_credentials); + assert_eq!(chain.account_authenticators, deserialized.account_authenticators); } } diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 02940fc5bd..5a6dd7e9d1 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -37,7 +37,7 @@ use miden_objects::{Felt, FieldElement, MAX_OUTPUT_NOTES_PER_BATCH, NoteError, W use miden_processor::crypto::RpoRandomCoin; use rand::Rng; -use crate::mock_chain::chain::AccountCredentials; +use crate::mock_chain::chain::AccountAuthenticator; use crate::utils::{create_p2any_note, create_spawn_note}; use crate::{AccountState, Auth, MockChain}; @@ -45,7 +45,7 @@ use crate::{AccountState, Auth, MockChain}; #[derive(Debug, Clone)] pub struct MockChainBuilder { accounts: BTreeMap, - account_credentials: BTreeMap, + account_authenticators: BTreeMap, notes: Vec, rng: RpoRandomCoin, // Fee parameters. @@ -69,7 +69,7 @@ impl MockChainBuilder { Self { accounts: BTreeMap::new(), - account_credentials: BTreeMap::new(), + account_authenticators: BTreeMap::new(), notes: Vec::new(), rng: RpoRandomCoin::new(Default::default()), native_asset_id, @@ -79,9 +79,9 @@ impl MockChainBuilder { /// Initializes a new mock chain builder with the provided accounts. /// - /// This method only adds the accounts and cannot not register any seed or authenticator for it. + /// This method only adds the accounts and cannot not register any authenticators for them. /// Calling [`MockChain::build_tx_context`] on accounts added in this way will not work if the - /// account is new or if they need an authenticator. + /// account needs an authenticator. /// /// Due to these limitations, prefer using other methods to add accounts to the chain, e.g. /// [`MockChainBuilder::add_account_from_builder`]. @@ -186,17 +186,17 @@ impl MockChainBuilder { transactions, ); - MockChain::from_genesis_block(genesis_block, account_tree, self.account_credentials) + MockChain::from_genesis_block(genesis_block, account_tree, self.account_authenticators) } // ACCOUNT METHODS // ---------------------------------------------------------------------------------------- - /// Creates a new public [`BasicWallet`] account and registers the authenticator (if any) and - /// seed. + /// Creates a new public [`BasicWallet`] account and registers the authenticator (if any) for + /// it. /// /// This does not add the account to the chain state, but it can still be used to call - /// [`MockChain::build_tx_context`] to automatically handle the authenticator and seed. + /// [`MockChain::build_tx_context`] to automatically add the authenticator. pub fn create_new_wallet(&mut self, auth_method: Auth) -> anyhow::Result { let account_builder = AccountBuilder::new(self.rng.random()) .storage_mode(AccountStorageMode::Public) @@ -227,10 +227,10 @@ impl MockChainBuilder { } /// Creates a new public [`BasicFungibleFaucet`] account and registers the authenticator (if - /// any) and seed. + /// any) for it. /// /// This does not add the account to the chain state, but it can still be used to call - /// [`MockChain::build_tx_context`] to automatically handle the authenticator and seed. + /// [`MockChain::build_tx_context`] to automatically add the authenticator. pub fn create_new_faucet( &mut self, auth_method: Auth, @@ -349,9 +349,9 @@ impl MockChainBuilder { /// to the initial chain state. It can then be used in a transaction without having to /// validate its seed. /// - If [`AccountState::New`] is given the account is built as a new account and is **not** - /// added to the chain. Its seed and authenticator are registered (if any). Its first - /// transaction will be its creation transaction. [`MockChain::build_tx_context`] can be - /// called with the account to automatically handle the authenticator and seed. + /// added to the chain. Its authenticator is registered (if present). Its first transaction + /// will be its creation transaction. [`MockChain::build_tx_context`] can be called with the + /// account to automatically add the authenticator. pub fn add_account_from_builder( &mut self, auth_method: Auth, @@ -361,19 +361,16 @@ impl MockChainBuilder { let (auth_component, authenticator) = auth_method.build_component(); account_builder = account_builder.with_auth_component(auth_component); - let (account, seed) = if let AccountState::New = account_state { - let (account, seed) = - account_builder.build().context("failed to build account from builder")?; - (account, Some(seed)) + let account = if let AccountState::New = account_state { + account_builder.build().context("failed to build account from builder")? } else { - let account = account_builder + account_builder .build_existing() - .context("failed to build account from builder")?; - (account, None) + .context("failed to build account from builder")? }; - self.account_credentials - .insert(account.id(), AccountCredentials::new(seed, authenticator)); + self.account_authenticators + .insert(account.id(), AccountAuthenticator::new(authenticator)); if let AccountState::Exists = account_state { self.accounts.insert(account.id(), account.clone()); @@ -384,9 +381,9 @@ impl MockChainBuilder { /// Adds the provided account to the list of genesis accounts. /// - /// This method only adds the account and does not store its account credentials (seed and - /// authenticator) for it. Calling [`MockChain::build_tx_context`] on accounts added in this - /// way will not work if the account is new or if they need an authenticator. + /// This method only adds the account and does not store its account authenticator for it. + /// Calling [`MockChain::build_tx_context`] on accounts added in this way will not work if + /// the account needs an authenticator. /// /// Due to these limitations, prefer using other methods to add accounts to the chain, e.g. /// [`MockChainBuilder::add_account_from_builder`]. diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 36ae3e8bbd..41b781b603 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -76,7 +76,6 @@ pub type MockAuthenticator = BasicAuthenticator; pub struct TransactionContextBuilder { source_manager: Arc, account: Account, - account_seed: Option, advice_inputs: AdviceInputs, authenticator: Option, expected_output_notes: Vec, @@ -96,7 +95,6 @@ impl TransactionContextBuilder { Self { source_manager: Arc::new(DefaultSourceManager::default()), account, - account_seed: None, input_notes: Vec::new(), expected_output_notes: Vec::new(), tx_script: None, @@ -148,12 +146,6 @@ impl TransactionContextBuilder { Self::new(account) } - /// Override and set the account seed manually - pub fn account_seed(mut self, account_seed: Option) -> Self { - self.account_seed = account_seed; - self - } - /// Extend the advice inputs with the provided [AdviceInputs] instance. pub fn extend_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self { self.advice_inputs.extend(advice_inputs); @@ -287,10 +279,9 @@ impl TransactionContextBuilder { let input_note_ids: Vec = mock_chain.committed_notes().values().map(MockChainNote::id).collect(); - let account = PartialAccount::from(&self.account); mock_chain - .get_transaction_inputs(account, self.account_seed, &input_note_ids, &[]) + .get_transaction_inputs(&self.account, &input_note_ids, &[]) .context("failed to get transaction inputs from mock chain")? }, }; @@ -299,8 +290,7 @@ impl TransactionContextBuilder { // merkle paths of assets and storage maps, in order to test lazy loading. // Otherwise, load the full account. let tx_inputs = if self.is_lazy_loading_enabled { - let (account, account_seed, block_header, partial_blockchain, input_notes) = - tx_inputs.into_parts(); + let (account, block_header, partial_blockchain, input_notes) = tx_inputs.into_parts(); // Construct a partial vault that tracks the empty word, but none of the assets // that are actually in the asset tree. That way, the partial vault has the same // root as the full vault, but will not add any relevant merkle paths to the @@ -335,15 +325,10 @@ impl TransactionContextBuilder { account.code().clone(), partial_storage, partial_vault, - ); - - TransactionInputs::new( - account, - account_seed, - block_header, - partial_blockchain, - input_notes, - )? + None, + )?; + + TransactionInputs::new(account, block_header, partial_blockchain, input_notes)? } else { tx_inputs }; diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index 44f11e4619..cab0246980 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -195,15 +195,14 @@ impl DataStore for TransactionContext { &self, account_id: AccountId, _ref_blocks: BTreeSet, - ) -> impl FutureMaybeSend< - Result<(PartialAccount, Option, BlockHeader, PartialBlockchain), DataStoreError>, - > { + ) -> impl FutureMaybeSend> + { assert_eq!(account_id, self.account().id()); assert_eq!(account_id, self.tx_inputs.account().id()); - let (partial_account, seed, header, mmr, _) = self.tx_inputs.clone().into_parts(); + let (partial_account, header, mmr, _) = self.tx_inputs.clone().into_parts(); - async move { Ok((partial_account, seed, header, mmr)) } + async move { Ok((partial_account, header, mmr)) } } fn get_foreign_account_inputs( diff --git a/crates/miden-testing/tests/scripts/p2id.rs b/crates/miden-testing/tests/scripts/p2id.rs index 8b8c866d37..e08ee2bb61 100644 --- a/crates/miden-testing/tests/scripts/p2id.rs +++ b/crates/miden-testing/tests/scripts/p2id.rs @@ -53,7 +53,7 @@ fn p2id_script_multiple_assets() -> anyhow::Result<()> { .execute_blocking()?; // vault delta - let target_account_after: Account = Account::from_parts( + let target_account_after: Account = Account::new_existing( target_account.id(), AssetVault::new(&[fungible_asset_1, fungible_asset_2]).unwrap(), target_account.storage().clone(), @@ -113,7 +113,7 @@ fn prove_consume_note_with_new_account() -> anyhow::Result<()> { .execute_blocking()?; // Apply delta to the target account to verify it is no longer new - let target_account_after: Account = Account::from_parts( + let target_account_after: Account = Account::new_existing( target_account.id(), AssetVault::new(&[fungible_asset]).unwrap(), target_account.storage().clone(), diff --git a/crates/miden-testing/tests/scripts/p2ide.rs b/crates/miden-testing/tests/scripts/p2ide.rs index 15d9bcc197..5d6d6ca16c 100644 --- a/crates/miden-testing/tests/scripts/p2ide.rs +++ b/crates/miden-testing/tests/scripts/p2ide.rs @@ -43,7 +43,7 @@ fn p2ide_script_success_without_reclaim_or_timelock() -> anyhow::Result<()> { .build()? .execute_blocking()?; - let target_account_after: Account = Account::from_parts( + let target_account_after: Account = Account::new_existing( target_account.id(), AssetVault::new(&[fungible_asset])?, target_account.storage().clone(), @@ -80,7 +80,7 @@ fn p2ide_script_success_timelock_unlock_before_reclaim_height() -> anyhow::Resul .build()? .execute_blocking()?; - let target_account_after: Account = Account::from_parts( + let target_account_after: Account = Account::new_existing( target_account.id(), AssetVault::new(&[fungible_asset])?, target_account.storage().clone(), @@ -152,7 +152,7 @@ fn p2ide_script_timelocked_reclaim_disabled() -> anyhow::Result<()> { .build()? .execute_blocking()?; - let target_after = Account::from_parts( + let target_after = Account::new_existing( target_account.id(), AssetVault::new(&[fungible_asset])?, target_account.storage().clone(), @@ -201,7 +201,7 @@ fn p2ide_script_reclaim_fails_before_timelock_expiry() -> anyhow::Result<()> { .build()? .execute_blocking()?; - let sender_account_after: Account = Account::from_parts( + let sender_account_after: Account = Account::new_existing( sender_account.id(), AssetVault::new(&[fungible_asset])?, sender_account.storage().clone(), @@ -280,7 +280,7 @@ fn p2ide_script_reclaimable_timelockable() -> anyhow::Result<()> { .build()? .execute_blocking()?; - let target_after = Account::from_parts( + let target_after = Account::new_existing( target_account.id(), AssetVault::new(&[fungible_asset])?, target_account.storage().clone(), @@ -324,7 +324,7 @@ fn p2ide_script_reclaim_success_after_timelock() -> anyhow::Result<()> { .build()? .execute_blocking()?; - let sender_after = Account::from_parts( + let sender_after = Account::new_existing( sender_account.id(), AssetVault::new(&[fungible_asset])?, sender_account.storage().clone(), diff --git a/crates/miden-testing/tests/wallet/mod.rs b/crates/miden-testing/tests/wallet/mod.rs index 52a5d7f096..84f498e88f 100644 --- a/crates/miden-testing/tests/wallet/mod.rs +++ b/crates/miden-testing/tests/wallet/mod.rs @@ -29,8 +29,7 @@ fn wallet_creation() { let account_type = AccountType::RegularAccountImmutableCode; let storage_mode = AccountStorageMode::Private; - let (wallet, _) = - create_basic_wallet(init_seed, auth_scheme, account_type, storage_mode).unwrap(); + let wallet = create_basic_wallet(init_seed, auth_scheme, account_type, storage_mode).unwrap(); let expected_code = AccountCode::from_components( &[AuthRpoFalcon512::new(pub_key).into(), BasicWallet.into()], diff --git a/crates/miden-tx/src/executor/data_store.rs b/crates/miden-tx/src/executor/data_store.rs index e5e32a748d..1b8569bc1a 100644 --- a/crates/miden-tx/src/executor/data_store.rs +++ b/crates/miden-tx/src/executor/data_store.rs @@ -31,9 +31,7 @@ pub trait DataStore: MastForestStore { &self, account_id: AccountId, ref_blocks: BTreeSet, - ) -> impl FutureMaybeSend< - Result<(PartialAccount, Option, BlockHeader, PartialBlockchain), DataStoreError>, - >; + ) -> impl FutureMaybeSend>; /// Returns a partial foreign account state together with a witness, proving its validity in the /// specified transaction reference block. diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index c06595eda7..5fb3604426 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -269,7 +269,7 @@ where let mut ref_blocks = validate_input_notes(¬es, block_ref)?; ref_blocks.insert(block_ref); - let (account, seed, ref_block, mmr) = self + let (account, ref_block, mmr) = self .data_store .get_transaction_inputs(account_id, ref_blocks) .await @@ -277,7 +277,7 @@ where validate_account_inputs(tx_args, &ref_block)?; - let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes) + let tx_inputs = TransactionInputs::new(account, ref_block, mmr, notes) .map_err(TransactionExecutorError::InvalidTransactionInputs)?; let (stack_inputs, tx_advice_inputs) = diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index 7ae3283e51..fbac6a713d 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -146,7 +146,7 @@ impl LocalTransactionProver { ) .map_err(TransactionProverError::CreateAccountProcedureIndexMap)?; - let (partial_account, _, ref_block, _, input_notes) = tx_inputs.into_parts(); + let (partial_account, ref_block, _, input_notes) = tx_inputs.into_parts(); let mut host = TransactionProverHost::new( &partial_account, input_notes, @@ -196,7 +196,7 @@ impl Default for LocalTransactionProver { } fn partial_account_to_full(partial_account: PartialAccount) -> Account { - let (id, nonce, code, partial_storage, partial_vault) = partial_account.into_parts(); + let (id, partial_vault, partial_storage, code, nonce, seed) = partial_account.into_parts(); // For new accounts, the partial storage must represent the full initial account // storage. @@ -206,7 +206,8 @@ fn partial_account_to_full(partial_account: PartialAccount) -> Account { debug_assert_eq!(partial_vault.leaves().count(), 0); let vault = AssetVault::default(); - Account::from_parts(id, vault, storage, code, nonce) + Account::new(id, vault, storage, code, nonce, seed) + .expect("partial account should ensure internal consistency for seed") } fn partial_storage_to_full(partial_storage: PartialStorage) -> AccountStorage { @@ -249,7 +250,7 @@ impl LocalTransactionProver { ) -> Result { let (account_delta, tx_outputs, tx_witness, _) = executed_tx.into_parts(); - let (partial_account, _, ref_block, _, input_notes) = tx_witness.tx_inputs.into_parts(); + let (partial_account, ref_block, _, input_notes) = tx_witness.tx_inputs.into_parts(); self.build_proven_transaction( &input_notes, From c80f3cc4f94f0d3a69e531dfc0a9940b16f25db2 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Tue, 16 Sep 2025 11:01:46 +0300 Subject: [PATCH 040/133] docs: add Fees section and align docs with kernel fee logic (#1728) * docs: add Fees section explaining fee computation and native asset payment * docs: mention automatic fee deduction in epilogue and link to Fees page * docs: clarify fees are paid in native asset and link to Fees page * docs: add Fees page to documentation navigation * Update fees.md * make fees a sub-section of transactions * Update docs/src/fees.md Co-authored-by: Philipp Gackstatter * Update docs/src/fees.md Co-authored-by: Philipp Gackstatter * Update docs/src/transaction.md Co-authored-by: Philipp Gackstatter * Update fees.md * Update fees.md * Update docs/src/fees.md Co-authored-by: Philipp Gackstatter * Update docs/src/fees.md Co-authored-by: Philipp Gackstatter * Update docs/src/fees.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Philipp Gackstatter Co-authored-by: Marti Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/src/SUMMARY.md | 1 + docs/src/asset.md | 4 ++-- docs/src/fees.md | 19 +++++++++++++++++++ docs/src/transaction.md | 1 + 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 docs/src/fees.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 66c7abe9f2..8017d07623 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -12,6 +12,7 @@ - [Notes](./note.md) - [Assets](./asset.md) - [Transactions](./transaction.md) + - [Fees](./fees.md) - [State](./state.md) - [Blockchain](./blockchain.md) - [Miden Protocol Library](./protocol_library.md) diff --git a/docs/src/asset.md b/docs/src/asset.md index 35825b11cc..e57cb672af 100644 --- a/docs/src/asset.md +++ b/docs/src/asset.md @@ -15,8 +15,8 @@ In Miden, assets serve as the primary means of expressing and transferring value 3. **Censorship resistance:** Users can transact freely and privately with no single contract or entity controlling `Asset` transfers. This reduces the risk of censored transactions, resulting in a more open and resilient system. -4. **Flexible fee payment:** - Unlike protocols that require a specific base `Asset` for fees, Miden allows users to pay fees in any supported `Asset`. This flexibility simplifies the user experience. +4. **Fee payment in native asset:** + Transaction fees are paid in the chain’s native asset as defined by the current reference block’s fee parameters. See [Fees](./fees.md). ## Native asset diff --git a/docs/src/fees.md b/docs/src/fees.md new file mode 100644 index 0000000000..c054685f93 --- /dev/null +++ b/docs/src/fees.md @@ -0,0 +1,19 @@ +# Fees + +Miden transactions pay a fee that is computed and charged automatically by the transaction kernel during the epilogue. + +## How fees are computed + +- The fee depends on the number of VM cycles the transaction executes and grows logarithmically with that count. +- The kernel estimates the number of verification cycles by taking log2 of the estimated total execution cycles (rounded up). The result is then multiplied by the `verification_base_fee` from the reference block’s fee parameters. +- In other words, the fee is proportional to the logarithm of the transaction’s number of execution cycles, scaled by the base verification fee defined in the block header. + +## Which asset is used to pay fees + +- Fees are paid in the chain’s native asset, defined by the current reference block’s fee parameters. +- The native asset is chosen once as part of the genesis block and then copied to every newly created block, which means the native asset stays consistent for a given network. + +## How fees are paid + +- Users should ensure their account’s vault holds sufficient balance of the native asset to cover the fee. The fee is charged automatically; no explicit transaction kernel API must be called. +- If the account does not contain enough of the native asset to cover the computed fee, the transaction fails during the epilogue. diff --git a/docs/src/transaction.md b/docs/src/transaction.md index 8911e702c4..a6b08335af 100644 --- a/docs/src/transaction.md +++ b/docs/src/transaction.md @@ -47,6 +47,7 @@ A `Transaction` requires several inputs: 4. **Epilogue** Completes the execution, resulting in an updated account state and a generated zero-knowledge proof. The validity of the resulting transaction is ensured by a combination of user-defined and protocol-defined checks: - The account's [authentication procedure](account/code.md#authentication) is called to authorize the transaction. + - The transaction fee is computed and removed from the account's vault in the chain's native asset. See [Fees](./fees.md). - The account's state must have changed, or at least one input note must have been consumed to make the transaction non-empty. - If the account's state has changed, the `nonce` must have been incremented to prevent replay attacks. - Additionally, the sum of all input assets must be equal to the sum of all output assets (if the account is not a faucet). From 6b856f68553e160f5fc6ba8c5773c84094eb2a3d Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Wed, 17 Sep 2025 15:39:16 +0300 Subject: [PATCH 041/133] Replace word functionality with `std::word` procedures (#1897) * refactor: use word::test_eq instead of eqw, remove is_key_greater and is_key_less from the link_map * chore: update changelog * chore: update changelog --- CHANGELOG.md | 1 + .../transaction/lib/account_delta.masm | 5 +- .../kernels/transaction/lib/asset_vault.masm | 7 +- .../asm/kernels/transaction/lib/link_map.masm | 154 +----------------- .../kernels/transaction/lib/output_note.masm | 4 +- .../src/transaction/kernel_procedures.rs | 16 +- .../src/kernel_tests/tx/test_account.rs | 24 +-- .../src/kernel_tests/tx/test_epilogue.rs | 5 +- .../src/kernel_tests/tx/test_link_map.rs | 65 -------- 9 files changed, 42 insertions(+), 239 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6b395f5e6..da316d83d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - [BREAKING] Move `TransactionKernelError` to miden-tx ([#1859](https://github.com/0xMiden/miden-base/pull/1859)). - Change terminology of "current note" to "active note" ([#1863](https://github.com/0xMiden/miden-base/issues/1863)). - [BREAKING] Move and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). +- Replace `eqw` usages with `exec.word::test_eq` and `exec.word::eq`, remove `is_key_greater` and `is_key_less` from `link_map` module ([#1897](https://github.com/0xMiden/miden-base/pull/1897)). ## 0.11.3 (2025-09-15) diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm index 426418f990..a60f558061 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm @@ -6,6 +6,7 @@ use.$kernel::asset use.$kernel::asset_vault use.std::crypto::hashes::rpo use.std::math::u64 +use.std::word # ERRORS # ================================================================================================= @@ -176,7 +177,7 @@ proc.update_value_slot_delta dup.4 exec.get_item_initial # => [INIT_VALUE, CURRENT_VALUE, slot_idx, RATE, RATE, PERM] - eqw not + exec.word::test_eq not # => [was_changed, INIT_VALUE, CURRENT_VALUE, slot_idx, RATE, RATE, PERM] # only include in delta if the slot's value has changed @@ -252,7 +253,7 @@ proc.update_map_slot_delta.4 swapw.2 # => [NEW_VALUE, INIT_VALUE, KEY, ...] - eqw not + exec.word::test_eq not # => [was_changed, NEW_VALUE, INIT_VALUE, KEY, ...] # if the key-value pair has actually changed, update the hasher diff --git a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm index f43beb706f..4cae60233e 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm @@ -1,4 +1,5 @@ use.std::collections::smt +use.std::word use.$kernel::account_id use.$kernel::asset @@ -154,11 +155,7 @@ export.has_non_fungible_asset # => [ASSET] # compare with EMPTY_WORD to assess if the asset exists in the vault - padw eqw not - # => [has_asset, PAD, ASSET] - - # organize the stack for return - movdn.4 dropw movdn.4 dropw + exec.word::eqz not # => [has_asset] end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm b/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm index ab9cbd18c7..bb03a73d5a 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm @@ -1,4 +1,5 @@ use.std::collections::smt +use.std::word use.$kernel::memory # A link map is a map data structure based on a sorted linked list. @@ -835,11 +836,10 @@ end #! Inputs: [entry_ptr, KEY] #! Outputs: [] proc.assert_key_is_greater - exec.get_key swapw - # => [KEY, ENTRY_KEY] + exec.get_key + # => [ENTRY_KEY, KEY] - exec.is_key_greater assert.err=ERR_LINK_MAP_PROVIDED_KEY_NOT_GREATER_THAN_ENTRY_KEY - # => [] + exec.word::gt assert.err=ERR_LINK_MAP_PROVIDED_KEY_NOT_GREATER_THAN_ENTRY_KEY end #! Asserts that the KEY is less than the key in the entry pointer. @@ -847,10 +847,10 @@ end #! Inputs: [entry_ptr, KEY] #! Outputs: [] proc.assert_key_is_less - exec.get_key swapw - # => [KEY, ENTRY_KEY] + exec.get_key + # => [ENTRY_KEY, KEY] - exec.is_key_less assert.err=ERR_LINK_MAP_PROVIDED_KEY_NOT_LESS_THAN_ENTRY_KEY + exec.word::lt assert.err=ERR_LINK_MAP_PROVIDED_KEY_NOT_LESS_THAN_ENTRY_KEY # => [] end @@ -925,143 +925,3 @@ export.assert_entry_ptr_is_valid # this assertion is always true if is_empty_map is true or assert.err=ERR_LINK_MAP_MAP_PTR_IN_ENTRY_DOES_NOT_MATCH_EXPECTED_MAP_PTR end - -# COMPARISON OPERATIONS -# ------------------------------------------------------------------------------------------------- - -#! Returns true if KEY1 is strictly greater than KEY2, false otherwise. -#! -#! The implementation avoids branching for performance reasons. -#! The procedure is exported for testing purposes only. -#! -#! For reference, this is equivalent to the following Rust function: -#! -#! fn is_key_greater(key1: Word, key2: Word) -> bool { -#! let mut result = false; -#! let mut cont = true; -#! -#! for i in (0..4).rev() { -#! let gt = key1[i].as_int() > key2[i].as_int(); -#! let eq = key1[i].as_int() == key2[i].as_int(); -#! result |= gt & cont; -#! cont &= eq; -#! } -#! -#! result -#! } -#! -#! Inputs: [KEY1, KEY2] -#! Outputs: [is_key_greater] -export.is_key_greater - exec.arrange_words_adjacent - # => [2_3, 1_3, 2_2, 1_2, 2_1, 1_1, 2_0, 1_0] - - push.1.0 - # => [is_key_greater, continue, 2_3, 1_3, 2_2, 1_2, 2_1, 1_1, 2_0, 1_0] - - repeat.4 - movup.3 movup.3 - # => [2_x, 1_x, is_key_greater, continue, ] - - # check 1_x == 2_x; if so, we continue - dup dup.2 eq - # => [is_felt_eq, 2_x, 1_x, is_key_greater, continue, ] - - movdn.3 - # => [2_x, 1_x, is_key_greater, is_felt_eq, continue, ] - - # check 1_x > 2_x - gt - # => [is_felt_gt, is_key_greater, is_felt_eq, continue, ] - - dup.3 and - # => [is_felt_gt_if_continue, is_key_greater, is_felt_eq, continue, ] - - or movdn.2 - # => [is_felt_eq, continue, is_key_greater, ] - - # keeps continue at 1 if the felts are equal - # sets continue to 0 if the felts are not equal - and - # => [continue, is_key_greater, ] - - swap - # => [is_key_greater, continue, ] - end - # => [is_key_greater, continue] - - swap drop - # => [is_key_greater] -end - -#! Returns true if KEY1 is strictly less than KEY2, false otherwise. -#! -#! The implementation avoids branching for performance reasons. -#! The procedure is exported for testing purposes only. -#! -#! From an implementation standpoint this is exactly the same as `is_key_greater` except it uses -#! `lt` rather than `gt`. See its docs for details. -#! -#! Inputs: [KEY1, KEY2] -#! Outputs: [is_key_less] -export.is_key_less - exec.arrange_words_adjacent - # => [2_3, 1_3, 2_2, 1_2, 2_1, 1_1, 2_0, 1_0] - - push.1.0 - # => [is_key_less, continue, 2_3, 1_3, 2_2, 1_2, 2_1, 1_1, 2_0, 1_0] - - repeat.4 - movup.3 movup.3 - # => [2_x, 1_x, is_key_less, continue, ] - - # check 1_x == 2_x; if so, we continue - dup dup.2 eq - # => [is_felt_eq, 2_x, 1_x, is_key_less, continue, ] - - movdn.3 - # => [2_x, 1_x, is_key_less, is_felt_eq, continue, ] - - # check 1_x < 2_x - lt - # => [is_felt_lt, is_key_less, is_felt_eq, continue, ] - - dup.3 and - # => [is_felt_lt_if_continue, is_key_less, is_felt_eq, continue, ] - - or movdn.2 - # => [is_felt_eq, continue, is_key_less, ] - - # keeps continue at 1 if the felts are equal - # sets continue to 0 if the felts are not equal - and - # => [continue, is_key_less, ] - - swap - # => [is_key_less, continue, ] - end - # => [is_key_less, continue] - - swap drop - # => [is_key_less] -end - -#! Arranges the given words such that the corresponding elements are next to each other. -#! -#! Inputs: [KEY1, KEY2] -#! Outputs: [key2_3, key1_3, key2_2, key1_2, key2_1, key1_1, key2_0, key1_0] -proc.arrange_words_adjacent - # => [1_3, 1_2, 1_1, 1_0, 2_3, 2_2, 2_1, 2_0] - - movup.3 movup.7 - # => [2_0, 1_0, 1_3, 1_2, 1_1, 2_3, 2_2, 2_1] - - movup.4 movup.7 - # => [2_1, 1_1, 2_0, 1_0, 1_3, 1_2, 2_3, 2_2] - - movup.5 movup.7 - # => [2_2, 1_2, 2_1, 1_1, 2_0, 1_0, 1_3, 2_3] - - movup.6 movup.7 - # => [2_3, 1_3, 2_2, 1_2, 2_1, 1_1, 2_0, 1_0] -end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm b/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm index 8eb215983f..1589a82584 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm @@ -3,6 +3,7 @@ use.$kernel::memory use.$kernel::note use.$kernel::asset use.$kernel::constants +use.std::word # CONSTANTS # ================================================================================================= @@ -499,7 +500,8 @@ proc.add_non_fungible_asset while.true # load the asset and compare - mem_loadw eqw assertz.err=ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS + mem_loadw exec.word::test_eq + assertz.err=ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS # => [ASSET', ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] # drop ASSET' and increment the asset pointer diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index b080026d09..bf0746f11d 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -34,27 +34,27 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ // account_get_map_item word!("0x061afed82416f597aa87e2de48d7dad96a40c5f3e2c839d6522bafd3646414ca"), // account_set_map_item - word!("0xb539c160a862fedb3a4bd81bcea2076adc54c4e064a2c74b8770cef778ec5c59"), + word!("0x33309593aa405279a27907cee07b0cde54dbf4c088d0995a05f61e60236cfb0f"), // account_get_initial_vault_root word!("0x4d4d91079aaacad1bc86b29a0d61d25508ccb705c29d1b1357016f7373bf299e"), // account_get_vault_root word!("0x42a2bfb8eac4fce9bbf75ea15215b00729faeeaf7fff784692948d3f618a9bb7"), // account_add_asset - word!("0x9a96c031f9ca6d839acc5ce6aa01b63ddba91fe312ea824ccd224c365cc1327d"), + word!("0x8e7c972f1eaa807de54df810f851db14557bb88c846c7bcb3d21340f34834c57"), // account_remove_asset - word!("0x8372d56ed394254481026d264b8ea97c2342ba42db21c61b2e96c6bf06d950a9"), + word!("0xc5ac5f912281dca3f8f778e713f564f71695af68d094937072a303ea0d8385c0"), // account_get_balance word!("0xb4e92ae0196ca128a451e40dd8a5ff56c13919efa67f63dca488214fbba3ffbc"), // account_has_non_fungible_asset - word!("0x9c0f7851d3211ff4744393b673ce4e1aeb05525dc9186a218c8ff8d6f1a04ee7"), + word!("0x62776e8641d241f404724cf115c416e4918b75ced3625a5cd21d487fd7aef68b"), // account_compute_delta_commitment - word!("0x95dab713c8f9fe01a4c81d1fb57737ac6a45171659456dbc03df049654db3d78"), + word!("0x01cf0da392179c475c4ffdca0f0114932e09b75189433969debe1895881bf8b0"), // account_was_procedure_called word!("0x84c8c518a005605619909976ce54c41d6a88505e815421ff4b5516d0285b28bf"), // faucet_mint_asset - word!("0x80876450b0bcff4534e17cd850c75bcfdff4cc0b00703465f713d350001cce79"), + word!("0xbdd92de4f1308992f95c45de90a79799ff96439494088d715bd7838880625433"), // faucet_burn_asset - word!("0xa78c7ae04e75f6e447fa72df757dfb0854a3236b86ece2b5478a757b3c156ad5"), + word!("0x17b95ae638852d24116254223385d5c627820000880b7f60f39c5b3cb5e5231e"), // faucet_get_total_fungible_asset_issuance word!("0x7d32952d4dc0edd0311e3424b8128df2d48cf949f800c28218fbc851a8db42b5"), // faucet_is_non_fungible_asset_issued @@ -80,7 +80,7 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ // output_note_get_recipient word!("0xc824115ed79a2e1670daed8c18fba1bc15f54c5ec0ec6699de69a00b21d9df92"), // output_note_add_asset - word!("0x47673b932aac8c186cb0979bbc3c4c2afa00fa1b80c0afb5e5efb4924bba48d9"), + word!("0xb40136c331981a3aff1cc2879e394517abef86a75e4ee5180161976ac360f43d"), // tx_get_num_input_notes word!("0xfcc186d4b65c584f3126dda1460b01eef977efd76f9e36f972554af28e33c685"), // tx_get_input_notes_commitment diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index f7624f14b7..00180072e3 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -72,6 +72,8 @@ pub fn compute_current_commitment() -> miette::Result<()> { let tx_script = format!( r#" + use.std::word + use.miden::prologue use.miden::account use.mock::account->mock_account @@ -115,10 +117,10 @@ pub fn compute_current_commitment() -> miette::Result<()> { swapdw dropw dropw swapw dropw # => [STORAGE_COMMITMENT1, STORAGE_COMMITMENT0] - eqw not assert.err="storage commitment should have been updated by compute_current_commitment" - # => [STORAGE_COMMITMENT1, STORAGE_COMMITMENT0] - - dropw dropw dropw dropw + # assert that the commitment has changed + exec.word::eq + assertz.err="storage commitment should have been updated by compute_current_commitment" + # => [] end "#, key = &key, @@ -609,6 +611,7 @@ fn test_account_component_storage_offset() -> miette::Result<()> { // We will then assert that we are able to retrieve the correct elements from storage // insuring consistent "set" and "get" using offsets. let source_code_component1 = " + use.std::word use.miden::account export.foo_write @@ -621,13 +624,14 @@ fn test_account_component_storage_offset() -> miette::Result<()> { export.foo_read push.0 exec.account::get_item - push.1.2.3.4 eqw assert - - dropw dropw + push.1.2.3.4 + + exec.word::eq assert end "; let source_code_component2 = " + use.std::word use.miden::account export.bar_write @@ -640,9 +644,9 @@ fn test_account_component_storage_offset() -> miette::Result<()> { export.bar_read push.0 exec.account::get_item - push.5.6.7.8 eqw assert - - dropw dropw + push.5.6.7.8 + + exec.word::eq assert end "; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index c90535f017..67231c7ad5 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -535,6 +535,7 @@ fn test_epilogue_empty_transaction_with_empty_output_note() -> anyhow::Result<() // create an empty output note in the transaction script let tx_script_source = format!( r#" + use.std::word use.miden::output_note use.$kernel::prologue use.$kernel::epilogue @@ -562,10 +563,12 @@ fn test_epilogue_empty_transaction_with_empty_output_note() -> anyhow::Result<() # make sure that output note was created: compare the output note hash with an empty # word exec.note::compute_output_notes_commitment - padw eqw assertz.err="output note was created, but the output notes hash remains to be zeros" + exec.word::eqz assertz.err="output note was created, but the output notes hash remains to be zeros" + # => [note_idx, GARBAGE(15)] # clean the stack dropw dropw dropw dropw + # => [] exec.epilogue::finalize_transaction end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs b/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs index 086ccf348b..e5229e0a85 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs @@ -1,5 +1,4 @@ use alloc::vec::Vec; -use core::cmp::Ordering; use std::collections::BTreeMap; use std::string::String; @@ -11,7 +10,6 @@ use rand::seq::IteratorRandom; use winter_rand_utils::rand_value; use crate::TransactionContextBuilder; -use crate::executor::CodeExecutor; /// Tests the following properties: /// - Insertion into an empty map. @@ -361,69 +359,6 @@ fn set_update_get_random_entries() -> anyhow::Result<()> { execute_link_map_test(test_operations) } -// COMPARISON OPERATIONS TESTS -// ================================================================================================ - -#[test] -fn is_key_greater() -> anyhow::Result<()> { - execute_comparison_test(Ordering::Greater) -} - -#[test] -fn is_key_less() -> anyhow::Result<()> { - execute_comparison_test(Ordering::Less) -} - -fn execute_comparison_test(operation: Ordering) -> anyhow::Result<()> { - let procedure_name = match operation { - Ordering::Less => "is_key_less", - Ordering::Equal => anyhow::bail!("unsupported ordering operation for testing"), - Ordering::Greater => "is_key_greater", - }; - - let mut test_code = String::new(); - - for _ in 0..1000 { - let key0 = rand_value::(); - let key1 = rand_value::(); - - let cmp = LexicographicWord::from(key0).cmp(&LexicographicWord::from(key1)); - let expected = cmp == operation; - - let code = format!( - r#" - push.{KEY_1} - push.{KEY_0} - exec.link_map::{proc_name} - push.{expected_value} - assert_eq.err="failed for procedure {proc_name} with keys {key0:?}, {key1:?}" - "#, - KEY_0 = key0, - KEY_1 = key1, - proc_name = procedure_name, - expected_value = expected as u8 - ); - - test_code.push_str(&code); - } - - let code = format!( - r#" - use.$kernel::link_map - - begin - {test_code} - end - "#, - ); - - CodeExecutor::with_default_host() - .run(&code) - .with_context(|| format!("comparison test for {procedure_name} failed"))?; - - Ok(()) -} - // TEST HELPERS // ================================================================================================ From 67a1d692c026e1b0cfcc7f3e12b56d495b9051d5 Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Thu, 18 Sep 2025 00:57:45 +0300 Subject: [PATCH 042/133] feat: add initial slot & map storage kernel procedures (#1883) --- CHANGELOG.md | 1 + .../asm/kernels/transaction/api.masm | 60 +++++++ .../asm/kernels/transaction/lib/account.masm | 161 +++++++++++++----- .../transaction/lib/account_delta.masm | 37 +--- .../asm/kernels/transaction/lib/memory.masm | 27 +++ crates/miden-lib/asm/miden/account.masm | 69 +++++++- .../asm/miden/kernel_proc_offsets.masm | 111 +++++++----- .../src/testing/mock_account_code.rs | 17 ++ .../src/transaction/kernel_procedures.rs | 12 +- .../src/kernel_tests/tx/test_account.rs | 120 +++++++++++++ .../src/kernel_tests/tx/test_fpi.rs | 100 +++++++++++ docs/src/protocol_library.md | 2 + 12 files changed, 598 insertions(+), 119 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da316d83d3..6966d0600d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Added lazy loading of the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). - Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). - [BREAKING] Move account seed into `PartialAccount` ([#1875](https://github.com/0xMiden/miden-base/pull/1875)). +- Added `get_item_init` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). ### Changes diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 92787d8251..55f43bc287 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -426,6 +426,66 @@ export.account_get_map_item # => [VALUE, pad(12)] end +#! Gets an item from the account storage at its initial state (beginning of transaction). +#! +#! Inputs: [index, pad(15)] +#! Outputs: [INIT_VALUE, pad(12)] +#! +#! Where: +#! - index is the index of the item to get. +#! - INIT_VALUE is the initial value of the item at the beginning of the transaction. +#! +#! Panics if: +#! - the index is out of bounds. +#! +#! Invocation: dynexec +export.account_get_item_init + # authenticate that the procedure invocation originates from the account context + exec.authenticate_account_origin + # => [storage_offset, storage_size, index, pad(15)] + + # apply offset to storage slot index + exec.account::apply_storage_offset + # => [index_with_offset, pad(15)] + + # fetch the initial account storage item + exec.account::get_item_init + # => [INIT_VALUE, pad(15)] + + # truncate the stack + movup.4 drop movup.4 drop movup.4 drop + # => [INIT_VALUE, pad(12)] +end + +#! Returns the initial VALUE located under the specified KEY within the map contained in the given +#! account storage slot at the beginning of the transaction. +#! +#! Inputs: [index, KEY, pad(11)] +#! Outputs: [INIT_VALUE, pad(12)] +#! +#! Where: +#! - index is the index of the storage slot that contains the map root. +#! - INIT_VALUE is the initial value of the map item at KEY at the beginning of the transaction. +#! +#! Panics if: +#! - the index is out of bounds (>255). +#! - the requested storage slot type is not map. +#! +#! Invocation: dynexec +export.account_get_map_item_init + # authenticate that the procedure invocation originates from the account context + exec.authenticate_account_origin + # => [storage_offset, storage_size, index, KEY, pad(11)] + + # apply offset to storage slot index + exec.account::apply_storage_offset + # => [index_with_offset, KEY, pad(11)] + + # fetch the initial map item from account storage + exec.account::get_map_item_init + # => [INIT_VALUE, pad(12)] +end + #! Stores NEW_VALUE under the specified KEY within the map contained in the given account storage slot. #! #! Inputs: [index, KEY, NEW_VALUE, pad(7)] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index b1cb0d44e6..4709823533 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -1,13 +1,12 @@ -use.std::collections::mmr -use.std::collections::smt -use.std::crypto::hashes::rpo -use.std::mem - +use.$kernel::account_delta use.$kernel::account_id use.$kernel::asset_vault -use.$kernel::account_delta use.$kernel::constants use.$kernel::memory +use.std::collections::mmr +use.std::collections::smt +use.std::crypto::hashes::rpo +use.std::mem # ERRORS # ================================================================================================= @@ -199,10 +198,10 @@ end #! Computes the account commitment of the current account. #! -#! Notice that there is no caching (and, hence, dirty flag) for the commitment of the entire -#! account. Assuming that the storage commitment is current, computing account commitment is +#! Notice that there is no caching (and, hence, dirty flag) for the commitment of the entire +#! account. Assuming that the storage commitment is current, computing account commitment is #! relatively cheap — essentially is consists of just 2 permutations of the hash function and takes -#! relatively small number of cycles, so it would not be worth adding a separate caching mechanism +#! relatively small number of cycles, so it would not be worth adding a separate caching mechanism #! for this. #! #! Inputs: [] @@ -466,10 +465,31 @@ export.get_item # => [acct_storage_slots_section_offset, index] # get the item from storage - swap mul.8 add padw movup.4 mem_loadw + exec.get_item_raw # => [VALUE] end +#! Gets an item from the account storage at its initial state (beginning of transaction). +#! +#! Note: +#! - We assume that index has been validated and is within bounds. +#! +#! Inputs: [index] +#! Outputs: [INIT_VALUE] +#! +#! Where: +#! - index is the index of the item to get. +#! - INIT_VALUE is the initial value of the item at the beginning of the transaction. +export.get_item_init + # get account initial storage slots section offset + exec.memory::get_account_initial_storage_slots_ptr + # => [account_initial_storage_slots_ptr, index] + + # get the item from initial storage + exec.get_item_raw + # => [INIT_VALUE] +end + #! Sets an item in the account storage. #! #! Note: @@ -529,15 +549,7 @@ end #! Panics if: #! - the requested storage slot type is not map. export.get_map_item - # get the storage slot type - dup exec.get_storage_slot_type - # => [slot_type, index, KEY] - - # check if storage slot type is map - exec.constants::get_storage_slot_type_map eq - assert.err=ERR_ACCOUNT_READING_MAP_VALUE_FROM_NON_MAP_SLOT - # => [index, KEY] - + # duplicate index for later use dup movdn.5 # => [index, KEY, index] @@ -545,23 +557,34 @@ export.get_map_item exec.get_item swapw # => [KEY, ROOT, index] - emit.ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT - # => [KEY, ROOT, index] + exec.get_map_item_raw +end - # see hash_map_key's docs for why this is done - exec.hash_map_key - # => [HASHED_KEY, ROOT, index] +#! Returns the VALUE located under the specified KEY within the map contained in the given +#! account storage slot at its initial state (beginning of transaction). +#! +#! Inputs: [index, KEY] +#! Outputs: [INIT_VALUE] +#! +#! Note: +#! - We assume that index has been validated and is within bounds. +#! +#! Where: +#! - index is the index of the storage slot that contains the map root. +#! - INIT_VALUE is the initial value of the map item at KEY at the beginning of the transaction. +#! +#! Panics if: +#! - the requested storage slot type is not map. +export.get_map_item_init + # duplicate index for later use + dup movdn.5 + # => [index, KEY, index] - # fetch the VALUE located under HASHED_KEY in the tree - exec.smt::get - # => [VALUE, ROOT, index] + # fetch the initial account storage item, which is ROOT of the map + exec.get_item_init swapw + # => [KEY, INIT_ROOT, index] - # remove the ROOT from the stack - swapw dropw - # => [VALUE, index] - - movup.4 drop - # => [VALUE] + exec.get_map_item_raw end #! Stores NEW_VALUE under the specified KEY within the map contained in the given account storage slot. @@ -859,7 +882,7 @@ end #! #! Panics if: #! - the asset is not valid. -#! - the total value of the fungible asset is greater than or equal to 2^63 after the new asset was +#! - the total value of the fungible asset is greater than or equal to 2^63 after the new asset was #! added. #! - the vault already contains the same non-fungible asset. export.add_asset_to_vault @@ -1149,9 +1172,69 @@ export.save_account_procedure_data # OS => [] end -# HELPER PROCEDURES +# HELPER PROCEDURES # ================================================================================================= +#! Gets an item from storage using the provided storage slots pointer and index. +#! +#! Note: +#! - We assume that index has been validated and is within bounds. +#! +#! Inputs: [storage_slots_ptr, index] +#! Outputs: [VALUE] +#! +#! Where: +#! - storage_slots_ptr is the pointer to the storage slots section. +#! - index is the index of the item to get. +#! - VALUE is the value of the item. +export.get_item_raw + # get the item from storage + swap mul.8 add padw movup.4 mem_loadw + # => [VALUE] +end + +#! Shared procedure for getting a map item from a storage slot. +#! +#! Inputs: [KEY, ROOT, index] +#! Outputs: [VALUE] +#! +#! Where: +#! - KEY is the key to look up in the map. +#! - ROOT is the root of the map. +#! - index is the index of the storage slot that contains the map root. +#! - VALUE is the value of the map item at KEY. +#! +#! Panics if: +#! - the requested storage slot type is not map. +proc.get_map_item_raw + # check storage slot type + dup.8 exec.get_storage_slot_type + # => [slot_type, KEY, ROOT, index] + + # check if storage slot type is map + exec.constants::get_storage_slot_type_map eq + assert.err=ERR_ACCOUNT_READING_MAP_VALUE_FROM_NON_MAP_SLOT + # => [KEY, ROOT, index] + + emit.ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT + # => [KEY, ROOT, index] + + # see hash_map_key's docs for why this is done + exec.hash_map_key + # => [HASHED_KEY, ROOT, index] + + # fetch the VALUE located under HASHED_KEY in the tree + exec.smt::get + # => [VALUE, ROOT, index] + + # remove the ROOT from the stack + swapw dropw + # => [VALUE, index] + + movup.4 drop + # => [VALUE] +end + #! Sets an item in the account storage. Doesn't emit any events. #! #! Inputs: [index, NEW_VALUE] @@ -1349,10 +1432,10 @@ end #! Makes the account storage commitment up-to-date. #! -#! Notice that the account storage commitment got updated only if it is outdated: if the account +#! Notice that the account storage commitment got updated only if it is outdated: if the account #! storage changes, its commitment will be recomputed by hashing the storage slots. Then this newly #! computed storage commitment updates the storage commitment in memory. If the storage commitment -#! is up-to-date, this procedure does nothing. +#! is up-to-date, this procedure does nothing. #! #! Inputs: [] #! Outputs: [] @@ -1364,7 +1447,7 @@ proc.refresh_storage_commitment # => [should_recompute_storage_commitment] if.true - # dirty flag being equal 1 ensures that we have at least one storage slot, so we have to + # dirty flag being equal 1 ensures that we have at least one storage slot, so we have to # hash the storage anyway # get number of storage slots @@ -1396,7 +1479,7 @@ proc.refresh_storage_commitment # => [] # update the storage commitment dirty flag, indicating that the commitment is up-to-date - push.0 + push.0 exec.memory::set_native_account_storage_commitment_dirty_flag # => [] end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm index a60f558061..cf71c242bf 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm @@ -1,9 +1,9 @@ -use.$kernel::memory -use.$kernel::link_map -use.$kernel::constants use.$kernel::account use.$kernel::asset use.$kernel::asset_vault +use.$kernel::constants +use.$kernel::link_map +use.$kernel::memory use.std::crypto::hashes::rpo use.std::math::u64 use.std::word @@ -174,7 +174,7 @@ proc.update_value_slot_delta dup exec.account::get_item # => [CURRENT_VALUE, slot_idx, RATE, RATE, PERM] - dup.4 exec.get_item_initial + dup.4 exec.account::get_item_init # => [INIT_VALUE, CURRENT_VALUE, slot_idx, RATE, RATE, PERM] exec.word::test_eq not @@ -390,7 +390,6 @@ proc.update_fungible_asset_delta.2 # => [RATE, RATE, PERM] end - #! Updates the given delta hasher with the non-fungible asset vault delta. #! #! Inputs: [RATE, RATE, PERM] @@ -443,9 +442,9 @@ proc.update_non_fungible_asset_delta.2 hperm # => [RATE, RATE, PERM] else - # discard the two key and value words loaded from the map - dropw dropw - # => [RATE, RATE, PERM] + # discard the two key and value words loaded from the map + dropw dropw + # => [RATE, RATE, PERM] end # => [RATE, RATE, PERM] @@ -462,27 +461,6 @@ proc.update_non_fungible_asset_delta.2 # => [RATE, RATE, PERM] end -#! Returns the initial value of a storage slot from the account storage. -#! -#! This is the value of the slot at the beginning of the transaction. -#! -#! If this this procedure is moved to the account, additional assertions are necessary to make it -#! safe to use. -#! -#! Note: Assumes the index is within bounds. -#! -#! Inputs: [index] -#! Outputs: [INIT_VALUE] -proc.get_item_initial - # get account storage slots section offset - exec.memory::get_native_account_initial_storage_slots_ptr - # => [account_delta_initial_storage_slots_ptr, index] - - # get the item from storage - swap mul.8 add padw movup.4 mem_loadw - # => [INIT_VALUE] -end - # DELTA BOOKKEEPING # ------------------------------------------------------------------------------------------------- @@ -681,7 +659,6 @@ export.add_non_fungible_asset # => [] end - #! Removes the given non-fungible asset from the non-fungible asset vault delta. #! #! ASSET must be a valid non-fungible asset. diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index 143a6d835e..f9709ecb97 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -1354,6 +1354,33 @@ export.get_native_account_initial_storage_slots_ptr exec.get_native_account_data_ptr add.ACCT_INITIAL_STORAGE_SLOTS_SECTION_OFFSET end +#! Returns the memory pointer to the initial storage slots of the current account. +#! +#! For the native account, this returns the pointer to the initial storage slots section. +#! For foreign accounts, this returns the regular storage slots pointer since foreign accounts +#! are read-only and their initial and current storage state always matches. +#! +#! Inputs: [] +#! Outputs: [account_initial_storage_slots_ptr] +#! +#! Where: +#! - account_initial_storage_slots_ptr is the memory pointer to the initial storage slot values. +export.get_account_initial_storage_slots_ptr + exec.is_native_account + # => [is_native_account] + + if.true + # For native account, return the initial storage slots pointer + exec.get_native_account_initial_storage_slots_ptr + # => [account_initial_storage_slots_ptr] + else + # For foreign account, return the regular storage slots pointer since + # foreign accounts are read-only and initial == current + exec.get_account_storage_slots_section_ptr + # => [account_initial_storage_slots_ptr] + end +end + ### ACCOUNT DELTA ################################################# #! Returns the link map pointer to the fungible asset vault delta. diff --git a/crates/miden-lib/asm/miden/account.masm b/crates/miden-lib/asm/miden/account.masm index 278b859d4f..08cf7e6e4b 100644 --- a/crates/miden-lib/asm/miden/account.masm +++ b/crates/miden-lib/asm/miden/account.masm @@ -64,7 +64,7 @@ end #! Returns the nonce of the current account. #! #! This procedure always returns the initial account nonce, as the nonce can only be incremented -#! in the authentication procedure when signing the transaction after all user code has been +#! in the authentication procedure when signing the transaction after all user code has been #! executed. #! #! Inputs: [] @@ -264,6 +264,38 @@ export.get_item # => [VALUE] end +#! Gets the initial item from the account storage slot as it was at the beginning of the transaction. +#! +#! Inputs: [index] +#! Outputs: [INIT_VALUE] +#! +#! Where: +#! - index is the index of the item to get. +#! - INIT_VALUE is the initial value of the item at the beginning of the transaction. +#! +#! Panics if: +#! - the index of the requested item is out of bounds. +#! +#! Invocation: exec +export.get_item_init + push.0.0 movup.2 + # => [index, 0, 0] + + exec.kernel_proc_offsets::account_get_item_init_offset + # => [offset, index, 0, 0] + + # pad the stack + padw swapw padw padw swapdw + # => [offset, index, pad(14)] + + syscall.exec_kernel_proc + # => [INIT_VALUE, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [INIT_VALUE] +end + #! Sets an item in the account storage. Panics if the index is out of bounds. #! #! Inputs: [index, VALUE] @@ -358,9 +390,40 @@ export.set_map_item # => [OLD_MAP_ROOT, OLD_MAP_VALUE] end +#! Gets the initial VALUE from the account storage map as it was at the beginning of the transaction. +#! +#! Inputs: [index, KEY] +#! Outputs: [INIT_VALUE] +#! +#! Where: +#! - index is the index of the map where the KEY VALUE should be read. +#! - KEY is the key of the item to get. +#! - INIT_VALUE is the initial value of the item at the beginning of the transaction. +#! +#! Panics if: +#! - the index for the map is out of bounds, meaning > 255. +#! - the slot item at index is not a map. +#! +#! Invocation: exec +export.get_map_item_init + exec.kernel_proc_offsets::account_get_map_item_init_offset + # => [offset, index, KEY] + + # pad the stack + push.0.0 movdn.7 movdn.7 padw padw swapdw + # => [offset, index, KEY, pad(10)] + + syscall.exec_kernel_proc + # => [INIT_VALUE, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [INIT_VALUE] +end + #! Gets the account code commitment of the current account. #! -#! Notice that the account code cannot be changed during the user code execution, so the code +#! Notice that the account code cannot be changed during the user code execution, so the code #! commitment doesn't change during transaction execution: commitment returned by this procedure #! could be used as both the initial and the current. #! @@ -414,7 +477,7 @@ end #! Computes the latest account storage commitment of the current account. #! -#! Notice that this procedure always returns the latest commitment, but it doesn't actually always +#! Notice that this procedure always returns the latest commitment, but it doesn't actually always #! recompute it: recomputation is performed only if the account's storage has been changed, #! otherwise the cached value is returned. #! diff --git a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm index 3f3eb390a9..5f1522c2aa 100644 --- a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm +++ b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm @@ -23,70 +23,71 @@ const.ACCOUNT_GET_CODE_COMMITMENT_OFFSET=7 const.ACCOUNT_GET_INITIAL_STORAGE_COMMITMENT_OFFSET=8 const.ACCOUNT_COMPUTE_STORAGE_COMMITMENT_OFFSET=9 const.ACCOUNT_GET_ITEM_OFFSET=10 -const.ACCOUNT_SET_ITEM_OFFSET=11 -const.ACCOUNT_GET_MAP_ITEM_OFFSET=12 -const.ACCOUNT_SET_MAP_ITEM_OFFSET=13 +const.ACCOUNT_GET_ITEM_INIT_OFFSET=11 +const.ACCOUNT_SET_ITEM_OFFSET=12 +const.ACCOUNT_GET_MAP_ITEM_OFFSET=13 +const.ACCOUNT_GET_MAP_ITEM_INIT_OFFSET=14 +const.ACCOUNT_SET_MAP_ITEM_OFFSET=15 # Vault -const.ACCOUNT_GET_INITIAL_VAULT_ROOT_OFFSET=14 -const.ACCOUNT_GET_VAULT_ROOT_OFFSET=15 -const.ACCOUNT_ADD_ASSET_OFFSET=16 -const.ACCOUNT_REMOVE_ASSET_OFFSET=17 -const.ACCOUNT_GET_BALANCE_OFFSET=18 -const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=19 +const.ACCOUNT_GET_INITIAL_VAULT_ROOT_OFFSET=16 +const.ACCOUNT_GET_VAULT_ROOT_OFFSET=17 +const.ACCOUNT_ADD_ASSET_OFFSET=18 +const.ACCOUNT_REMOVE_ASSET_OFFSET=19 +const.ACCOUNT_GET_BALANCE_OFFSET=20 +const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=21 # Delta -const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=20 +const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=22 # Procedure introspection -const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=21 +const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=23 ### Faucet ###################################### - -const.FAUCET_MINT_ASSET_OFFSET=22 -const.FAUCET_BURN_ASSET_OFFSET=23 -const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=24 -const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=25 +const.FAUCET_MINT_ASSET_OFFSET=24 +const.FAUCET_BURN_ASSET_OFFSET=25 +const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=26 +const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=27 ### Note ######################################## # input notes -const.INPUT_NOTE_GET_METADATA_OFFSET=26 -const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=27 -const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=28 -const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=29 -const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=30 -const.INPUT_NOTE_GET_RECIPIENT_OFFSET=31 +const.INPUT_NOTE_GET_METADATA_OFFSET=28 +const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=29 +const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=30 +const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=31 +const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=32 +const.INPUT_NOTE_GET_RECIPIENT_OFFSET=33 # output notes -const.OUTPUT_NOTE_CREATE_OFFSET=32 -const.OUTPUT_NOTE_GET_METADATA_OFFSET=33 -const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=34 -const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=35 -const.OUTPUT_NOTE_ADD_ASSET_OFFSET=36 +const.OUTPUT_NOTE_CREATE_OFFSET=34 +const.OUTPUT_NOTE_GET_METADATA_OFFSET=35 +const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=36 +const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=37 +const.OUTPUT_NOTE_ADD_ASSET_OFFSET=38 ### Tx ########################################## # input notes -const.TX_GET_NUM_INPUT_NOTES_OFFSET=37 -const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=38 +const.TX_GET_NUM_INPUT_NOTES_OFFSET=39 +const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=40 # output notes -const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=39 -const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=40 +const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=41 +const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=42 # block info -const.TX_GET_BLOCK_COMMITMENT_OFFSET=41 -const.TX_GET_BLOCK_NUMBER_OFFSET=42 -const.TX_GET_BLOCK_TIMESTAMP_OFFSET=43 +const.TX_GET_BLOCK_COMMITMENT_OFFSET=43 +const.TX_GET_BLOCK_NUMBER_OFFSET=44 +const.TX_GET_BLOCK_TIMESTAMP_OFFSET=45 # foreign context -const.TX_START_FOREIGN_CONTEXT_OFFSET=44 -const.TX_END_FOREIGN_CONTEXT_OFFSET=45 +const.TX_START_FOREIGN_CONTEXT_OFFSET=46 +const.TX_END_FOREIGN_CONTEXT_OFFSET=47 # expiration data -const.TX_GET_EXPIRATION_DELTA_OFFSET=46 # accessor -const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=47 # mutator +const.TX_GET_EXPIRATION_DELTA_OFFSET=48 # accessor +const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=49 # mutator # ACCESSORS # ------------------------------------------------------------------------------------------------- @@ -99,7 +100,7 @@ const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=47 # mutator #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `account_get_initial_commitment` kernel procedure required to +#! - proc_offset is the offset of the `account_get_initial_commitment` kernel procedure required to #! get the address where this procedure is stored. export.account_get_initial_commitment_offset push.ACCOUNT_GET_INITIAL_COMMITMENT_OFFSET @@ -123,7 +124,7 @@ end #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `account_compute_delta_commitment` kernel procedure required +#! - proc_offset is the offset of the `account_compute_delta_commitment` kernel procedure required #! to get the address where this procedure is stored. export.account_compute_delta_commitment_offset push.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET @@ -147,7 +148,7 @@ end #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `account_get_native_id` kernel procedure required to get the +#! - proc_offset is the offset of the `account_get_native_id` kernel procedure required to get the #! address where this procedure is stored. export.account_get_native_id_offset push.ACCOUNT_GET_NATIVE_ID_OFFSET @@ -207,7 +208,7 @@ end #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `account_get_initial_storage_commitment` kernel procedure +#! - proc_offset is the offset of the `account_get_initial_storage_commitment` kernel procedure #! required to get the address where this procedure is stored. export.account_get_initial_storage_commitment_offset push.ACCOUNT_GET_INITIAL_STORAGE_COMMITMENT_OFFSET @@ -273,6 +274,30 @@ export.account_set_map_item_offset push.ACCOUNT_SET_MAP_ITEM_OFFSET end +#! Returns the offset of the `account_get_item_init` kernel procedure. +#! +#! Inputs: [] +#! Outputs: [proc_offset] +#! +#! Where: +#! - proc_offset is the offset of the `account_get_item_init` kernel procedure required to get the +#! address where this procedure is stored. +export.account_get_item_init_offset + push.ACCOUNT_GET_ITEM_INIT_OFFSET +end + +#! Returns the offset of the `account_get_map_item_init` kernel procedure. +#! +#! Inputs: [] +#! Outputs: [proc_offset] +#! +#! Where: +#! - proc_offset is the offset of the `account_get_map_item_init` kernel procedure required to get the +#! address where this procedure is stored. +export.account_get_map_item_init_offset + push.ACCOUNT_GET_MAP_ITEM_INIT_OFFSET +end + #! Returns the offset of the `account_get_initial_vault_root` kernel procedure. #! #! Inputs: [] @@ -513,7 +538,7 @@ end #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `input_note_get_serial_number` kernel procedure required to +#! - proc_offset is the offset of the `input_note_get_serial_number` kernel procedure required to #! get the address where this procedure is stored. export.input_note_get_serial_number_offset push.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET diff --git a/crates/miden-lib/src/testing/mock_account_code.rs b/crates/miden-lib/src/testing/mock_account_code.rs index f3a04e1ba7..ff78405970 100644 --- a/crates/miden-lib/src/testing/mock_account_code.rs +++ b/crates/miden-lib/src/testing/mock_account_code.rs @@ -52,6 +52,17 @@ const MOCK_ACCOUNT_CODE: &str = " # => [VALUE, pad(12)] end + # Stack: [index, pad(15)] + # Output: [VALUE, pad(12)] + export.get_item_init + exec.account::get_item_init + # => [VALUE, pad(15)] + + # truncate the stack + movup.8 drop movup.8 drop movup.8 drop + # => [VALUE, pad(12)] + end + # Stack: [index, KEY, VALUE, pad(7)] # Output: [OLD_MAP_ROOT, OLD_MAP_VALUE, pad(8)] export.set_map_item @@ -65,6 +76,12 @@ const MOCK_ACCOUNT_CODE: &str = " exec.account::get_map_item end + # Stack: [index, KEY, pad(11)] + # Output: [VALUE, pad(12)] + export.get_map_item_init + exec.account::get_map_item_init + end + # Stack: [pad(16)] # Output: [CODE_COMMITMENT, pad(12)] export.get_code_commitment diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index bf0746f11d..90bfd4d6bd 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -6,7 +6,7 @@ use miden_objects::{Word, word}; // ================================================================================================ /// Hashes of all dynamically executed kernel procedures. -pub const KERNEL_PROCEDURES: [Word; 48] = [ +pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_initial_commitment word!("0x920898348bacd6d98a399301eb308478fd32b32eab019a5a6ef7a6b44abb61f6"), // account_compute_current_commitment @@ -29,10 +29,14 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ word!("0x2e508f8505188ce9b6c46be727e2c1a237806a19402486e38105665b87191526"), // account_get_item word!("0x011e508cf9b261c33e5f3da1afbf23caefa1f6bc7eac9de2cd123c77fa74f02a"), + // account_get_item_init + word!("0x46948d2c64c5b8979cbf1d628a90459b54b41491db5e0f1cff8747c9901da165"), // account_set_item word!("0xd2232daa3895669f2bb34af764504d72432fb119eb0be0ce07481290c8701af8"), // account_get_map_item - word!("0x061afed82416f597aa87e2de48d7dad96a40c5f3e2c839d6522bafd3646414ca"), + word!("0x95449dd3a32ca3e069acc132e8f44fa87679e2f373f4ca7ae1807246802c5d0d"), + // account_get_map_item_init + word!("0x2e84c009a58b5fda1547865090ac446294d4db20d0aefef3d9e4c6a1a93df8fd"), // account_set_map_item word!("0x33309593aa405279a27907cee07b0cde54dbf4c088d0995a05f61e60236cfb0f"), // account_get_initial_vault_root @@ -48,7 +52,7 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ // account_has_non_fungible_asset word!("0x62776e8641d241f404724cf115c416e4918b75ced3625a5cd21d487fd7aef68b"), // account_compute_delta_commitment - word!("0x01cf0da392179c475c4ffdca0f0114932e09b75189433969debe1895881bf8b0"), + word!("0xb4589587f804af8205f9179ec6b58814d78171a3dc6d78cbf470db512ec25129"), // account_was_procedure_called word!("0x84c8c518a005605619909976ce54c41d6a88505e815421ff4b5516d0285b28bf"), // faucet_mint_asset @@ -58,7 +62,7 @@ pub const KERNEL_PROCEDURES: [Word; 48] = [ // faucet_get_total_fungible_asset_issuance word!("0x7d32952d4dc0edd0311e3424b8128df2d48cf949f800c28218fbc851a8db42b5"), // faucet_is_non_fungible_asset_issued - word!("0xe0d7571e530b703ededf549f5ca8c57c5cffe0d1d9f59da20e5db05ca18de58b"), + word!("0xfe8db6e0903ad0d6a368cedd197f756ade6c17d275d131fc8e1a07f9cb96875e"), // input_note_get_metadata word!("0x7ad3e94585e7a397ee27443c98b376ed8d4ba762122af6413fde9314c00a6219"), // input_note_get_assets_info diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 00180072e3..6f44b946a5 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -1261,6 +1261,126 @@ fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { Ok(()) } +// ACCOUNT INITIAL STORAGE TESTS +// ================================================================================================ + +#[test] +fn test_get_item_init() -> miette::Result<()> { + let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); + + // Test that get_item_init returns the initial value before any changes + let code = format!( + " + use.$kernel::account + use.$kernel::prologue + use.mock::account->mock_account + + begin + exec.prologue::prepare_transaction + + # get initial value of storage slot 0 + push.0 + exec.account::get_item_init + + push.{expected_initial_value} + assert_eqw.err=\"initial value should match expected\" + + # modify the storage slot + push.9.10.11.12.0 + call.mock_account::set_item dropw drop + + # get_item should return the new value + push.0 + exec.account::get_item + push.9.10.11.12 + assert_eqw.err=\"current value should be updated\" + + # get_item_init should still return the initial value + push.0 + exec.account::get_item_init + push.{expected_initial_value} + assert_eqw.err=\"initial value should remain unchanged\" + end + ", + expected_initial_value = &AccountStorage::mock_item_0().slot.value(), + ); + + tx_context.execute_code(&code).unwrap(); + + Ok(()) +} + +#[test] +fn test_get_map_item_init() -> miette::Result<()> { + let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(MockAccountComponent::with_slots(vec![AccountStorage::mock_item_2().slot])) + .build_existing() + .unwrap(); + + let tx_context = TransactionContextBuilder::new(account).build().unwrap(); + + // Use the first key-value pair from the mock storage + let (initial_key, initial_value) = STORAGE_LEAVES_2[0]; + let new_key = Word::from([201, 202, 203, 204u32]); + let new_value = Word::from([301, 302, 303, 304u32]); + + let code = format!( + " + use.$kernel::prologue + use.mock::account->mock_account + + begin + exec.prologue::prepare_transaction + + # get initial value from map + push.{initial_key} + push.0 + call.mock_account::get_map_item_init + push.{initial_value} + assert_eqw.err=\"initial map value should match expected\" + + # add a new key-value pair to the map + push.{new_value} + push.{new_key} + push.0 + call.mock_account::set_map_item dropw dropw + + # get_map_item should return the new value + push.{new_key} + push.0 + call.mock_account::get_map_item + push.{new_value} + assert_eqw.err=\"current map value should be updated\" + + # get_map_item_init should still return the initial value for the initial key + push.{initial_key} + push.0 + call.mock_account::get_map_item_init + push.{initial_value} + assert_eqw.err=\"initial map value should remain unchanged\" + + # get_map_item_init for the new key should return empty word (default) + push.{new_key} + push.0 + call.mock_account::get_map_item_init + push.0.0.0.0 + assert_eqw.err=\"new key should have empty initial value\" + + dropw dropw + end + ", + initial_key = &initial_key, + initial_value = &initial_value, + new_key = &new_key, + new_value = &new_value, + ); + + tx_context.execute_code(&code).unwrap(); + + Ok(()) +} + /// Tests that incrementing the account nonce fails if it would overflow the field. #[test] fn incrementing_nonce_overflow_fails() -> anyhow::Result<()> { diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index 121cc0a48f..1ae29b4e5e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -1552,3 +1552,103 @@ fn foreign_account_data_memory_assertions(foreign_account: &Account, process: &P ); } } + +/// Test that get_item_init and get_map_item_init work correctly with foreign accounts. +#[test] +fn test_get_item_init_and_get_map_item_init_with_foreign_account() -> anyhow::Result<()> { + // Create a native account + let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(MockAccountComponent::with_empty_slots()) + .storage_mode(AccountStorageMode::Public) + .build_existing()?; + + let (map_key, map_value) = STORAGE_LEAVES_2[0]; + + // Create foreign procedures that test get_item_init and get_map_item_init + let foreign_account_code_source = " + use.miden::account + use.std::sys + + + export.test_get_item_init + push.0 + exec.account::get_item_init + exec.sys::truncate_stack + end + + export.test_get_map_item_init + exec.account::get_map_item_init + exec.sys::truncate_stack + end + "; + + let foreign_account_component = AccountComponent::compile( + NamedSource::new("foreign_account", foreign_account_code_source), + TransactionKernel::assembler(), + vec![AccountStorage::mock_item_0().slot, AccountStorage::mock_item_2().slot], + )? + .with_supports_all_types(); + + let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(foreign_account_component.clone()) + .build_existing()?; + + // Create the mock chain with both accounts + let mut mock_chain = + MockChainBuilder::with_accounts([native_account.clone(), foreign_account.clone()])? + .build()?; + mock_chain.prove_next_block()?; + + let foreign_account_inputs = mock_chain.get_foreign_account_inputs(foreign_account.id())?; + + let code = format!( + " + use.std::sys + use.miden::tx + + begin + + # Test get_item_init on foreign account + padw padw padw push.0.0 + push.0 + procref.::foreign_account::test_get_item_init + push.{foreign_account_id_suffix}.{foreign_account_id_prefix} + exec.tx::execute_foreign_procedure + push.{expected_value_slot_0} + assert_eqw.err=\"foreign account get_item_init should work\" + + # Test get_map_item_init on foreign account + padw padw push.0.0 + push.{map_key} + push.1 + procref.::foreign_account::test_get_map_item_init + push.{foreign_account_id_suffix}.{foreign_account_id_prefix} + exec.tx::execute_foreign_procedure + push.{map_value} + assert_eqw.err=\"foreign account get_map_item_init should work\" + + exec.sys::truncate_stack + end + ", + foreign_account_id_prefix = foreign_account.id().prefix().as_felt(), + foreign_account_id_suffix = foreign_account.id().suffix(), + expected_value_slot_0 = &AccountStorage::mock_item_0().slot.value(), + map_key = &map_key, + map_value = &map_value, + ); + + let tx_script = ScriptBuilder::with_mock_libraries()? + .with_dynamically_linked_library(foreign_account_component.library())? + .compile_tx_script(code)?; + + mock_chain + .build_tx_context(native_account.id(), &[], &[])? + .foreign_accounts(vec![foreign_account_inputs]) + .tx_script(tx_script) + .build()? + .execute_blocking()?; + + Ok(()) +} diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index 6348acfb85..28fa3a0e67 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -36,8 +36,10 @@ Account procedures can be used to read and write to account storage, add or remo | `compute_current_commitment` | Computes and returns the account commitment from account data stored in memory.

Inputs: `[]`
Outputs: `[ACCOUNT_COMMITMENT]` | Any | | `compute_delta_commitment` | Computes the commitment to the native account's delta. Can only be called from auth procedures.

Inputs: `[]`
Outputs: `[DELTA_COMMITMENT]` | Auth | | `get_item` | Gets an item from the account storage.

Inputs: `[index]`
Outputs: `[VALUE]` | Account | +| `get_item_init` | Gets the initial item from the account storage slot as it was at the beginning of the transaction.

Inputs: `[index]`
Outputs: `[VALUE]` | Account | | `set_item` | Sets an item in the account storage.

Inputs: `[index, VALUE]`
Outputs: `[OLD_VALUE]` | Native & Account | | `get_map_item` | Returns the VALUE located under the specified KEY within the map contained in the given account storage slot.

Inputs: `[index, KEY]`
Outputs: `[VALUE]` | Account | +| `get_map_item_init` | Gets the initial VALUE from the account storage map as it was at the beginning of the transaction.

Inputs: `[index, KEY]`
Outputs: `[VALUE]` | Account | | `set_map_item` | Sets VALUE under the specified KEY within the map contained in the given account storage slot.

Inputs: `[index, KEY, VALUE]`
Outputs: `[OLD_MAP_ROOT, OLD_MAP_VALUE]` | Native & Account | | `get_code_commitment` | Gets the account code commitment of the current account.

Inputs: `[]`
Outputs: `[CODE_COMMITMENT]` | Account | | `get_initial_storage_commitment` | Returns the storage commitment of the native account at the beginning of the transaction.

Inputs: `[]`
Outputs: `[INIT_STORAGE_COMMITMENT]` | Any | From 084ef043e8ad8f6d9ddafeb4e750227a1b91c52d Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Thu, 18 Sep 2025 08:42:00 +0300 Subject: [PATCH 043/133] refactor: split `miden::note` into two modules (#1901) --- CHANGELOG.md | 1 + crates/miden-lib/asm/miden/active_note.masm | 367 +++++++++++++ crates/miden-lib/asm/miden/note.masm | 357 +------------ crates/miden-lib/asm/note_scripts/P2ID.masm | 6 +- crates/miden-lib/asm/note_scripts/P2IDE.masm | 10 +- crates/miden-lib/asm/note_scripts/SWAP.masm | 6 +- .../miden-testing/src/kernel_tests/tx/mod.rs | 1 + .../src/kernel_tests/tx/test_active_note.rs | 494 ++++++++++++++++++ .../src/kernel_tests/tx/test_note.rs | 474 +---------------- crates/miden-testing/src/utils.rs | 4 +- crates/miden-testing/tests/scripts/faucet.rs | 2 +- docs/src/protocol_library.md | 21 +- docs/src/transaction.md | 2 +- 13 files changed, 898 insertions(+), 847 deletions(-) create mode 100644 crates/miden-lib/asm/miden/active_note.masm create mode 100644 crates/miden-testing/src/kernel_tests/tx/test_active_note.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef0b4cd2d..0d324aa5b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - [BREAKING] Move `TransactionKernelError` to miden-tx ([#1859](https://github.com/0xMiden/miden-base/pull/1859)). - Change terminology of "current note" to "active note" ([#1863](https://github.com/0xMiden/miden-base/issues/1863)). - [BREAKING] Move and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). +- [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). - Replace `eqw` usages with `exec.word::test_eq` and `exec.word::eq`, remove `is_key_greater` and `is_key_less` from `link_map` module ([#1897](https://github.com/0xMiden/miden-base/pull/1897)). ## 0.11.4 (2025-09-17) diff --git a/crates/miden-lib/asm/miden/active_note.masm b/crates/miden-lib/asm/miden/active_note.masm new file mode 100644 index 0000000000..3aed5dca4c --- /dev/null +++ b/crates/miden-lib/asm/miden/active_note.masm @@ -0,0 +1,367 @@ +use.std::mem + +use.miden::kernel_proc_offsets +use.miden::note +use.miden::account_id +use.miden::contracts::wallets::basic->wallet + +# ERRORS +# ================================================================================================= + +const.ERR_NOTE_DATA_DOES_NOT_MATCH_COMMITMENT="note data does not match the commitment" + +const.ERR_NOTE_INVALID_NUMBER_OF_INPUTS="the specified number of note inputs does not match the actual number" + +# ACTIVE NOTE PROCEDURES +# ================================================================================================= +# +# By the "active note" notion here we assume the note which is currently being processed by the +# transaction kernel. + +#! Writes the assets of the active note into memory starting at the specified address. +#! +#! Inputs: [dest_ptr] +#! Outputs: [num_assets, dest_ptr] +#! +#! Where: +#! - dest_ptr is the memory address to write the assets. +#! - num_assets is the number of assets in the active note. +#! +#! Panics if: +#! - no note is currently active. +#! +#! Invocation: exec +export.get_assets + # pad the stack + padw padw padw push.0.0 + # => [pad(14), dest_ptr] + + # push the flag indicating that we want to request assets info from the active note + push.1 + # => [is_active_note = 1, pad(14), dest_ptr] + + exec.kernel_proc_offsets::input_note_get_assets_info_offset + # => [offset, is_active_note = 1, pad(14), dest_ptr] + + syscall.exec_kernel_proc + # => [ASSETS_COMMITMENT, num_assets, pad(11), dest_ptr] + + # clean the stack + swapdw dropw dropw movup.7 movup.7 movup.7 drop drop drop + # => [ASSETS_COMMITMENT, num_assets, dest_ptr] + + # write the assets from the advice map into memory + exec.note::write_assets_to_memory + # => [num_assets, dest_ptr] +end + +#! Returns the recipient of the active note. +#! +#! Inputs: [] +#! Outputs: [RECIPIENT] +#! +#! Where: +#! - RECIPIENT is the commitment to the active note's script, inputs, the serial number. +#! +#! Panics if: +#! - no note is currently active. +#! +#! Invocation: exec +export.get_recipient + # pad the stack + padw padw padw push.0.0 + # => [pad(14)] + + # push the flag indicating that we want to request recipient from the active note + push.1 + # => [is_active_note = 1, pad(14)] + + exec.kernel_proc_offsets::input_note_get_recipient_offset + # => [offset, is_active_note = 1, pad(14)] + + syscall.exec_kernel_proc + # => [RECIPIENT, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [RECIPIENT] +end + +#! Writes the active note's inputs to memory starting at the specified address. +#! +#! Inputs: +#! Stack: [dest_ptr] +#! Advice Map: { NOTE_INPUTS_COMMITMENT: [INPUTS] } +#! Outputs: +#! Stack: [num_inputs, dest_ptr] +#! +#! Where: +#! - dest_ptr is the memory address to write the note inputs. +#! - NOTE_INPUTS_COMMITMENT is the sequential hash of the padded note's inputs. +#! - INPUTS is the data corresponding to the note's inputs. +#! +#! Panics if: +#! - no note is currently active. +#! +#! Invocation: exec +export.get_inputs + # pad the stack + padw padw padw push.0.0 + # => [pad(14), dest_ptr] + + # push the flag indicating that we want to request inputs info from the active note + push.1 + # => [is_active_note = 1, pad(14), dest_ptr] + + exec.kernel_proc_offsets::input_note_get_inputs_info_offset + # => [offset, is_active_note = 1, pad(14), dest_ptr] + + syscall.exec_kernel_proc + # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11), dest_ptr] + + # clean the stack + swapdw dropw dropw + movup.5 drop movup.5 drop movup.5 drop + # => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + + # write the inputs to the memory using the provided destination pointer + exec.write_inputs_to_memory + # => [num_inputs, dest_ptr] +end + +#! Returns the sender of the active note. +#! +#! Inputs: [] +#! Outputs: [sender_id_prefix, sender_id_suffix] +#! +#! Where: +#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender of the active note. +#! +#! Panics if: +#! - no note is currently active. +#! +#! Invocation: exec +export.get_sender + # pad the stack + padw padw padw push.0.0 + # => [pad(14)] + + # push the flag indicating that we want to request metadata from the active note + push.1 + # => [is_active_note = 1, pad(14)] + + exec.kernel_proc_offsets::input_note_get_metadata_offset + # => [offset, is_active_note = 1, pad(14)] + + syscall.exec_kernel_proc + # => [METADATA, pad(12)] + + # extract the sender ID from the metadata word + exec.extract_sender_from_metadata + # => [sender_id_prefix, sender_id_suffix, pad(12)] + + # clean the stack + swapw dropw swapw dropw movdn.5 movdn.5 dropw + # => [sender_id_prefix, sender_id_suffix] +end + +#! Returns the serial number of the active note. +#! +#! Inputs: [] +#! Outputs: [SERIAL_NUMBER] +#! +#! Where: +#! - SERIAL_NUMBER is the serial number of the active note. +#! +#! Panics if: +#! - no note is currently active. +#! +#! Invocation: exec +export.get_serial_number + # pad the stack + padw padw padw push.0.0 + # => [pad(14)] + + # push the flag indicating that we want to request serial number from the active note + push.1 + # => [is_active_note = 1, pad(14)] + + exec.kernel_proc_offsets::input_note_get_serial_number_offset + # => [offset, is_active_note = 1, pad(14)] + + syscall.exec_kernel_proc + # => [SERIAL_NUMBER, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [SERIAL_NUMBER] +end + +#! Returns the script root of the active note. +#! +#! Inputs: [] +#! Outputs: [SCRIPT_ROOT] +#! +#! Where: +#! - SCRIPT_ROOT is the script root of the active note. +#! +#! Panics if: +#! - no note is currently active. +#! +#! Invocation: exec +export.get_script_root + # pad the stack + padw padw padw push.0.0 + # => [pad(14)] + + # push the flag indicating that we want to request script root from the active note + push.1 + # => [is_active_note = 1, pad(14)] + + exec.kernel_proc_offsets::input_note_get_script_root_offset + # => [offset, is_active_note = 1, pad(14)] + + syscall.exec_kernel_proc + # => [SCRIPT_ROOT, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [SCRIPT_ROOT] +end + +#! Adds all assets from the active note to the native account's vault. +#! +#! Inputs: [] +#! Outputs: [] +export.add_assets_to_account.1024 + # write assets to local memory starting at offset 0 + # we have allocated 4 * MAX_ASSETS_PER_NOTE number of locals so all assets should fit + # since the asset memory will be overwritten, we don't have to initialize the locals to zero + locaddr.0 exec.get_assets + # => [num_of_assets, ptr = 0] + + # compute the pointer at which we should stop iterating + mul.4 dup.1 add + # => [end_ptr, ptr] + + # pad the stack and move the pointer to the top + padw movup.5 + # => [ptr, EMPTY_WORD, end_ptr] + + # loop if the amount of assets is non-zero + dup dup.6 neq + # => [should_loop, ptr, EMPTY_WORD, end_ptr] + + while.true + # => [ptr, EMPTY_WORD, end_ptr] + + # save the pointer so that we can use it later + dup movdn.5 + # => [ptr, EMPTY_WORD, ptr, end_ptr] + + # load the asset + mem_loadw + # => [ASSET, ptr, end_ptr] + + # pad the stack before call + padw swapw padw padw swapdw + # => [ASSET, pad(12), ptr, end_ptr] + + # add asset to the account + call.wallet::receive_asset + # => [pad(16), ptr, end_ptr] + + # clean the stack after call + dropw dropw dropw + # => [EMPTY_WORD, ptr, end_ptr] + + # increment the pointer and continue looping if ptr != end_ptr + movup.4 add.4 dup dup.6 neq + # => [should_loop, ptr+4, EMPTY_WORD, end_ptr] + end + # => [ptr', EMPTY_WORD, end_ptr] + + # clear the stack + drop dropw drop + # => [] +end + +# HELPER PROCEDURES +# ================================================================================================= + +#! Writes the note inputs stored in the advice map to the memory specified by the provided +#! destination pointer. +#! +#! Inputs: +#! Operand stack: [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] +#! Advice map: { +#! NOTE_INPUTS_COMMITMENT: [[INPUT_VALUES]] +#! } +#! Outputs: +#! Operand stack: [num_inputs, dest_ptr] +proc.write_inputs_to_memory + # load the inputs from the advice map to the advice stack + adv.push_mapvaln + # OS => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [advice_num_inputs, [INPUT_VALUES]] + + # move the number of inputs obtained from advice map to the operand stack + adv_push.1 dup.5 + # OS => [num_inputs, advice_num_inputs, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + # Validate the note inputs length. Round up the number of inputs to the next multiple of 8: that + # value should be equal to the length obtained from the `adv.push_mapvaln` procedure. + u32divmod.8 neq.0 add mul.8 + # OS => [rounded_up_num_inputs, advice_num_inputs, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + assert_eq.err=ERR_NOTE_INVALID_NUMBER_OF_INPUTS + # OS => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + # calculate the number of words required to store the inputs + dup.4 u32divmod.4 neq.0 add + # OS => [num_words, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + # round up the number of words to the next multiple of 2 + dup is_odd add + # OS => [even_num_words, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + # prepare the stack for the `pipe_preimage_to_memory` procedure + dup.6 swap + # OS => [even_num_words, dest_ptr, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # AS => [[INPUT_VALUES]] + + # write the inputs from the advice stack into memory + exec.mem::pipe_preimage_to_memory drop + # OS => [num_inputs, dest_ptr] + # AS => [] +end + +#! Extracts the sender ID from the provided metadata word. +#! +#! Inputs: [METADATA] +#! Outputs: [sender_id_prefix, sender_id_suffix] +#! +#! Where: +#! - METADATA is the metadata of some note. +#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender ID of the note which +#! metadata was provided. +proc.extract_sender_from_metadata + # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_id_prefix] + + # drop aux felt and the felt containing tag, execution hint and payload + drop drop + # => [merged_sender_id_type_hint_tag, sender_id_prefix] + + # extract suffix of sender from merged layout, which means clearing the least significant byte + exec.account_id::shape_suffix + # => [sender_id_suffix, sender_id_prefix] + + # rearrange suffix and prefix + swap + # => [sender_id_prefix, sender_id_suffix] +end diff --git a/crates/miden-lib/asm/miden/note.masm b/crates/miden-lib/asm/miden/note.masm index c276226570..b606c924b3 100644 --- a/crates/miden-lib/asm/miden/note.masm +++ b/crates/miden-lib/asm/miden/note.masm @@ -1,232 +1,14 @@ -use.miden::kernel_proc_offsets use.std::crypto::hashes::rpo use.std::mem -use.miden::contracts::wallets::basic->wallet -use.miden::account_id # ERRORS # ================================================================================================= -const.ERR_NOTE_DATA_DOES_NOT_MATCH_COMMITMENT="note data does not match the commitment" - const.ERR_PROLOGUE_NOTE_INPUTS_LEN_EXCEEDED_LIMIT="number of note inputs exceeded the maximum limit of 128" -const.ERR_NOTE_INVALID_NUMBER_OF_INPUTS="the specified number of note inputs does not match the actual number" - -# PROCEDURES +# NOTE UTILITY PROCEDURES # ================================================================================================= -#! Writes the assets of the active note into memory starting at the specified address. -#! -#! Inputs: [dest_ptr] -#! Outputs: [num_assets, dest_ptr] -#! -#! Where: -#! - dest_ptr is the memory address to write the assets. -#! - num_assets is the number of assets in the active note. -#! -#! Panics if: -#! - no note is currently active. -#! -#! Invocation: exec -export.get_assets - # pad the stack - padw padw padw push.0.0 - # => [pad(14), dest_ptr] - - # push the flag indicating that we want to request assets info from the active note - push.1 - # => [is_active_note = 1, pad(14), dest_ptr] - - exec.kernel_proc_offsets::input_note_get_assets_info_offset - # => [offset, is_active_note = 1, pad(14), dest_ptr] - - syscall.exec_kernel_proc - # => [ASSETS_COMMITMENT, num_assets, pad(11), dest_ptr] - - # clean the stack - swapdw dropw dropw movup.7 movup.7 movup.7 drop drop drop - # => [ASSETS_COMMITMENT, num_assets, dest_ptr] - - # write the assets from the advice map into memory - exec.write_assets_to_memory - # => [num_assets, dest_ptr] -end - -#! Returns the recipient of the active note. -#! -#! Inputs: [] -#! Outputs: [RECIPIENT] -#! -#! Where: -#! - RECIPIENT is the commitment to the active note's script, inputs, the serial number. -#! -#! Panics if: -#! - no note is currently active. -#! -#! Invocation: exec -export.get_recipient - # pad the stack - padw padw padw push.0.0 - # => [pad(14)] - - # push the flag indicating that we want to request recipient from the active note - push.1 - # => [is_active_note = 1, pad(14)] - - exec.kernel_proc_offsets::input_note_get_recipient_offset - # => [offset, is_active_note = 1, pad(14)] - - syscall.exec_kernel_proc - # => [RECIPIENT, pad(12)] - - # clean the stack - swapdw dropw dropw swapw dropw - # => [RECIPIENT] -end - -#! Writes the active note's inputs to memory starting at the specified address. -#! -#! Inputs: -#! Stack: [dest_ptr] -#! Advice Map: { NOTE_INPUTS_COMMITMENT: [INPUTS] } -#! Outputs: -#! Stack: [num_inputs, dest_ptr] -#! -#! Where: -#! - dest_ptr is the memory address to write the note inputs. -#! - NOTE_INPUTS_COMMITMENT is the sequential hash of the padded note's inputs. -#! - INPUTS is the data corresponding to the note's inputs. -#! -#! Panics if: -#! - no note is currently active. -#! -#! Invocation: exec -export.get_inputs - # pad the stack - padw padw padw push.0.0 - # => [pad(14), dest_ptr] - - # push the flag indicating that we want to request inputs info from the active note - push.1 - # => [is_active_note = 1, pad(14), dest_ptr] - - exec.kernel_proc_offsets::input_note_get_inputs_info_offset - # => [offset, is_active_note = 1, pad(14), dest_ptr] - - syscall.exec_kernel_proc - # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11), dest_ptr] - - # clean the stack - swapdw dropw dropw - movup.5 drop movup.5 drop movup.5 drop - # => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] - - # write the inputs to the memory using the provided destination pointer - exec.write_inputs_to_memory - # => [num_inputs, dest_ptr] -end - -#! Returns the sender of the active note. -#! -#! Inputs: [] -#! Outputs: [sender_id_prefix, sender_id_suffix] -#! -#! Where: -#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender of the active note. -#! -#! Panics if: -#! - no note is currently active. -#! -#! Invocation: exec -export.get_sender - # pad the stack - padw padw padw push.0.0 - # => [pad(14)] - - # push the flag indicating that we want to request metadata from the active note - push.1 - # => [is_active_note = 1, pad(14)] - - exec.kernel_proc_offsets::input_note_get_metadata_offset - # => [offset, is_active_note = 1, pad(14)] - - syscall.exec_kernel_proc - # => [METADATA, pad(12)] - - # extract the sender ID from the metadata word - exec.extract_sender_from_metadata - # => [sender_id_prefix, sender_id_suffix, pad(12)] - - # clean the stack - swapw dropw swapw dropw movdn.5 movdn.5 dropw - # => [sender_id_prefix, sender_id_suffix] -end - -#! Returns the serial number of the active note. -#! -#! Inputs: [] -#! Outputs: [SERIAL_NUMBER] -#! -#! Where: -#! - SERIAL_NUMBER is the serial number of the active note. -#! -#! Panics if: -#! - no note is currently active. -#! -#! Invocation: exec -export.get_serial_number - # pad the stack - padw padw padw push.0.0 - # => [pad(14)] - - # push the flag indicating that we want to request serial number from the active note - push.1 - # => [is_active_note = 1, pad(14)] - - exec.kernel_proc_offsets::input_note_get_serial_number_offset - # => [offset, is_active_note = 1, pad(14)] - - syscall.exec_kernel_proc - # => [SERIAL_NUMBER, pad(12)] - - # clean the stack - swapdw dropw dropw swapw dropw - # => [SERIAL_NUMBER] -end - -#! Returns the script root of the active note. -#! -#! Inputs: [] -#! Outputs: [SCRIPT_ROOT] -#! -#! Where: -#! - SCRIPT_ROOT is the script root of the active note. -#! -#! Panics if: -#! - no note is currently active. -#! -#! Invocation: exec -export.get_script_root - # pad the stack - padw padw padw push.0.0 - # => [pad(14)] - - # push the flag indicating that we want to request script root from the active note - push.1 - # => [is_active_note = 1, pad(14)] - - exec.kernel_proc_offsets::input_note_get_script_root_offset - # => [offset, is_active_note = 1, pad(14)] - - syscall.exec_kernel_proc - # => [SCRIPT_ROOT, pad(12)] - - # clean the stack - swapdw dropw dropw swapw dropw - # => [SCRIPT_ROOT] -end - #! Computes the commitment to the note inputs starting at the specified memory address. #! #! This procedure checks that the provided number of note inputs is within limits and then computes @@ -272,63 +54,6 @@ end #! - max_inputs_per_note is the max inputs per note. export.::miden::util::note::get_max_inputs_per_note -#! Adds all assets from the active note to the native account's vault. -#! -#! Inputs: [] -#! Outputs: [] -export.add_assets_to_account.1024 - # write assets to local memory starting at offset 0 - # we have allocated 4 * MAX_ASSETS_PER_NOTE number of locals so all assets should fit - # since the asset memory will be overwritten, we don't have to initialize the locals to zero - locaddr.0 exec.get_assets - # => [num_of_assets, ptr = 0] - - # compute the pointer at which we should stop iterating - mul.4 dup.1 add - # => [end_ptr, ptr] - - # pad the stack and move the pointer to the top - padw movup.5 - # => [ptr, EMPTY_WORD, end_ptr] - - # loop if the amount of assets is non-zero - dup dup.6 neq - # => [should_loop, ptr, EMPTY_WORD, end_ptr] - - while.true - # => [ptr, EMPTY_WORD, end_ptr] - - # save the pointer so that we can use it later - dup movdn.5 - # => [ptr, EMPTY_WORD, ptr, end_ptr] - - # load the asset - mem_loadw - # => [ASSET, ptr, end_ptr] - - # pad the stack before call - padw swapw padw padw swapdw - # => [ASSET, pad(12), ptr, end_ptr] - - # add asset to the account - call.wallet::receive_asset - # => [pad(16), ptr, end_ptr] - - # clean the stack after call - dropw dropw dropw - # => [EMPTY_WORD, ptr, end_ptr] - - # increment the pointer and continue looping if ptr != end_ptr - movup.4 add.4 dup dup.6 neq - # => [should_loop, ptr+4, EMPTY_WORD, end_ptr] - end - # => [ptr', EMPTY_WORD, end_ptr] - - # clear the stack - drop dropw drop - # => [] -end - #! Writes the assets data stored in the advice map to the memory specified by the provided #! destination pointer. #! @@ -414,83 +139,3 @@ export.build_recipient_hash swapw hmerge # [RECIPIENT] end - -# HELPER PROCEDURES -# ================================================================================================= - -#! Writes the note inputs stored in the advice map to the memory specified by the provided -#! destination pointer. -#! -#! Inputs: -#! Operand stack: [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] -#! Advice map: { -#! NOTE_INPUTS_COMMITMENT: [[INPUT_VALUES]] -#! } -#! Outputs: -#! Operand stack: [num_inputs, dest_ptr] -proc.write_inputs_to_memory - # load the inputs from the advice map to the advice stack - adv.push_mapvaln - # OS => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [advice_num_inputs, [INPUT_VALUES]] - - # move the number of inputs obtained from advice map to the operand stack - adv_push.1 dup.5 - # OS => [num_inputs, advice_num_inputs, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - # Validate the note inputs length. Round up the number of inputs to the next multiple of 8: that - # value should be equal to the length obtained from the `adv.push_mapvaln` procedure. - u32divmod.8 neq.0 add mul.8 - # OS => [rounded_up_num_inputs, advice_num_inputs, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - assert_eq.err=ERR_NOTE_INVALID_NUMBER_OF_INPUTS - # OS => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - # calculate the number of words required to store the inputs - dup.4 u32divmod.4 neq.0 add - # OS => [num_words, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - # round up the number of words to the next multiple of 2 - dup is_odd add - # OS => [even_num_words, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - # prepare the stack for the `pipe_preimage_to_memory` procedure - dup.6 swap - # OS => [even_num_words, dest_ptr, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [[INPUT_VALUES]] - - # write the inputs from the advice stack into memory - exec.mem::pipe_preimage_to_memory drop - # OS => [num_inputs, dest_ptr] - # AS => [] -end - -#! Extracts the sender ID from the provided metadata word. -#! -#! Inputs: [METADATA] -#! Outputs: [sender_id_prefix, sender_id_suffix] -#! -#! Where: -#! - METADATA is the metadata of some note. -#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender ID of the note which -#! metadata was provided. -proc.extract_sender_from_metadata - # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_id_prefix] - - # drop aux felt and the felt containing tag, execution hint and payload - drop drop - # => [merged_sender_id_type_hint_tag, sender_id_prefix] - - # extract suffix of sender from merged layout, which means clearing the least significant byte - exec.account_id::shape_suffix - # => [sender_id_suffix, sender_id_prefix] - - # rearrange suffix and prefix - swap - # => [sender_id_prefix, sender_id_suffix] -end diff --git a/crates/miden-lib/asm/note_scripts/P2ID.masm b/crates/miden-lib/asm/note_scripts/P2ID.masm index 24bfc59a0b..3f95e326d3 100644 --- a/crates/miden-lib/asm/note_scripts/P2ID.masm +++ b/crates/miden-lib/asm/note_scripts/P2ID.masm @@ -1,6 +1,6 @@ use.miden::account use.miden::account_id -use.miden::note +use.miden::active_note # ERRORS # ================================================================================================= @@ -29,7 +29,7 @@ const.ERR_P2ID_TARGET_ACCT_MISMATCH="P2ID's target account address and transacti #! greater than 2^63. begin # store the note inputs to memory starting at address 0 - padw push.0 exec.note::get_inputs + padw push.0 exec.active_note::get_inputs # => [num_inputs, inputs_ptr, EMPTY_WORD] # make sure the number of inputs is 2 @@ -47,6 +47,6 @@ begin exec.account_id::is_equal assert.err=ERR_P2ID_TARGET_ACCT_MISMATCH # => [] - exec.note::add_assets_to_account + exec.active_note::add_assets_to_account # => [] end diff --git a/crates/miden-lib/asm/note_scripts/P2IDE.masm b/crates/miden-lib/asm/note_scripts/P2IDE.masm index 4fe6bb72f5..34a6095d32 100644 --- a/crates/miden-lib/asm/note_scripts/P2IDE.masm +++ b/crates/miden-lib/asm/note_scripts/P2IDE.masm @@ -1,6 +1,6 @@ use.miden::account use.miden::account_id -use.miden::note +use.miden::active_note use.miden::tx # ERRORS @@ -56,7 +56,7 @@ proc.reclaim_note # => [account_id_prefix, account_id_suffix] # if current account is not the target, we need to ensure it is the sender - exec.note::get_sender + exec.active_note::get_sender # => [sender_account_id_prefix, sender_account_id_suffix, account_id_prefix, account_id_suffix] # ensure current account ID = sender account ID @@ -64,7 +64,7 @@ proc.reclaim_note # => [] # add note assets to account - exec.note::add_assets_to_account + exec.active_note::add_assets_to_account # => [] end @@ -101,7 +101,7 @@ end #! greater than 2^63. begin # store the note inputs to memory starting at address 0 - push.0 exec.note::get_inputs + push.0 exec.active_note::get_inputs # => [num_inputs, inputs_ptr] # make sure the number of inputs is 4 @@ -130,7 +130,7 @@ begin if.true # we can safely consume the note since the current account is the target of the note - dropw exec.note::add_assets_to_account + dropw exec.active_note::add_assets_to_account # => [] else diff --git a/crates/miden-lib/asm/note_scripts/SWAP.masm b/crates/miden-lib/asm/note_scripts/SWAP.masm index cb8e79a40a..f64252166c 100644 --- a/crates/miden-lib/asm/note_scripts/SWAP.masm +++ b/crates/miden-lib/asm/note_scripts/SWAP.masm @@ -1,4 +1,4 @@ -use.miden::note +use.miden::active_note use.miden::output_note use.miden::contracts::wallets::basic->wallet @@ -46,7 +46,7 @@ begin # --- create a payback note with the requested asset ---------------- # store note inputs into memory starting at address 0 - push.0 exec.note::get_inputs + push.0 exec.active_note::get_inputs # => [num_inputs, inputs_ptr] # make sure the number of inputs is 12 @@ -89,7 +89,7 @@ begin # --- move assets from the SWAP note into the account ------------------------- # store the number of note assets to memory starting at address 0 - push.0 exec.note::get_assets + push.0 exec.active_note::get_assets # => [num_assets, ptr, pad(12)] # make sure the number of assets is 1 diff --git a/crates/miden-testing/src/kernel_tests/tx/mod.rs b/crates/miden-testing/src/kernel_tests/tx/mod.rs index 9fee06a3d2..0a37abbe96 100644 --- a/crates/miden-testing/src/kernel_tests/tx/mod.rs +++ b/crates/miden-testing/src/kernel_tests/tx/mod.rs @@ -29,6 +29,7 @@ use crate::MockChain; mod test_account; mod test_account_delta; mod test_account_interface; +mod test_active_note; mod test_asset; mod test_asset_vault; mod test_auth; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs new file mode 100644 index 0000000000..358c5c657e --- /dev/null +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -0,0 +1,494 @@ +use alloc::string::String; + +use anyhow::Context; +use miden_lib::errors::tx_kernel_errors::ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED; +use miden_lib::testing::mock_account::MockAccountExt; +use miden_lib::utils::ScriptBuilder; +use miden_objects::account::Account; +use miden_objects::asset::FungibleAsset; +use miden_objects::crypto::rand::{FeltRng, RpoRandomCoin}; +use miden_objects::note::{ + Note, + NoteAssets, + NoteExecutionHint, + NoteInputs, + NoteMetadata, + NoteRecipient, + NoteTag, + NoteType, +}; +use miden_objects::testing::account_id::{ + ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, + ACCOUNT_ID_SENDER, +}; +use miden_objects::{EMPTY_WORD, Felt, ONE, WORD_SIZE, Word}; + +use crate::utils::create_p2any_note; +use crate::{ + Auth, + MockChain, + TransactionContextBuilder, + TxContextInput, + assert_transaction_executor_error, +}; + +#[test] +fn test_active_note_get_sender_fails_from_tx_script() -> anyhow::Result<()> { + // Creates a mockchain with an account and a note + let mut builder = MockChain::builder(); + let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let p2id_note = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(150)], + NoteType::Public, + )?; + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + let code = " + use.miden::active_note + + begin + # try to get the sender from transaction script + exec.active_note::get_sender + end + "; + let tx_script = ScriptBuilder::default() + .compile_tx_script(code) + .context("failed to compile tx script")?; + + let tx_context = mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[p2id_note.id()], &[])? + .tx_script(tx_script) + .build()?; + + let result = tx_context.execute_blocking(); + assert_transaction_executor_error!( + result, + ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED + ); + + Ok(()) +} + +#[test] +fn test_active_note_get_sender() -> anyhow::Result<()> { + let tx_context = { + let account = + Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); + let input_note = + create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); + TransactionContextBuilder::new(account) + .extend_input_notes(vec![input_note]) + .build()? + }; + + // calling get_sender should return sender of the active note + let code = " + use.$kernel::prologue + use.$kernel::note->note_internal + use.miden::active_note + + begin + exec.prologue::prepare_transaction + exec.note_internal::prepare_note + dropw dropw dropw dropw + exec.active_note::get_sender + + # truncate the stack + swapw dropw + end + "; + + let process = tx_context.execute_code(code)?; + + let sender = tx_context.input_notes().get_note(0).note().metadata().sender(); + assert_eq!(process.stack.get(0), sender.prefix().as_felt()); + assert_eq!(process.stack.get(1), sender.suffix()); + Ok(()) +} + +#[test] +fn test_active_note_get_assets() -> anyhow::Result<()> { + // Creates a mockchain with an account and a note that it can consume + let tx_context = { + let mut builder = MockChain::builder(); + let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let p2id_note_1 = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(150)], + NoteType::Public, + )?; + let p2id_note_2 = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(300)], + NoteType::Public, + )?; + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + mock_chain + .build_tx_context( + TxContextInput::AccountId(account.id()), + &[], + &[p2id_note_1, p2id_note_2], + )? + .build()? + }; + + let notes = tx_context.input_notes(); + + const DEST_POINTER_NOTE_0: u32 = 100000000; + const DEST_POINTER_NOTE_1: u32 = 200000000; + + fn construct_asset_assertions(note: &Note) -> String { + let mut code = String::new(); + for asset in note.assets().iter() { + code += &format!( + " + # assert the asset is correct + dup padw movup.4 mem_loadw push.{asset} assert_eqw push.4 add + ", + asset = Word::from(asset) + ); + } + code + } + + // calling get_assets should return assets at the specified address + let code = format!( + " + use.std::sys + + use.$kernel::prologue + use.$kernel::note->note_internal + use.miden::active_note + + proc.process_note_0 + # drop the note inputs + dropw dropw dropw dropw + + # set the destination pointer for note 0 assets + push.{DEST_POINTER_NOTE_0} + + # get the assets + exec.active_note::get_assets + + # assert the number of assets is correct + eq.{note_0_num_assets} assert + + # assert the pointer is returned + dup eq.{DEST_POINTER_NOTE_0} assert + + # asset memory assertions + {NOTE_0_ASSET_ASSERTIONS} + + # clean pointer + drop + end + + proc.process_note_1 + # drop the note inputs + dropw dropw dropw dropw + + # set the destination pointer for note 1 assets + push.{DEST_POINTER_NOTE_1} + + # get the assets + exec.active_note::get_assets + + # assert the number of assets is correct + eq.{note_1_num_assets} assert + + # assert the pointer is returned + dup eq.{DEST_POINTER_NOTE_1} assert + + # asset memory assertions + {NOTE_1_ASSET_ASSERTIONS} + + # clean pointer + drop + end + + begin + # prepare tx + exec.prologue::prepare_transaction + + # prepare note 0 + exec.note_internal::prepare_note + + # process note 0 + call.process_note_0 + + # increment active input note pointer + exec.note_internal::increment_active_input_note_ptr + + # prepare note 1 + exec.note_internal::prepare_note + + # process note 1 + call.process_note_1 + + # truncate the stack + exec.sys::truncate_stack + end + ", + note_0_num_assets = notes.get_note(0).note().assets().num_assets(), + note_1_num_assets = notes.get_note(1).note().assets().num_assets(), + NOTE_0_ASSET_ASSERTIONS = construct_asset_assertions(notes.get_note(0).note()), + NOTE_1_ASSET_ASSERTIONS = construct_asset_assertions(notes.get_note(1).note()), + ); + + tx_context.execute_code(&code)?; + Ok(()) +} + +#[test] +fn test_active_note_get_inputs() -> anyhow::Result<()> { + // Creates a mockchain with an account and a note that it can consume + let tx_context = { + let mut builder = MockChain::builder(); + let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let p2id_note = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(100)], + NoteType::Public, + )?; + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note])? + .build()? + }; + + fn construct_inputs_assertions(note: &Note) -> String { + let mut code = String::new(); + for inputs_chunk in note.inputs().values().chunks(WORD_SIZE) { + let mut inputs_word = EMPTY_WORD; + inputs_word.as_mut_slice()[..inputs_chunk.len()].copy_from_slice(inputs_chunk); + + code += &format!( + r#" + # assert the inputs are correct + # => [dest_ptr] + dup padw movup.4 mem_loadw push.{inputs_word} assert_eqw.err="inputs are incorrect" + # => [dest_ptr] + + push.4 add + # => [dest_ptr+4] + "# + ); + } + code + } + + let note0 = tx_context.input_notes().get_note(0).note(); + + let code = format!( + " + use.$kernel::prologue + use.$kernel::note->note_internal + use.miden::active_note + + begin + # => [BH, acct_id, IAH, NC] + exec.prologue::prepare_transaction + # => [] + + exec.note_internal::prepare_note + # => [note_script_root_ptr, NOTE_ARGS, pad(11)] + + # clean the stack + dropw dropw dropw dropw + # => [] + + push.{NOTE_0_PTR} exec.active_note::get_inputs + # => [num_inputs, dest_ptr] + + eq.{num_inputs} assert + # => [dest_ptr] + + dup eq.{NOTE_0_PTR} assert + # => [dest_ptr] + + # apply note 1 inputs assertions + {inputs_assertions} + # => [dest_ptr] + + # clear the stack + drop + # => [] + end + ", + num_inputs = note0.inputs().num_values(), + inputs_assertions = construct_inputs_assertions(note0), + NOTE_0_PTR = 100000000, + ); + + tx_context.execute_code(&code)?; + Ok(()) +} + +/// This test checks the scenario when an input note has exactly 8 inputs, and the transaction +/// script attempts to load the inputs to memory using the `miden::active_note::get_inputs` +/// procedure. +/// +/// Previously this setup was leading to the incorrect number of note inputs computed during the +/// `get_inputs` procedure, see the [issue #1363](https://github.com/0xMiden/miden-base/issues/1363) +/// for more details. +#[test] +fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { + let sender_id = ACCOUNT_ID_SENDER + .try_into() + .context("failed to convert ACCOUNT_ID_SENDER to account ID")?; + let target_id = ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE.try_into().context( + "failed to convert ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE to account ID", + )?; + + // prepare note data + let serial_num = RpoRandomCoin::new(Word::from([4u32; 4])).draw_word(); + let tag = NoteTag::from_account_id(target_id); + let metadata = NoteMetadata::new( + sender_id, + NoteType::Public, + tag, + NoteExecutionHint::always(), + Default::default(), + ) + .context("failed to create metadata")?; + let vault = NoteAssets::new(vec![]).context("failed to create input note assets")?; + let note_script = ScriptBuilder::default() + .compile_note_script("begin nop end") + .context("failed to compile note script")?; + + // create a recipient with note inputs, which number divides by 8. For simplicity create 8 input + // values + let recipient = NoteRecipient::new( + serial_num, + note_script, + NoteInputs::new(vec![ + ONE, + Felt::new(2), + Felt::new(3), + Felt::new(4), + Felt::new(5), + Felt::new(6), + Felt::new(7), + Felt::new(8), + ]) + .context("failed to create note inputs")?, + ); + let input_note = Note::new(vault.clone(), metadata, recipient); + + // provide this input note to the transaction context + let tx_context = TransactionContextBuilder::with_existing_mock_account() + .extend_input_notes(vec![input_note]) + .build()?; + + let tx_code = " + use.$kernel::prologue + use.miden::active_note + + begin + exec.prologue::prepare_transaction + + # execute the `get_inputs` procedure to trigger note inputs length assertion + push.0 exec.active_note::get_inputs + # => [num_inputs, 0] + + # assert that the inputs length is 8 + push.8 assert_eq.err=\"number of inputs values should be equal to 8\" + + # clean the stack + drop + end + "; + + tx_context.execute_code(tx_code).context("transaction execution failed")?; + + Ok(()) +} + +#[test] +fn test_active_note_get_serial_number() -> anyhow::Result<()> { + let tx_context = { + let mut builder = MockChain::builder(); + let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let p2id_note_1 = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(150)], + NoteType::Public, + )?; + let mock_chain = builder.build()?; + + mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note_1])? + .build()? + }; + + // calling get_serial_number should return the serial number of the active note + let code = " + use.$kernel::prologue + use.miden::active_note + + begin + exec.prologue::prepare_transaction + exec.active_note::get_serial_number + + # truncate the stack + swapw dropw + end + "; + + let process = tx_context.execute_code(code)?; + + let serial_number = tx_context.input_notes().get_note(0).note().serial_num(); + assert_eq!(process.stack.get_word(0), serial_number); + Ok(()) +} + +#[test] +fn test_active_note_get_script_root() -> anyhow::Result<()> { + let tx_context = { + let mut builder = MockChain::builder(); + let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let p2id_note_1 = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(150)], + NoteType::Public, + )?; + let mock_chain = builder.build()?; + + mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note_1])? + .build()? + }; + + // calling get_script_root should return script root of the active note + let code = " + use.$kernel::prologue + use.miden::active_note + + begin + exec.prologue::prepare_transaction + exec.active_note::get_script_root + + # truncate the stack + swapw dropw + end + "; + + let process = tx_context.execute_code(code)?; + + let script_root = tx_context.input_notes().get_note(0).note().script().root(); + assert_eq!(process.stack.get_word(0), script_root); + Ok(()) +} diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 261eff9bee..968c34322a 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -1,18 +1,15 @@ use alloc::collections::BTreeMap; -use alloc::string::String; use alloc::sync::Arc; use alloc::vec::Vec; use anyhow::Context; use miden_lib::account::wallets::BasicWallet; use miden_lib::errors::MasmError; -use miden_lib::errors::tx_kernel_errors::ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED; -use miden_lib::testing::mock_account::MockAccountExt; use miden_lib::testing::note::NoteBuilder; use miden_lib::transaction::TransactionKernel; use miden_lib::transaction::memory::ACTIVE_INPUT_NOTE_PTR; use miden_lib::utils::ScriptBuilder; -use miden_objects::account::{Account, AccountBuilder, AccountId}; +use miden_objects::account::{AccountBuilder, AccountId}; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::diagnostics::miette::{self, miette}; use miden_objects::asset::FungibleAsset; @@ -31,17 +28,16 @@ use miden_objects::note::{ }; use miden_objects::testing::account_id::{ ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, - ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_SENDER, }; use miden_objects::transaction::{AccountInputs, OutputNote, TransactionArgs}; -use miden_objects::{EMPTY_WORD, Felt, ONE, WORD_SIZE, Word, ZERO}; +use miden_objects::{Felt, Word, ZERO}; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; use super::Process; use crate::kernel_tests::tx::ProcessMemoryExt; -use crate::utils::{create_p2any_note, input_note_data_ptr}; +use crate::utils::input_note_data_ptr; use crate::{ Auth, MockChain, @@ -51,388 +47,6 @@ use crate::{ assert_transaction_executor_error, }; -#[test] -fn test_get_sender_fails_from_tx_script() -> anyhow::Result<()> { - // Creates a mockchain with an account and a note - let mut builder = MockChain::builder(); - let account = builder.add_existing_wallet(Auth::BasicAuth)?; - let p2id_note = builder.add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(150)], - NoteType::Public, - )?; - let mut mock_chain = builder.build()?; - mock_chain.prove_next_block()?; - - // calling get_sender should return sender - let code = " - use.miden::note - - begin - # try to get the sender from transaction script - exec.note::get_sender - end - "; - let tx_script = ScriptBuilder::default() - .compile_tx_script(code) - .context("failed to compile tx script")?; - - let tx_context = mock_chain - .build_tx_context(TxContextInput::AccountId(account.id()), &[p2id_note.id()], &[])? - .tx_script(tx_script) - .build()?; - - let result = tx_context.execute_blocking(); - assert_transaction_executor_error!( - result, - ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED - ); - - Ok(()) -} - -#[test] -fn test_get_sender() -> anyhow::Result<()> { - let tx_context = { - let account = - Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); - let input_note = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); - TransactionContextBuilder::new(account) - .extend_input_notes(vec![input_note]) - .build()? - }; - - // calling get_sender should return sender - let code = " - use.$kernel::prologue - use.$kernel::note->note_internal - use.miden::note - - begin - exec.prologue::prepare_transaction - exec.note_internal::prepare_note - dropw dropw dropw dropw - exec.note::get_sender - - # truncate the stack - swapw dropw - end - "; - - let process = tx_context.execute_code(code)?; - - let sender = tx_context.input_notes().get_note(0).note().metadata().sender(); - assert_eq!(process.stack.get(0), sender.prefix().as_felt()); - assert_eq!(process.stack.get(1), sender.suffix()); - Ok(()) -} - -#[test] -fn test_get_assets() -> anyhow::Result<()> { - // Creates a mockchain with an account and a note that it can consume - let tx_context = { - let mut builder = MockChain::builder(); - let account = builder.add_existing_wallet(Auth::BasicAuth)?; - let p2id_note_1 = builder.add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(150)], - NoteType::Public, - )?; - let p2id_note_2 = builder.add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(300)], - NoteType::Public, - )?; - let mut mock_chain = builder.build()?; - mock_chain.prove_next_block()?; - - mock_chain - .build_tx_context( - TxContextInput::AccountId(account.id()), - &[], - &[p2id_note_1, p2id_note_2], - )? - .build()? - }; - - let notes = tx_context.input_notes(); - - const DEST_POINTER_NOTE_0: u32 = 100000000; - const DEST_POINTER_NOTE_1: u32 = 200000000; - - fn construct_asset_assertions(note: &Note) -> String { - let mut code = String::new(); - for asset in note.assets().iter() { - code += &format!( - " - # assert the asset is correct - dup padw movup.4 mem_loadw push.{asset} assert_eqw push.4 add - ", - asset = Word::from(asset) - ); - } - code - } - - // calling get_assets should return assets at the specified address - let code = format!( - " - use.std::sys - - use.$kernel::prologue - use.$kernel::note->note_internal - use.miden::note - - proc.process_note_0 - # drop the note inputs - dropw dropw dropw dropw - - # set the destination pointer for note 0 assets - push.{DEST_POINTER_NOTE_0} - - # get the assets - exec.note::get_assets - - # assert the number of assets is correct - eq.{note_0_num_assets} assert - - # assert the pointer is returned - dup eq.{DEST_POINTER_NOTE_0} assert - - # asset memory assertions - {NOTE_0_ASSET_ASSERTIONS} - - # clean pointer - drop - end - - proc.process_note_1 - # drop the note inputs - dropw dropw dropw dropw - - # set the destination pointer for note 1 assets - push.{DEST_POINTER_NOTE_1} - - # get the assets - exec.note::get_assets - - # assert the number of assets is correct - eq.{note_1_num_assets} assert - - # assert the pointer is returned - dup eq.{DEST_POINTER_NOTE_1} assert - - # asset memory assertions - {NOTE_1_ASSET_ASSERTIONS} - - # clean pointer - drop - end - - begin - # prepare tx - exec.prologue::prepare_transaction - - # prepare note 0 - exec.note_internal::prepare_note - - # process note 0 - call.process_note_0 - - # increment active input note pointer - exec.note_internal::increment_active_input_note_ptr - - # prepare note 1 - exec.note_internal::prepare_note - - # process note 1 - call.process_note_1 - - # truncate the stack - exec.sys::truncate_stack - end - ", - note_0_num_assets = notes.get_note(0).note().assets().num_assets(), - note_1_num_assets = notes.get_note(1).note().assets().num_assets(), - NOTE_0_ASSET_ASSERTIONS = construct_asset_assertions(notes.get_note(0).note()), - NOTE_1_ASSET_ASSERTIONS = construct_asset_assertions(notes.get_note(1).note()), - ); - - tx_context.execute_code(&code)?; - Ok(()) -} - -#[test] -fn test_get_inputs() -> anyhow::Result<()> { - // Creates a mockchain with an account and a note that it can consume - let tx_context = { - let mut builder = MockChain::builder(); - let account = builder.add_existing_wallet(Auth::BasicAuth)?; - let p2id_note = builder.add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(100)], - NoteType::Public, - )?; - let mut mock_chain = builder.build()?; - mock_chain.prove_next_block()?; - - mock_chain - .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note])? - .build()? - }; - - fn construct_inputs_assertions(note: &Note) -> String { - let mut code = String::new(); - for inputs_chunk in note.inputs().values().chunks(WORD_SIZE) { - let mut inputs_word = EMPTY_WORD; - inputs_word.as_mut_slice()[..inputs_chunk.len()].copy_from_slice(inputs_chunk); - - code += &format!( - r#" - # assert the inputs are correct - # => [dest_ptr] - dup padw movup.4 mem_loadw push.{inputs_word} assert_eqw.err="inputs are incorrect" - # => [dest_ptr] - - push.4 add - # => [dest_ptr+4] - "# - ); - } - code - } - - let note0 = tx_context.input_notes().get_note(0).note(); - - let code = format!( - " - use.$kernel::prologue - use.$kernel::note->note_internal - use.miden::note - - begin - # => [BH, acct_id, IAH, NC] - exec.prologue::prepare_transaction - # => [] - - exec.note_internal::prepare_note - # => [note_script_root_ptr, NOTE_ARGS, pad(11)] - - # clean the stack - dropw dropw dropw dropw - # => [] - - push.{NOTE_0_PTR} exec.note::get_inputs - # => [num_inputs, dest_ptr] - - eq.{num_inputs} assert - # => [dest_ptr] - - dup eq.{NOTE_0_PTR} assert - # => [dest_ptr] - - # apply note 1 inputs assertions - {inputs_assertions} - # => [dest_ptr] - - # clear the stack - drop - # => [] - end - ", - num_inputs = note0.inputs().num_values(), - inputs_assertions = construct_inputs_assertions(note0), - NOTE_0_PTR = 100000000, - ); - - tx_context.execute_code(&code)?; - Ok(()) -} - -/// This test checks the scenario when an input note has exactly 8 inputs, and the transaction -/// script attempts to load the inputs to memory using the `miden::note::get_inputs` procedure. -/// -/// Previously this setup was leading to the incorrect number of note inputs computed during the -/// `get_inputs` procedure, see the [issue #1363](https://github.com/0xMiden/miden-base/issues/1363) -/// for more details. -#[test] -fn test_get_exactly_8_inputs() -> anyhow::Result<()> { - let sender_id = ACCOUNT_ID_SENDER - .try_into() - .context("failed to convert ACCOUNT_ID_SENDER to account ID")?; - let target_id = ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE.try_into().context( - "failed to convert ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE to account ID", - )?; - - // prepare note data - let serial_num = RpoRandomCoin::new(Word::from([4u32; 4])).draw_word(); - let tag = NoteTag::from_account_id(target_id); - let metadata = NoteMetadata::new( - sender_id, - NoteType::Public, - tag, - NoteExecutionHint::always(), - Default::default(), - ) - .context("failed to create metadata")?; - let vault = NoteAssets::new(vec![]).context("failed to create input note assets")?; - let note_script = ScriptBuilder::default() - .compile_note_script("begin nop end") - .context("failed to compile note script")?; - - // create a recipient with note inputs, which number divides by 8. For simplicity create 8 input - // values - let recipient = NoteRecipient::new( - serial_num, - note_script, - NoteInputs::new(vec![ - ONE, - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - Felt::new(6), - Felt::new(7), - Felt::new(8), - ]) - .context("failed to create note inputs")?, - ); - let input_note = Note::new(vault.clone(), metadata, recipient); - - // provide this input note to the transaction context - let tx_context = TransactionContextBuilder::with_existing_mock_account() - .extend_input_notes(vec![input_note]) - .build()?; - - let tx_code = " - use.$kernel::prologue - use.miden::note - - begin - exec.prologue::prepare_transaction - - # execute the `get_inputs` procedure to trigger note inputs length assertion - push.0 exec.note::get_inputs - # => [num_inputs, 0] - - # assert that the inputs length is 8 - push.8 assert_eq.err=\"number of inputs values should be equal to 8\" - - # clean the stack - drop - end - "; - - tx_context.execute_code(tx_code).context("transaction execution failed")?; - - Ok(()) -} - #[test] fn test_note_setup() -> anyhow::Result<()> { let tx_context = { @@ -577,45 +191,6 @@ fn note_setup_memory_assertions(process: &Process) { ); } -#[test] -fn test_get_note_serial_number() -> anyhow::Result<()> { - let tx_context = { - let mut builder = MockChain::builder(); - let account = builder.add_existing_wallet(Auth::BasicAuth)?; - let p2id_note_1 = builder.add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(150)], - NoteType::Public, - )?; - let mock_chain = builder.build()?; - - mock_chain - .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note_1])? - .build()? - }; - - // calling get_serial_number should return the serial number of the note - let code = " - use.$kernel::prologue - use.miden::note - - begin - exec.prologue::prepare_transaction - exec.note::get_serial_number - - # truncate the stack - swapw dropw - end - "; - - let process = tx_context.execute_code(code)?; - - let serial_number = tx_context.input_notes().get_note(0).note().serial_num(); - assert_eq!(process.stack.get_word(0), serial_number); - Ok(()) -} - #[test] fn test_build_recipient() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; @@ -797,45 +372,6 @@ fn test_compute_inputs_commitment() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_get_current_script_root() -> anyhow::Result<()> { - let tx_context = { - let mut builder = MockChain::builder(); - let account = builder.add_existing_wallet(Auth::BasicAuth)?; - let p2id_note_1 = builder.add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(150)], - NoteType::Public, - )?; - let mock_chain = builder.build()?; - - mock_chain - .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note_1])? - .build()? - }; - - // calling get_script_root should return script root - let code = " - use.$kernel::prologue - use.miden::note - - begin - exec.prologue::prepare_transaction - exec.note::get_script_root - - # truncate the stack - swapw dropw - end - "; - - let process = tx_context.execute_code(code)?; - - let script_root = tx_context.input_notes().get_note(0).note().script().root(); - assert_eq!(process.stack.get_word(0), script_root); - Ok(()) -} - #[test] fn test_build_metadata() -> miette::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); @@ -907,12 +443,12 @@ pub fn test_timelock() -> anyhow::Result<()> { let code = format!( r#" - use.miden::note + use.miden::active_note use.miden::tx begin # store the note inputs to memory starting at address 0 - push.0 exec.note::get_inputs + push.0 exec.active_note::get_inputs # => [num_inputs, inputs_ptr] # make sure the number of inputs is 1 diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 650afa194b..c6b5624dcb 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -114,12 +114,12 @@ pub fn create_p2any_note(sender: AccountId, assets: impl IntoIteratorwallet begin # fetch pointer & number of assets - push.0 exec.note::get_assets # [num_assets, dest_ptr] + push.0 exec.active_note::get_assets # [num_assets, dest_ptr] # runtime-check we got the expected count push.{num_assets} assert_eq # [dest_ptr] diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 73d7a99591..8a852522ea 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -241,7 +241,7 @@ fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result< padw padw padw padw # => [pad(16)] - exec.::miden::note::get_assets drop + exec.::miden::active_note::get_assets drop mem_loadw # => [ASSET, pad(12)] diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index 28fa3a0e67..f72cecddf2 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -52,9 +52,9 @@ Account procedures can be used to read and write to account storage, add or remo | `get_vault_root` | Returns the vault root of the current account.

Inputs: `[]`
Outputs: `[VAULT_ROOT]` | Any | | `was_procedure_called` | Returns 1 if a procedure was called during transaction execution, and 0 otherwise.

Inputs: `[PROC_ROOT]`
Outputs: `[was_called]` | Any | -## Note Procedures (`miden::note`) +## Active Note Procedures (`miden::active_note`) -Note procedures can be used to fetch data from the active note. +Active note procedures can be used to fetch data from the note that is currently being processed by the transaction kernel. | Procedure | Description | Context | | --- | --- | --- | @@ -64,12 +64,7 @@ Note procedures can be used to fetch data from the active note. | `get_sender` | Returns the sender of the active note.

Inputs: `[]`
Outputs: `[sender_id_prefix, sender_id_suffix]` | Note | | `get_serial_number` | Returns the serial number of the active note.

Inputs: `[]`
Outputs: `[SERIAL_NUMBER]` | Note | | `get_script_root` | Returns the script root of the active note.

Inputs: `[]`
Outputs: `[SCRIPT_ROOT]` | Note | -| `compute_inputs_commitment` | Computes the commitment to the output note inputs starting at the specified memory address.

Inputs: `[inputs_ptr, num_inputs]`
Outputs: `[INPUTS_COMMITMENT]` | Any | -| `get_max_inputs_per_note` | Returns the max allowed number of input values per note.

Inputs: `[]`
Outputs: `[max_inputs_per_note]` | Any | | `add_assets_to_account` | Adds all assets from the active note to the account vault.

Inputs: `[]`
Outputs: `[]` | Note | -| `write_assets_to_memory` | Writes the assets data stored in the advice map to the memory specified by the provided destination pointer.

Inputs: `[ASSETS_COMMITMENT, num_assets, dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Any | -| `build_recipient_hash` | Returns the `RECIPIENT` for a specified `SERIAL_NUM`, `SCRIPT_ROOT`, and inputs commitment.

Inputs: `[SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT]`
Outputs: `[RECIPIENT]` | Any | -| `build_recipient` | Builds the recipient hash from note inputs, script root, and serial number.

Inputs: `[inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]`
Outputs: `[RECIPIENT]` | Any | ## Input Note Procedures (`miden::input_note`) @@ -98,6 +93,18 @@ Output note procedures can be used to fetch data on output notes created by the | `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the output note with the specified index.

Inputs: `[note_index]`
Outputs: `[RECIPIENT]` | Any | | `get_metadata` | Returns the [metadata](note.md#metadata) of the output note with the specified index.

Inputs: `[note_index]`
Outputs: `[METADATA]` | Any | +## Note Utility Procedures (`miden::note`) + +Note utility procedures can be used to compute the required utility data or write note data to memory. + +| Procedure | Description | Context | +| --- | --- | --- | +| `compute_inputs_commitment` | Computes the commitment to the output note inputs starting at the specified memory address.

Inputs: `[inputs_ptr, num_inputs]`
Outputs: `[INPUTS_COMMITMENT]` | Any | +| `get_max_inputs_per_note` | Returns the max allowed number of input values per note.

Inputs: `[]`
Outputs: `[max_inputs_per_note]` | Any | +| `write_assets_to_memory` | Writes the assets data stored in the advice map to the memory specified by the provided destination pointer.

Inputs: `[ASSETS_COMMITMENT, num_assets, dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Any | +| `build_recipient_hash` | Returns the `RECIPIENT` for a specified `SERIAL_NUM`, `SCRIPT_ROOT`, and inputs commitment.

Inputs: `[SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT]`
Outputs: `[RECIPIENT]` | Any | +| `build_recipient` | Builds the recipient hash from note inputs, script root, and serial number.

Inputs: `[inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]`
Outputs: `[RECIPIENT]` | Any | + ## Transaction Procedures (`miden::tx`) Transaction procedures manage transaction-level operations including note creation, context switching, and reading transaction metadata. diff --git a/docs/src/transaction.md b/docs/src/transaction.md index a6b08335af..e9da19ad0e 100644 --- a/docs/src/transaction.md +++ b/docs/src/transaction.md @@ -76,7 +76,7 @@ To start the transaction process, the executor fetches and prepares all the inpu In the transaction's prologue the data is being authenticated by re-hashing the provided values and comparing them to the blockchain's data (this is how private data can be used and verified during the execution of transaction without actually revealing it to the network). -Then the P2ID note script is being executed. The script starts by reading the note inputs `note::get_inputs` — in our case the account ID of the intended target account. It checks if the provided target account ID equals the account ID of the executing account. This is the first time the note invokes a method exposed by the `Transaction` kernel, `account::get_id`. +Then the P2ID note script is being executed. The script starts by reading the note inputs `active_note::get_inputs` — in our case the account ID of the intended target account. It checks if the provided target account ID equals the account ID of the executing account. This is the first time the note invokes a method exposed by the `Transaction` kernel, `account::get_id`. If the check passes, the note script pushes the assets it holds into the account's vault. For every asset the note contains, the script calls the `wallets::basic::receive_asset` method exposed by the account's wallet component. The `wallets::basic::receive_asset` procedure calls `account::add_asset`, which cannot be called from the note itself. This allows accounts to control what functionality to expose, e.g. whether the account supports receiving assets or not, and the note cannot bypass that. From 397938b485fcec185aac0957b5f8df5ec3ca6720 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 18 Sep 2025 16:37:21 +0200 Subject: [PATCH 044/133] chore: Fix clippy 1.90 lints (#1912) --- crates/miden-lib/src/transaction/memory.rs | 4 ++-- crates/miden-objects/src/note/assets.rs | 4 ++-- crates/miden-objects/src/note/script.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/miden-lib/src/transaction/memory.rs b/crates/miden-lib/src/transaction/memory.rs index 8b3705271e..11bff29cbd 100644 --- a/crates/miden-lib/src/transaction/memory.rs +++ b/crates/miden-lib/src/transaction/memory.rs @@ -449,11 +449,11 @@ pub const LINK_MAP_USED_MEMORY_SIZE: MemoryAddress = 33_554_432; pub const LINK_MAP_ENTRY_SIZE: MemoryOffset = 16; const _: () = assert!( - LINK_MAP_REGION_START_PTR % LINK_MAP_ENTRY_SIZE == 0, + LINK_MAP_REGION_START_PTR.is_multiple_of(LINK_MAP_ENTRY_SIZE), "link map region start ptr should be aligned to entry size" ); const _: () = assert!( - (LINK_MAP_REGION_END_PTR - LINK_MAP_REGION_START_PTR) % LINK_MAP_ENTRY_SIZE == 0, + (LINK_MAP_REGION_END_PTR - LINK_MAP_REGION_START_PTR).is_multiple_of(LINK_MAP_ENTRY_SIZE), "the link map memory range should cleanly contain a multiple of the entry size" ); diff --git a/crates/miden-objects/src/note/assets.rs b/crates/miden-objects/src/note/assets.rs index c94c329ac2..260274eeaa 100644 --- a/crates/miden-objects/src/note/assets.rs +++ b/crates/miden-objects/src/note/assets.rs @@ -93,7 +93,7 @@ impl NoteAssets { /// because hashing the returned elements results in the note asset commitment. pub fn to_padded_assets(&self) -> Vec { // if we have an odd number of assets with pad with a single word. - let padded_len = if self.assets.len() % 2 == 0 { + let padded_len = if self.assets.len().is_multiple_of(2) { self.assets.len() * WORD_SIZE } else { (self.assets.len() + 1) * WORD_SIZE @@ -192,7 +192,7 @@ fn compute_asset_commitment(assets: &[Asset]) -> Word { // If we have an odd number of assets we pad the vector with 4 zero elements. This is to // ensure the number of elements is a multiple of 8 - the size of the hasher rate. - let word_capacity = if assets.len() % 2 == 0 { + let word_capacity = if assets.len().is_multiple_of(2) { assets.len() } else { assets.len() + 1 diff --git a/crates/miden-objects/src/note/script.rs b/crates/miden-objects/src/note/script.rs index 2ea6d1fbf3..67ef2cd177 100644 --- a/crates/miden-objects/src/note/script.rs +++ b/crates/miden-objects/src/note/script.rs @@ -84,7 +84,7 @@ impl From<&NoteScript> for Vec { let len = bytes.len(); // Pad the data so that it can be encoded with u32 - let missing = if len % 4 > 0 { 4 - (len % 4) } else { 0 }; + let missing = if !len.is_multiple_of(4) { 4 - (len % 4) } else { 0 }; bytes.resize(bytes.len() + missing, 0); let final_size = 2 + bytes.len(); From 0f51e8aa939a66807d00e14d4b82470e82018207 Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Thu, 18 Sep 2025 18:31:13 +0300 Subject: [PATCH 045/133] Refactor `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` (#1887) --- CHANGELOG.md | 1 + crates/miden-lib/asm/miden/auth/rpo_falcon512.masm | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d324aa5b8..aa7403d170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). - [BREAKING] Move account seed into `PartialAccount` ([#1875](https://github.com/0xMiden/miden-base/pull/1875)). - Added `get_item_init` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). +- Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) ### Changes diff --git a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm index 1807b2017d..f8a4a3d27a 100644 --- a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm +++ b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm @@ -78,6 +78,11 @@ end #! from the provided account storage map slot, verifies their signatures against the transaction message, #! and returns the number of successfully verified signatures. #! +#! Note: Calls `account::get_map_item_init` to access the transaction's initial storage state +#! rather than the current state. This is crucial when validating transactions that update +#! the owner public key mapping - the previous signers must authorize the change to +#! the new signers, not the new signers authorizing themselves. +#! #! Inputs: [pub_key_slot_idx, num_of_approvers, MSG] #! Outputs: [num_verified_signatures, MSG] export.verify_signatures.16 @@ -104,7 +109,8 @@ export.verify_signatures.16 sub.1 dup push.0.0.0 loc_load.PUB_KEY_MAP_IDX_LOC # => [owner_key_slot, [0, 0, 0, i-1], i-1, MSG] - exec.account::get_map_item + # Get public key from initial storage state + exec.account::get_map_item_init # => [OWNER_PUB_KEY, i-1, MSG] loc_storew.CURRENT_PK_LOC From c9b5bb6ea537eff248f55b90ac905fc41ac72549 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 22 Sep 2025 09:49:02 +0200 Subject: [PATCH 046/133] feat: lazy foreign account assets and storage map items (#1888) * feat: Abstract over requesting native or foreign account roots * feat: Implement asset and storage map loading in tx context * feat: Handle other account asset vault operations * feat: Add FPI test with `get_balance` and `has_non_fungible_asset` * chore: add changelog * chore: improve docs * chore: add refernence to issue * chore: make event constant names consistent * chore: Move map root branch to base host * chore: Move asset vault root branch to base host * chore: Use "raw key" instead of "unhashed key" --- CHANGELOG.md | 1 + .../asm/kernels/transaction/api.masm | 14 +- .../asm/kernels/transaction/lib/account.masm | 65 +++++- crates/miden-lib/src/transaction/events.rs | 22 +- .../src/transaction/kernel_procedures.rs | 4 +- .../src/kernel_tests/tx/test_fpi.rs | 126 ++++++++++ .../miden-testing/src/tx_context/builder.rs | 75 +++--- .../miden-testing/src/tx_context/context.rs | 131 ++++++++--- crates/miden-tx/src/executor/exec_host.rs | 63 +---- crates/miden-tx/src/host/mod.rs | 217 +++++++++++++++--- 10 files changed, 541 insertions(+), 177 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa7403d170..0ba4b659ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Added lazy loading of the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). - Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). - [BREAKING] Move account seed into `PartialAccount` ([#1875](https://github.com/0xMiden/miden-base/pull/1875)). +- [BREAKING] Enabled lazy loading of assets and storage map items for foreign accounts during transaction execution ([#1888](https://github.com/0xMiden/miden-base/pull/1888)). - Added `get_item_init` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). - Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 55f43bc287..7ebd7511cf 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -639,12 +639,7 @@ end #! #! Invocation: dynexec export.account_get_balance - # get the vault root - exec.memory::get_account_vault_root_ptr movdn.2 - # => [faucet_id_prefix, faucet_id_suffix, account_vault_root_ptr, pad(14)] - - # get the asset balance - exec.asset_vault::get_balance + exec.account::get_balance # => [balance, pad(15)] end @@ -662,12 +657,7 @@ end #! #! Invocation: dynexec export.account_has_non_fungible_asset - # get the vault root - exec.memory::get_account_vault_root_ptr movdn.4 - # => [ASSET, vault_root_ptr, pad(12)] - - # check if the account vault has the non-fungible asset - exec.asset_vault::has_non_fungible_asset + exec.account::has_non_fungible_asset # => [has_asset, pad(15)] end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 4709823533..7866a2405d 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -121,7 +121,7 @@ const.ACCOUNT_PROCEDURE_DATA_LENGTH=8 # ================================================================================================= # Event emitted before a foreign account is loaded from the advice inputs. -const.ACCOUNT_BEFORE_FOREIGN_LOAD=131104 +const.ACCOUNT_BEFORE_FOREIGN_LOAD_EVENT=131104 # Event emitted before an asset is added to the account vault. const.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT=131072 @@ -133,6 +133,12 @@ const.ACCOUNT_VAULT_BEFORE_REMOVE_ASSET_EVENT=131074 # Event emitted after an asset is removed from the account vault. const.ACCOUNT_VAULT_AFTER_REMOVE_ASSET_EVENT=131075 +# Event emitted before a fungible asset's balance is fetched from the account vault. +const.ACCOUNT_VAULT_BEFORE_GET_BALANCE_EVENT=131105 + +# Event emitted before it is checked whether a non-fungible asset exists in the account vault. +const.ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET_EVENT=131106 + # Event emitted before an account storage item is updated. const.ACCOUNT_STORAGE_BEFORE_SET_ITEM_EVENT=131076 # Event emitted after an account storage item is updated. @@ -868,6 +874,9 @@ export.validate_seed # => [] end +# ACCOUNT VAULT +# ================================================================================================= + #! Adds the specified asset to the account vault. #! #! Inputs: [ASSET] @@ -944,6 +953,58 @@ export.remove_asset_from_vault # => [ASSET] end +#! Returns the balance of a fungible asset associated with a faucet_id. +#! +#! Inputs: [faucet_id_prefix, faucet_id_suffix] +#! Outputs: [balance] +#! +#! Where: +#! - faucet_id_{prefix, suffix} are the prefix and suffix felts of the faucet id of the fungible +#! asset of interest. +#! - balance is the vault balance of the fungible asset. +#! +#! Panics if: +#! - the asset is not a fungible asset. +export.get_balance + # get the vault root + exec.memory::get_account_vault_root_ptr movdn.2 + # => [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] + + # emit event to signal that an asset's balance is requested + emit.ACCOUNT_VAULT_BEFORE_GET_BALANCE_EVENT + # => [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] + + # get the asset balance + exec.asset_vault::get_balance + # => [balance] +end + +#! Returns a boolean indicating whether the non-fungible asset is present in the current account's vault. +#! +#! Inputs: [ASSET] +#! Outputs: [has_asset] +#! +#! Where: +#! - ASSET is the non-fungible asset of interest. +#! - has_asset is a boolean indicating whether the account vault has the asset of interest. +#! +#! Panics if: +#! - the ASSET is a fungible asset. +export.has_non_fungible_asset + # get the vault root + exec.memory::get_account_vault_root_ptr movdn.4 + # => [ASSET, vault_root_ptr] + + # emit event to signal that an asset's presence is being checked + emit.ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET_EVENT + # => [ASSET, vault_root_ptr] + + # check if the account vault has the non-fungible asset + exec.asset_vault::has_non_fungible_asset + # => [has_asset] +end + + # DATA LOADERS # ================================================================================================= @@ -979,7 +1040,7 @@ end #! - the number of account storage slots exceeded the maximum limit of 255. #! - the computed account storage commitment does not match the provided account storage commitment. export.load_foreign_account - emit.ACCOUNT_BEFORE_FOREIGN_LOAD + emit.ACCOUNT_BEFORE_FOREIGN_LOAD_EVENT # => [account_id_prefix, account_id_suffix] # construct the word with account ID to load the core account data from the advice map diff --git a/crates/miden-lib/src/transaction/events.rs b/crates/miden-lib/src/transaction/events.rs index d9f0a5d940..a255f1af54 100644 --- a/crates/miden-lib/src/transaction/events.rs +++ b/crates/miden-lib/src/transaction/events.rs @@ -16,10 +16,14 @@ const ACCOUNT_VAULT_AFTER_ADD_ASSET: u32 = 0x2_0001; // 131073 const ACCOUNT_VAULT_BEFORE_REMOVE_ASSET: u32 = 0x2_0002; // 131074 const ACCOUNT_VAULT_AFTER_REMOVE_ASSET: u32 = 0x2_0003; // 131075 +const ACCOUNT_VAULT_BEFORE_GET_BALANCE: u32 = 0x2_0021; // 131105 + +const ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET: u32 = 0x2_0022; // 131106 + const ACCOUNT_STORAGE_BEFORE_SET_ITEM: u32 = 0x2_0004; // 131076 const ACCOUNT_STORAGE_AFTER_SET_ITEM: u32 = 0x2_0005; // 131077 -const ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT: u32 = 0x2_001f; // 131103 +const ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM: u32 = 0x2_001f; // 131103 const ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM: u32 = 0x2_0006; // 131078 const ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM: u32 = 0x2_0007; // 131079 @@ -77,10 +81,14 @@ pub enum TransactionEvent { AccountVaultBeforeRemoveAsset = ACCOUNT_VAULT_BEFORE_REMOVE_ASSET, AccountVaultAfterRemoveAsset = ACCOUNT_VAULT_AFTER_REMOVE_ASSET, + AccountVaultBeforeGetBalanceEvent = ACCOUNT_VAULT_BEFORE_GET_BALANCE, + + AccountVaultBeforeHasNonFungibleAssetEvent = ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET, + AccountStorageBeforeSetItem = ACCOUNT_STORAGE_BEFORE_SET_ITEM, AccountStorageAfterSetItem = ACCOUNT_STORAGE_AFTER_SET_ITEM, - AccountStorageBeforeGetMapItem = ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT, + AccountStorageBeforeGetMapItem = ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM, AccountStorageBeforeSetMapItem = ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM, AccountStorageAfterSetMapItem = ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM, @@ -158,10 +166,18 @@ impl TryFrom for TransactionEvent { }, ACCOUNT_VAULT_AFTER_REMOVE_ASSET => Ok(TransactionEvent::AccountVaultAfterRemoveAsset), + ACCOUNT_VAULT_BEFORE_GET_BALANCE => { + Ok(TransactionEvent::AccountVaultBeforeGetBalanceEvent) + }, + + ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET => { + Ok(TransactionEvent::AccountVaultBeforeHasNonFungibleAssetEvent) + }, + ACCOUNT_STORAGE_BEFORE_SET_ITEM => Ok(TransactionEvent::AccountStorageBeforeSetItem), ACCOUNT_STORAGE_AFTER_SET_ITEM => Ok(TransactionEvent::AccountStorageAfterSetItem), - ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT => { + ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM => { Ok(TransactionEvent::AccountStorageBeforeGetMapItem) }, diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index 90bfd4d6bd..4030ec3489 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -48,9 +48,9 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_remove_asset word!("0xc5ac5f912281dca3f8f778e713f564f71695af68d094937072a303ea0d8385c0"), // account_get_balance - word!("0xb4e92ae0196ca128a451e40dd8a5ff56c13919efa67f63dca488214fbba3ffbc"), + word!("0x010bf86e092ec9d35ab4778d7bb8a5c8659594b64409f682cf860cd48114d59c"), // account_has_non_fungible_asset - word!("0x62776e8641d241f404724cf115c416e4918b75ced3625a5cd21d487fd7aef68b"), + word!("0xc976f1583f11533cd4887d03bd4d82d8051c0a930f926f67ce8b6e9cb0af34a1"), // account_compute_delta_commitment word!("0xb4589587f804af8205f9179ec6b58814d78171a3dc6d78cbf470db512ec25129"), // account_was_procedure_called diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index 1ae29b4e5e..559dcaed41 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -27,6 +27,7 @@ use miden_objects::account::{ Account, AccountBuilder, AccountComponent, + AccountId, AccountProcedureInfo, AccountStorage, AccountStorageMode, @@ -35,6 +36,11 @@ use miden_objects::account::{ }; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::diagnostics::NamedSource; +use miden_objects::asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; +use miden_objects::testing::account_id::{ + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, + ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, +}; use miden_objects::testing::storage::STORAGE_LEAVES_2; use miden_objects::transaction::AccountInputs; use miden_processor::{AdviceInputs, Felt}; @@ -643,6 +649,126 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { Ok(()) } +/// Test that a foreign account can get the balance of a fungible asset and check the presence of a +/// non-fungible asset. +#[test] +fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Result<()> { + let fungible_faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1)?; + let non_fungible_faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET)?; + + // Create two different assets. + let fungible_asset = Asset::Fungible(FungibleAsset::new(fungible_faucet_id, 1)?); + let non_fungible_asset = Asset::NonFungible(NonFungibleAsset::new( + &NonFungibleAssetDetails::new(non_fungible_faucet_id.prefix(), vec![1, 2, 3])?, + )?); + + let foreign_account_code_source = format!( + " + use.miden::account + + export.get_asset_balance + # get balance of first asset + push.{fungible_faucet_id_suffix}.{fungible_faucet_id_prefix} + exec.account::get_balance + # => [balance] + + # check presence of non fungible asset + push.{non_fungible_asset_word} + exec.account::has_non_fungible_asset + # => [has_asset, balance] + + # add the balance and the bool + add + # => [has_asset_balance] + + # keep only the result on stack + swap drop + # => [has_asset_balance] + end + ", + fungible_faucet_id_prefix = fungible_faucet_id.prefix().as_felt(), + fungible_faucet_id_suffix = fungible_faucet_id.suffix(), + non_fungible_asset_word = Word::from(non_fungible_asset), + ); + + let source_manager = Arc::new(DefaultSourceManager::default()); + let foreign_account_component = AccountComponent::compile( + NamedSource::new("foreign_account_code", foreign_account_code_source), + TransactionKernel::with_kernel_library(source_manager.clone()), + vec![], + )? + .with_supports_all_types(); + + let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(foreign_account_component.clone()) + .with_assets(vec![fungible_asset, non_fungible_asset]) + .build_existing()?; + + let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(MockAccountComponent::with_empty_slots()) + .storage_mode(AccountStorageMode::Public) + .build_existing()?; + + let mut mock_chain = + MockChainBuilder::with_accounts([native_account.clone(), foreign_account.clone()])? + .build()?; + mock_chain.prove_next_block()?; + + let code = format!( + " + use.std::sys + + use.miden::tx + use.miden::account + + begin + # Get the added balance of two assets from foreign account + # pad the stack for the `execute_foreign_procedure` execution + padw padw padw push.0.0.0 + # => [pad(15)] + + # get the hash of the `get_asset_balance` procedure + procref.::foreign_account_code::get_asset_balance + + # push the foreign account ID + push.{foreign_suffix}.{foreign_prefix} + # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, pad(15)] + + exec.tx::execute_foreign_procedure + # => [has_asset_balance] + + # assert that the non fungible asset exists and the fungible asset has balance 1 + push.2 assert_eq.err=\"Total balance should be 2\" + # => [] + + # truncate the stack + exec.sys::truncate_stack + end + ", + foreign_prefix = foreign_account.id().prefix().as_felt(), + foreign_suffix = foreign_account.id().suffix(), + ); + + let tx_script = ScriptBuilder::with_source_manager(source_manager.clone()) + .with_dynamically_linked_library(foreign_account_component.library())? + .compile_tx_script(code)?; + + let foreign_account_inputs = mock_chain.get_foreign_account_inputs(foreign_account.id())?; + + mock_chain + .build_tx_context(native_account.id(), &[], &[])? + .foreign_accounts([foreign_account_inputs]) + .enable_lazy_loading() + .tx_script(tx_script) + .with_source_manager(source_manager) + .build()? + .execute_blocking()?; + + Ok(()) +} + // NESTED FPI TESTS // ================================================================================================ diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 41b781b603..fd69320781 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -290,43 +290,10 @@ impl TransactionContextBuilder { // merkle paths of assets and storage maps, in order to test lazy loading. // Otherwise, load the full account. let tx_inputs = if self.is_lazy_loading_enabled { - let (account, block_header, partial_blockchain, input_notes) = tx_inputs.into_parts(); - // Construct a partial vault that tracks the empty word, but none of the assets - // that are actually in the asset tree. That way, the partial vault has the same - // root as the full vault, but will not add any relevant merkle paths to the - // merkle store, which will test lazy loading of assets. + let (_account, block_header, partial_blockchain, input_notes) = tx_inputs.into_parts(); // Note that we use self.account instead of account, because we cannot do the same // operation on a partial vault. - let mut partial_vault = PartialVault::default(); - partial_vault.add(self.account.vault().open(Word::empty()).into())?; - - // Construct a partial storage that tracks the empty word in all storage maps, but none - // of the other keys, following the same rationale as the partial vault above. - let storage_header = self.account.storage().to_header(); - let storage_maps = - self.account.storage().slots().iter().filter_map( - |storage_slot| match storage_slot { - StorageSlot::Map(storage_map) => { - let mut partial_storage_map = PartialStorageMap::default(); - partial_storage_map - .add(storage_map.open(&Word::empty())) - .expect("adding the first proof should never error"); - Some(partial_storage_map) - }, - _ => None, - }, - ); - let partial_storage = PartialStorage::new(storage_header, storage_maps) - .expect("provided storage maps should match storage header"); - - let account = PartialAccount::new( - account.id(), - account.nonce(), - account.code().clone(), - partial_storage, - partial_vault, - None, - )?; + let account = minimal_partial_account(&self.account)?; TransactionInputs::new(account, block_header, partial_blockchain, input_notes)? } else { @@ -387,3 +354,41 @@ impl Default for TransactionContextBuilder { Self::with_existing_mock_account() } } + +/// Creates a minimal [`PartialAccount`] from the provided full [`Account`]. +fn minimal_partial_account(account: &Account) -> anyhow::Result { + // Construct a partial vault that tracks the empty word, but none of the assets + // that are actually in the asset tree. That way, the partial vault has the same + // root as the full vault, but will not add any relevant merkle paths to the + // merkle store, which will test lazy loading of assets. + let mut partial_vault = PartialVault::default(); + partial_vault.add(account.vault().open(Word::empty()).into())?; + + // Construct a partial storage that tracks the empty word in all storage maps, but none + // of the other keys, following the same rationale as the partial vault above. + let storage_header = account.storage().to_header(); + let storage_maps = + account.storage().slots().iter().filter_map(|storage_slot| match storage_slot { + StorageSlot::Map(storage_map) => { + let mut partial_storage_map = PartialStorageMap::default(); + partial_storage_map + .add(storage_map.open(&Word::empty())) + .expect("adding the first proof should never error"); + Some(partial_storage_map) + }, + _ => None, + }); + let partial_storage = PartialStorage::new(storage_header, storage_maps) + .expect("provided storage maps should match storage header"); + + let account = PartialAccount::new( + account.id(), + account.nonce(), + account.code().clone(), + partial_storage, + partial_vault, + None, + )?; + + Ok(account) +} diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index cab0246980..d909045192 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -227,20 +227,48 @@ impl DataStore for TransactionContext { vault_root: Word, vault_key: Word, ) -> impl FutureMaybeSend> { - assert_eq!( - account_id, - self.account.id(), - "only native account vault witnesses can be requested (for now)" - ); - assert_eq!( - vault_root, - self.account.vault().root(), - "vault root should match the native account's root (for now)" - ); - - let asset_witness = self.account().vault().open(vault_key); - - async { Ok(asset_witness) } + async move { + if account_id == self.account().id() { + if self.account().vault().root() != vault_root { + return Err(DataStoreError::other(format!( + "native account {account_id} has vault root {} but {vault_root} was requested", + self.account().vault().root() + ))); + } + + Ok(self.account().vault().open(vault_key)) + } else { + let foreign_account_inputs = self + .foreign_account_inputs + .iter() + .find_map( + |(id, account_inputs)| { + if account_id == *id { Some(account_inputs) } else { None } + }, + ) + .ok_or_else(|| { + DataStoreError::other(format!( + "failed to find foreign account {account_id} in foreign account inputs" + )) + })?; + + if foreign_account_inputs.account().vault().root() != vault_root { + return Err(DataStoreError::other(format!( + "foreign account {account_id} has vault root {} but {vault_root} was requested", + foreign_account_inputs.account().vault().root() + ))); + } + + foreign_account_inputs.account().vault().open(vault_key).map_err(|err| { + DataStoreError::other_with_source( + format!( + "failed to open vault_key {vault_key} in foreign account {account_id}" + ), + err, + ) + }) + } + } } fn get_storage_map_witness( @@ -249,32 +277,61 @@ impl DataStore for TransactionContext { map_root: Word, map_key: Word, ) -> impl FutureMaybeSend> { - assert_eq!( - account_id, - self.account.id(), - "only native account storage map witnesses can be requested (for now)" - ); - async move { - // Iterate the account storage to find the map with the requested root. - let storage_map = self - .account() - .storage() - .slots() - .iter() - .find_map(|slot| match slot { - StorageSlot::Map(storage_map) if storage_map.root() == map_root => { - Some(storage_map) - }, - _ => None, - }) - .ok_or_else(|| { - DataStoreError::other(format!( - "failed to find storage map with root {map_root} in account storage" - )) + if account_id == self.account().id() { + // Iterate the account storage to find the map with the requested root. + let storage_map = self + .account() + .storage() + .slots() + .iter() + .find_map(|slot| match slot { + StorageSlot::Map(storage_map) if storage_map.root() == map_root => { + Some(storage_map) + }, + _ => None, + }) + .ok_or_else(|| { + DataStoreError::other(format!( + "failed to find storage map with root {map_root} in account storage" + )) + })?; + + Ok(storage_map.open(&map_key)) + } else { + let foreign_account_inputs = self + .foreign_account_inputs + .iter() + .find_map( + |(id, account_inputs)| { + if account_id == *id { Some(account_inputs) } else { None } + }, + ) + .ok_or_else(|| { + DataStoreError::other(format!( + "failed to find foreign account {account_id} in foreign account inputs" + )) + })?; + + let map = foreign_account_inputs + .account() + .storage() + .maps() + .find(|map| map.root() == map_root) + .ok_or_else(|| { + DataStoreError::other(format!( + "failed to find storage map with root {map_root} in foreign account {account_id}" + )) + })?; + + let smt_proof = map.open(&map_key).map_err(|err| { + DataStoreError::other_with_source(format!( + "failed to open {map_key} in storage map of foreign account {account_id}" + ), err) })?; - Ok(storage_map.open(&map_key)) + Ok(StorageMapWitness::new(smt_proof)) + } } } } diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 9406292bcc..0bb17b32d7 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -3,13 +3,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_lib::transaction::{TransactionAdviceInputs, TransactionEvent}; -use miden_objects::account::{ - AccountCode, - AccountDelta, - AccountId, - PartialAccount, - StorageSlotType, -}; +use miden_objects::account::{AccountCode, AccountDelta, AccountId, PartialAccount}; use miden_objects::assembly::debuginfo::Location; use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; use miden_objects::asset::{Asset, AssetWitness, FungibleAsset}; @@ -277,41 +271,14 @@ where /// Handles a request for a storage map witness by querying the data store for a merkle path. /// - /// Note that we request witnesses against the initial map root for native accounts. See also + /// Note that we request witnesses against the _initial_ map root of the accounts. See also /// [`Self::on_account_vault_asset_witness_requested`] for more on this topic. async fn on_account_storage_map_witness_requested( &self, current_account_id: AccountId, - slot_index: usize, - _map_root: Word, + map_root: Word, map_key: Word, ) -> Result, TransactionKernelError> { - // For now, we only support getting witnesses for the native account, so return early if the - // requested account is not the native one. - if current_account_id != self.base_host.initial_account_header().id() { - return Ok(Vec::new()); - } - - // For native accounts, we have to request witnesses against the initial root instead of the - // _current_ one, since the data store only has witnesses for initial one. - let map_root = { - let (slot_type, slot_value) = - self.base_host.initial_account_storage_header().slot(slot_index).map_err( - |err| { - TransactionKernelError::other_with_source( - "failed to access storage map in storage header", - err, - ) - }, - )?; - if *slot_type != StorageSlotType::Map { - return Err(TransactionKernelError::other(format!( - "expected map slot type at slot index {slot_index}" - ))); - } - *slot_value - }; - let storage_map_witness = self .base_host .store() @@ -361,19 +328,19 @@ where /// the merkle store. Note that B' is in the store because the transaction inserted it into the /// merkle store as part of updating E, not because we inserted it. B is present in the store, /// but is simply ignored for the purpose of verifying G's inclusion. + /// + /// ## Foreign Accounts + /// + /// Foreign accounts are read-only and so they cannot change throughout transaction execution. + /// This means their _current_ vault root is always equivalent to their _initial_ vault root. + /// So, for foreign accounts, just like for the native account, we also always request + /// witnesses for the initial vault root. async fn on_account_vault_asset_witness_requested( &self, current_account_id: AccountId, - _vault_root: Word, + vault_root: Word, asset: Asset, ) -> Result, TransactionKernelError> { - // For now, we only support getting witnesses for the native account, so return early if the - // requested account is not the native one. - if current_account_id != self.base_host.initial_account_header().id() { - return Ok(Vec::new()); - } - - let vault_root = self.base_host.initial_account_header().vault_root(); let vault_key = asset.vault_key(); let asset_witness = self .base_host @@ -485,16 +452,10 @@ where .map_err(EventError::from), TransactionEventData::AccountStorageMapWitness { current_account_id, - slot_index, map_root, map_key, } => self - .on_account_storage_map_witness_requested( - current_account_id, - slot_index, - map_root, - map_key, - ) + .on_account_storage_map_witness_requested(current_account_id, map_root, map_key) .await .map_err(EventError::from), } diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index a78cb66526..e4e2fad51a 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -36,6 +36,7 @@ use miden_objects::account::{ AccountStorageHeader, PartialAccount, StorageMap, + StorageSlotType, }; use miden_objects::asset::{Asset, AssetVault, FungibleAsset}; use miden_objects::note::NoteId; @@ -233,21 +234,29 @@ where } TransactionEvent::AccountVaultBeforeAddAsset => { - Self::on_account_vault_before_add_or_remove_asset(process) + self.on_account_vault_before_add_or_remove_asset(process) }, TransactionEvent::AccountVaultAfterAddAsset => { self.on_account_vault_after_add_asset(process).map(|_| TransactionEventHandling::Handled(Vec::new())) }, TransactionEvent::AccountVaultBeforeRemoveAsset => { - Self::on_account_vault_before_add_or_remove_asset(process) + self.on_account_vault_before_add_or_remove_asset(process) }, TransactionEvent::AccountVaultAfterRemoveAsset => { self.on_account_vault_after_remove_asset(process).map(|_| TransactionEventHandling::Handled(Vec::new())) }, + TransactionEvent::AccountVaultBeforeGetBalanceEvent => { + self.on_account_vault_before_get_balance(process) + }, + + TransactionEvent::AccountVaultBeforeHasNonFungibleAssetEvent => { + self.on_account_vault_before_has_non_fungible_asset(process) + } + TransactionEvent::AccountStorageBeforeGetMapItem => { - Self::on_account_storage_before_get_map_item(process) + self.on_account_storage_before_get_map_item(process) } TransactionEvent::AccountStorageBeforeSetItem => Ok(TransactionEventHandling::Handled(Vec::new())), @@ -256,7 +265,7 @@ where }, TransactionEvent::AccountStorageBeforeSetMapItem => { - Self::on_account_storage_before_set_map_item(process) + self.on_account_storage_before_set_map_item(process) }, TransactionEvent::AccountStorageAfterSetMapItem => { self.on_account_storage_after_set_map_item(process).map(|_| TransactionEventHandling::Handled(Vec::new())) @@ -596,13 +605,19 @@ where /// /// Expected stack state: `[KEY, ROOT, index]` pub fn on_account_storage_before_get_map_item( + &self, process: &ProcessState, ) -> Result { let map_key = process.get_stack_word(0); - let map_root = process.get_stack_word(1); + let current_map_root = process.get_stack_word(1); let slot_index = process.get_stack_item(8); - Self::on_account_storage_before_get_or_set_map_item(slot_index, map_root, map_key, process) + self.on_account_storage_before_get_or_set_map_item( + slot_index, + current_map_root, + map_key, + process, + ) } /// Checks if the necessary witness for accessing the map item is already in the merkle store, @@ -610,6 +625,7 @@ where /// /// Expected stack state: `[index, KEY, NEW_VALUE, OLD_ROOT]` pub fn on_account_storage_before_set_map_item( + &self, process: &ProcessState, ) -> Result { let slot_index = process.get_stack_item(0); @@ -619,20 +635,27 @@ where process.get_stack_item(2), process.get_stack_item(1), ]); - let map_root = Word::from([ + let current_map_root = Word::from([ process.get_stack_item(12), process.get_stack_item(11), process.get_stack_item(10), process.get_stack_item(9), ]); - Self::on_account_storage_before_get_or_set_map_item(slot_index, map_root, map_key, process) + + self.on_account_storage_before_get_or_set_map_item( + slot_index, + current_map_root, + map_key, + process, + ) } /// Checks if the necessary witness for accessing the map item is already in the merkle store, /// and if not, extracts all necessary data for requesting it. fn on_account_storage_before_get_or_set_map_item( + &self, slot_index: Felt, - map_root: Word, + current_map_root: Word, map_key: Word, process: &ProcessState, ) -> Result { @@ -641,17 +664,43 @@ where let leaf_index = StorageMap::hashed_map_key_to_leaf_index(hashed_map_key); if Self::advice_provider_has_merkle_path::<{ StorageMap::DEPTH }>( - process, map_root, leaf_index, + process, + current_map_root, + leaf_index, )? { // If the merkle path is already in the store there is nothing to do. Ok(TransactionEventHandling::Handled(Vec::new())) } else { + // For the native account we need to explicitly request the initial map root, while for + // foreign accounts the current map root is always the initial one. + let map_root = if current_account_id == self.initial_account_header().id() { + // For native accounts, we have to request witnesses against the initial root + // instead of the _current_ one, since the data store only has + // witnesses for initial one. + let (slot_type, slot_value) = self + .initial_account_storage_header() + // Slot index should always fit into a usize. + .slot(slot_index.as_int() as usize) + .map_err(|err| { + TransactionKernelError::other_with_source( + "failed to access storage map in storage header", + err, + ) + })?; + if *slot_type != StorageSlotType::Map { + return Err(TransactionKernelError::other(format!( + "expected map slot type at slot index {slot_index}" + ))); + } + *slot_value + } else { + current_map_root + }; + // If the merkle path is not in the store return the data to request it. Ok(TransactionEventHandling::Unhandled( TransactionEventData::AccountStorageMapWitness { current_account_id, - // Slot index should always fit into a usize. - slot_index: slot_index.as_int() as usize, map_root, map_key, }, @@ -744,6 +793,7 @@ where /// /// Expected stack state: `[ASSET, account_vault_root_ptr]` pub fn on_account_vault_before_add_or_remove_asset( + &self, process: &ProcessState, ) -> Result { let asset: Asset = process.get_stack_word(0).try_into().map_err(|source| { @@ -759,7 +809,7 @@ where "vault root ptr should fit into a u32, but was {vault_root_ptr}" )) })?; - let vault_root = process + let current_vault_root = process .get_mem_word(process.ctx(), vault_root_ptr) .map_err(|_err| { TransactionKernelError::other(format!( @@ -772,24 +822,7 @@ where )) })?; - let current_account_id = Self::get_current_account_id(process)?; - let leaf_index = AssetVault::vault_key_to_leaf_index(asset.vault_key()); - - if Self::advice_provider_has_merkle_path::<{ AssetVault::DEPTH }>( - process, vault_root, leaf_index, - )? { - // If the merkle path is already in the store there is nothing to do. - Ok(TransactionEventHandling::Handled(Vec::new())) - } else { - // If the merkle path is not in the store return the data to request it. - Ok(TransactionEventHandling::Unhandled( - TransactionEventData::AccountVaultAssetWitness { - current_account_id, - vault_root, - asset, - }, - )) - } + self.on_account_vault_asset_accessed(process, asset, current_vault_root) } /// Extracts the asset that is being removed from the account's vault from the process state @@ -814,6 +847,98 @@ where Ok(()) } + /// Checks if the necessary witness for accessing the asset is already in the merkle store, + /// and if not, extracts all necessary data for requesting it. + /// + /// Expected stack state: `[faucet_id_prefix, faucet_id_suffix, vault_root_ptr]` + pub fn on_account_vault_before_get_balance( + &self, + process: &ProcessState, + ) -> Result { + let stack_top = process.get_stack_word(0); + let faucet_id = AccountId::try_from([stack_top[3], stack_top[2]]).map_err(|err| { + TransactionKernelError::other_with_source( + "failed to convert faucet ID word into faucet ID", + err, + ) + })?; + let vault_root_ptr = stack_top[1]; + let vault_root = Self::get_vault_root(process, vault_root_ptr)?; + + // Construct the fungible asset so we can easily fetch the vault key. + // TODO: Replace this once we have a AssetKey type that can be constructed from a faucet ID + // directly: https://github.com/0xMiden/miden-base/issues/1890. + let asset = FungibleAsset::new(faucet_id, 0).map_err(|err| { + TransactionKernelError::other_with_source( + "provided faucet ID is not valid for fungible assets", + err, + ) + })?; + + self.on_account_vault_asset_accessed(process, asset.into(), vault_root) + } + + /// Checks if the necessary witness for accessing the asset is already in the merkle store, + /// and if not, extracts all necessary data for requesting it. + /// + /// Expected stack state: `[ASSET, vault_root_ptr]` + pub fn on_account_vault_before_has_non_fungible_asset( + &self, + process: &ProcessState, + ) -> Result { + let asset_word = process.get_stack_word(0); + let asset = Asset::try_from(asset_word).map_err(|err| { + TransactionKernelError::other_with_source("provided asset is not a valid asset", err) + })?; + + let vault_root_ptr = process.get_stack_item(4); + let vault_root = Self::get_vault_root(process, vault_root_ptr)?; + + self.on_account_vault_asset_accessed(process, asset, vault_root) + } + + /// Checks if the necessary witness for accessing the provided asset is already in the merkle + /// store, and if not, extracts all necessary data for requesting it. + fn on_account_vault_asset_accessed( + &self, + process: &ProcessState, + asset: Asset, + current_vault_root: Word, + ) -> Result { + let leaf_index = AssetVault::vault_key_to_leaf_index(asset.vault_key()); + let current_account_id = Self::get_current_account_id(process)?; + + // Note that we check whether a merkle path for the current vault root is present, not + // necessarily for the root we are going to request. This is because the end goal is to + // enable access to an asset against the current vault root, and so if this + // condition is already satisfied, there is nothing to request. + if Self::advice_provider_has_merkle_path::<{ AssetVault::DEPTH }>( + process, + current_vault_root, + leaf_index, + )? { + // If the merkle path is already in the store there is nothing to do. + Ok(TransactionEventHandling::Handled(Vec::new())) + } else { + // For the native account we need to explicitly request the initial vault root, while + // for foreign accounts the current vault root is always the initial one. + let vault_root = if current_account_id == self.initial_account_header().id() { + self.initial_account_header().vault_root() + } else { + current_vault_root + }; + + // If the merkle path is not in the store return the data to request it. + Ok(TransactionEventHandling::Unhandled( + TransactionEventData::AccountVaultAssetWitness { + current_account_id, + vault_root, + asset, + }, + )) + } + } + // HELPER FUNCTIONS // -------------------------------------------------------------------------------------------- @@ -881,6 +1006,30 @@ where }) } + /// Returns the vault root at the provided pointer. + fn get_vault_root( + process: &ProcessState, + vault_root_ptr: Felt, + ) -> Result { + let vault_root_ptr = u32::try_from(vault_root_ptr).map_err(|_err| { + TransactionKernelError::other(format!( + "vault root ptr should fit into a u32, but was {vault_root_ptr}" + )) + })?; + process + .get_mem_word(process.ctx(), vault_root_ptr) + .map_err(|_err| { + TransactionKernelError::other(format!( + "vault root ptr {vault_root_ptr} is not word-aligned" + )) + })? + .ok_or_else(|| { + TransactionKernelError::other(format!( + "vault root ptr {vault_root_ptr} was not initialized" + )) + }) + } + /// Returns the number of storage slots initialized for the current account. /// /// # Errors @@ -1045,11 +1194,9 @@ pub(super) enum TransactionEventData { AccountStorageMapWitness { /// The account ID for whose storage a witness is requested. current_account_id: AccountId, - /// The index of the slot that contains the map root. - slot_index: usize, - /// The root of the storage map in the account. + /// The root of the storage map in the account at the beginning of the transaction. map_root: Word, - /// The unhashed map key for which a witness is requested. + /// The raw map key for which a witness is requested. map_key: Word, }, } From 43d0486be7e296ef6b83b2dc2a56c5a0a317bf27 Mon Sep 17 00:00:00 2001 From: Tomas Rodriguez Dala <43424983+tomyrd@users.noreply.github.com> Date: Mon, 22 Sep 2025 06:36:37 -0300 Subject: [PATCH 047/133] feat: add entry map to `PartialStorageMap` (#1878) * feat: add entry map to `PartialStorageMap` * chore: update CHANGELOG * feat: Add raw map keys to `StorageMapWitness` * feat: Use `BTreeMap` in witness and partial storage map * chore: Validate partial storage map entries on deserialization * fix: remove duplicate docs * feat: Add storage map test for proven tx * feat: Return `StorageMapWitness` from `PartialStorageMap::open` * chore: Rename `map` to `entries` in (partial) storage map and witness * chore: Unify storage map APIs and use "raw key" everywhere * chore: Rename `StorageMapWitness::as_proof` to `proof` * chore: Add test for `StorageMapWitness::new` with missing key --------- Co-authored-by: Ignacio Amigo Co-authored-by: Philipp Gackstatter --- CHANGELOG.md | 1 + .../src/account/delta/storage.rs | 6 +- .../src/account/storage/map/mod.rs | 56 +++++---- .../src/account/storage/map/partial.rs | 107 +++++++++++++---- .../src/account/storage/map/witness.rs | 113 +++++++++++++++--- .../src/account/storage/partial.rs | 8 +- crates/miden-objects/src/errors.rs | 2 + .../src/kernel_tests/tx/test_account.rs | 81 ++++++++++++- .../src/kernel_tests/tx/test_lazy_loading.rs | 4 +- .../miden-testing/src/tx_context/builder.rs | 4 +- .../miden-testing/src/tx_context/context.rs | 6 +- crates/miden-tx/src/prover/mod.rs | 5 +- 12 files changed, 311 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba4b659ec..c633fea88d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). - [BREAKING] Move `TransactionKernelError` to miden-tx ([#1859](https://github.com/0xMiden/miden-base/pull/1859)). +- [BREAKING] Changed `PartialStorageMap` to track the correct set of key+value pairings ([#1878](https://github.com/0xMiden/miden-base/pull/1878)). - Change terminology of "current note" to "active note" ([#1863](https://github.com/0xMiden/miden-base/issues/1863)). - [BREAKING] Move and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). - [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). diff --git a/crates/miden-objects/src/account/delta/storage.rs b/crates/miden-objects/src/account/delta/storage.rs index b2fbb8200d..ef271c97dd 100644 --- a/crates/miden-objects/src/account/delta/storage.rs +++ b/crates/miden-objects/src/account/delta/storage.rs @@ -292,13 +292,15 @@ impl StorageMapDelta { } /// Returns a reference to the updated entries in this storage map delta. + /// + /// Note that the returned key is the raw map key. pub fn entries(&self) -> &BTreeMap { &self.0 } /// Inserts an item into the storage map delta. - pub fn insert(&mut self, key: Word, value: Word) { - self.0.insert(LexicographicWord::new(key), value); + pub fn insert(&mut self, raw_key: Word, value: Word) { + self.0.insert(LexicographicWord::new(raw_key), value); } /// Returns true if storage map delta contains no updates. diff --git a/crates/miden-objects/src/account/storage/map/mod.rs b/crates/miden-objects/src/account/storage/map/mod.rs index 166adcc7d2..3032570eb2 100644 --- a/crates/miden-objects/src/account/storage/map/mod.rs +++ b/crates/miden-objects/src/account/storage/map/mod.rs @@ -42,11 +42,11 @@ pub const EMPTY_STORAGE_MAP_ROOT: Word = *EmptySubtreeRoots::entry(StorageMap::D pub struct StorageMap { /// The SMT where each key is the hashed original key. smt: Smt, - /// The entries of the map where the key is the original user-chosen one. + /// The entries of the map where the key is the raw user-chosen one. /// /// It is an invariant of this type that the map's entries are always consistent with the SMT's /// entries and vice-versa. - map: BTreeMap, + entries: BTreeMap, } impl StorageMap { @@ -66,7 +66,10 @@ impl StorageMap { /// /// All leaves in the returned tree are set to [Self::EMPTY_VALUE]. pub fn new() -> Self { - StorageMap { smt: Smt::new(), map: BTreeMap::new() } + StorageMap { + smt: Smt::new(), + entries: BTreeMap::new(), + } } /// Creates a new [`StorageMap`] from the provided key-value entries. @@ -94,12 +97,12 @@ impl StorageMap { } /// Creates a new [`StorageMap`] from the given map. For internal use. - fn from_btree_map(map: BTreeMap) -> Self { - let hashed_keys_iter = map.iter().map(|(key, value)| (Self::hash_key(*key), *value)); + fn from_btree_map(entries: BTreeMap) -> Self { + let hashed_keys_iter = entries.iter().map(|(key, value)| (Self::hash_key(*key), *value)); let smt = Smt::with_entries(hashed_keys_iter) .expect("btree maps should not contain duplicate keys"); - StorageMap { smt, map } + StorageMap { smt, entries } } // PUBLIC ACCESSORS @@ -112,16 +115,21 @@ impl StorageMap { /// Returns the value corresponding to the key or [`Self::EMPTY_VALUE`] if the key is not /// associated with a value. - pub fn get(&self, key: &Word) -> Word { - self.map.get(key).copied().unwrap_or_default() + pub fn get(&self, raw_key: &Word) -> Word { + self.entries.get(raw_key).copied().unwrap_or_default() } - /// Returns an opening of the leaf associated with `key`. + /// Returns an opening of the leaf associated with raw key. /// /// Conceptually, an opening is a Merkle path to the leaf, as well as the leaf itself. - pub fn open(&self, key: &Word) -> StorageMapWitness { - let key = Self::hash_key(*key); - StorageMapWitness::new(self.smt.open(&key)) + pub fn open(&self, raw_key: &Word) -> StorageMapWitness { + let hashed_map_key = Self::hash_key(*raw_key); + let smt_proof = self.smt.open(&hashed_map_key); + let value = self.entries.get(raw_key).copied().unwrap_or_default(); + + // SAFETY: The key value pair is guaranteed to be present in the provided proof since we + // open its hashed version and because of the guarantees of the storage map. + StorageMapWitness::new_unchecked(smt_proof, [(*raw_key, value)]) } // ITERATORS @@ -132,9 +140,11 @@ impl StorageMap { self.smt.leaves() // Delegate to Smt's leaves method } - /// Returns an iterator over the key value pairs of the map. + /// Returns an iterator over the key-value pairs in this storage map. + /// + /// Note that the returned key is the raw map key. pub fn entries(&self) -> impl Iterator { - self.map.iter() + self.entries.iter() } /// Returns an iterator over the inner nodes of the underlying [`Smt`]. @@ -149,15 +159,15 @@ impl StorageMap { /// [`Self::EMPTY_VALUE`] if no entry was previously present. /// /// If the provided `value` is [`Self::EMPTY_VALUE`] the entry will be removed. - pub fn insert(&mut self, key: Word, value: Word) -> Word { + pub fn insert(&mut self, raw_key: Word, value: Word) -> Word { if value == EMPTY_WORD { - self.map.remove(&key); + self.entries.remove(&raw_key); } else { - self.map.insert(key, value); + self.entries.insert(raw_key, value); } - let key = Self::hash_key(key); - self.smt.insert(key, value) // Delegate to Smt's insert method + let hashed_key = Self::hash_key(raw_key); + self.smt.insert(hashed_key, value) } /// Applies the provided delta to this account storage. @@ -172,12 +182,12 @@ impl StorageMap { /// Consumes the map and returns the underlying map of entries. pub fn into_entries(self) -> BTreeMap { - self.map + self.entries } /// Hashes the given key to get the key of the SMT. - pub fn hash_key(key: Word) -> Word { - Hasher::hash_elements(key.as_elements()) + pub fn hash_key(raw_key: Word) -> Word { + Hasher::hash_elements(raw_key.as_elements()) } // TODO: Replace with https://github.com/0xMiden/crypto/issues/515 once implemented. @@ -199,7 +209,7 @@ impl Default for StorageMap { impl Serializable for StorageMap { fn write_into(&self, target: &mut W) { - self.map.write_into(target); + self.entries.write_into(target); } fn get_size_hint(&self) -> usize { diff --git a/crates/miden-objects/src/account/storage/map/partial.rs b/crates/miden-objects/src/account/storage/map/partial.rs index 24b9aeca67..3f3633f677 100644 --- a/crates/miden-objects/src/account/storage/map/partial.rs +++ b/crates/miden-objects/src/account/storage/map/partial.rs @@ -1,3 +1,5 @@ +use alloc::collections::BTreeMap; + use miden_core::utils::{Deserializable, Serializable}; use miden_crypto::Word; use miden_crypto::merkle::{ @@ -11,6 +13,7 @@ use miden_crypto::merkle::{ }; use crate::account::{StorageMap, StorageMapWitness}; +use crate::utils::serde::{ByteReader, DeserializationError}; /// A partial representation of a [`StorageMap`], containing only proofs for a subset of the /// key-value pairs. @@ -18,41 +21,80 @@ use crate::account::{StorageMap, StorageMapWitness}; /// A partial storage map carries only the Merkle authentication data a transaction will need. /// Every included entry pairs a value with its proof, letting the transaction kernel verify reads /// (and prepare writes) without needing the complete tree. +/// +/// ## Guarantees +/// +/// This type guarantees that the raw key-value pairs it contains are all present in the +/// contained partial SMT. Note that the inverse is not necessarily true. The SMT may contain more +/// entries than the map because to prove inclusion of a given raw key A an +/// [`SmtLeaf::Multiple`] may be present that contains both keys hash(A) and hash(B). However, B may +/// not be present in the key-value pairs and this is a valid state. #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct PartialStorageMap { partial_smt: PartialSmt, + /// The entries of the map where the key is the raw user-chosen one. + /// + /// It is an invariant of this type that the map's entries are always consistent with the + /// partial SMT's entries and vice-versa. + entries: BTreeMap, } impl PartialStorageMap { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns a new instance of partial storage map with the specified partial SMT. - pub fn new(partial_smt: PartialSmt) -> Self { - PartialStorageMap { partial_smt } + /// Returns a new instance of partial storage map with the specified partial SMT and stored + /// entries. + pub fn from_witnesses( + witnesses: impl IntoIterator, + ) -> Result { + let mut partial_smt = PartialSmt::default(); + let mut map = BTreeMap::new(); + + for witness in witnesses.into_iter() { + map.extend(witness.entries()); + let smt_proof = SmtProof::from(witness); + partial_smt.add_proof(smt_proof)?; + } + + Ok(PartialStorageMap { partial_smt, entries: map }) } + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns a reference to the underlying [`PartialSmt`]. pub fn partial_smt(&self) -> &PartialSmt { &self.partial_smt } + /// Returns the root of the underlying [`PartialSmt`]. pub fn root(&self) -> Word { self.partial_smt.root() } - /// Returns an opening of the leaf associated with `key`. + /// Returns the value corresponding to the key or [`Word::empty`] if the key is not + /// associated with a value. + pub fn get(&self, raw_key: &Word) -> Word { + self.entries.get(raw_key).copied().unwrap_or_default() + } + + /// Returns an opening of the leaf associated with the raw key. /// /// Conceptually, an opening is a Merkle path to the leaf, as well as the leaf itself. - /// The key needs to be hashed to have a behavior in line with [`StorageMap`]. For more details - /// as to why this is needed, refer to the docs for that struct. /// /// # Errors /// /// Returns an error if: /// - the key is not tracked by this partial storage map. - pub fn open(&self, key: &Word) -> Result { - let key = StorageMap::hash_key(*key); - self.partial_smt.open(&key) + pub fn open(&self, raw_key: &Word) -> Result { + let hashed_key = StorageMap::hash_key(*raw_key); + let smt_proof = self.partial_smt.open(&hashed_key)?; + let value = self.entries.get(raw_key).copied().unwrap_or_default(); + + // SAFETY: The key value pair is guaranteed to be present in the provided proof since we + // open its hashed version and because of the guarantees of the partial storage map. + Ok(StorageMapWitness::new_unchecked(smt_proof, [(*raw_key, value)])) } // ITERATORS @@ -63,9 +105,11 @@ impl PartialStorageMap { self.partial_smt.leaves() } - /// Returns an iterator over the key value pairs of the map. - pub fn entries(&self) -> impl Iterator { - self.partial_smt.entries().copied() + /// Returns an iterator over the key-value pairs in this storage map. + /// + /// Note that the returned key is the raw map key. + pub fn entries(&self) -> impl Iterator { + self.entries.iter() } /// Returns an iterator over the inner nodes of the underlying [`PartialSmt`]. @@ -76,37 +120,48 @@ impl PartialStorageMap { // MUTATORS // -------------------------------------------------------------------------------------------- - /// Adds a [`StorageMapWitness`] to this [`PartialStorageMap`]. + /// Adds a [`StorageMapWitness`] for the specific key-value pair to this [`PartialStorageMap`]. pub fn add(&mut self, witness: StorageMapWitness) -> Result<(), MerkleError> { + self.entries.extend(witness.entries().map(|(key, value)| (*key, *value))); self.partial_smt.add_proof(SmtProof::from(witness)) } } impl From for PartialStorageMap { fn from(value: StorageMap) -> Self { - let v = value.smt; - - PartialStorageMap { partial_smt: v.into() } - } -} + let smt = value.smt; + let map = value.entries; -impl From for PartialStorageMap { - fn from(partial_smt: PartialSmt) -> Self { - PartialStorageMap { partial_smt } + PartialStorageMap { partial_smt: smt.into(), entries: map } } } impl Serializable for PartialStorageMap { fn write_into(&self, target: &mut W) { target.write(&self.partial_smt); + target.write_usize(self.entries.len()); + target.write_many(self.entries.keys()); } } impl Deserializable for PartialStorageMap { - fn read_from( - source: &mut R, - ) -> Result { - let storage: PartialSmt = source.read()?; - Ok(PartialStorageMap { partial_smt: storage }) + fn read_from(source: &mut R) -> Result { + let mut map = BTreeMap::new(); + + let partial_smt: PartialSmt = source.read()?; + let num_entries: usize = source.read()?; + + for _ in 0..num_entries { + let key: Word = source.read()?; + let hashed_map_key = StorageMap::hash_key(key); + let value = partial_smt.get_value(&hashed_map_key).map_err(|err| { + DeserializationError::InvalidValue(format!( + "failed to find map key {key} in partial SMT: {err}" + )) + })?; + map.insert(key, value); + } + + Ok(PartialStorageMap { partial_smt, entries: map }) } } diff --git a/crates/miden-objects/src/account/storage/map/witness.rs b/crates/miden-objects/src/account/storage/map/witness.rs index 7cf4cc2875..8ae08d7ea4 100644 --- a/crates/miden-objects/src/account/storage/map/witness.rs +++ b/crates/miden-objects/src/account/storage/map/witness.rs @@ -1,50 +1,135 @@ +use alloc::collections::BTreeMap; + use miden_crypto::merkle::{InnerNodeInfo, SmtProof}; use crate::Word; +use crate::account::StorageMap; +use crate::errors::StorageMapError; /// A witness of an asset in a [`StorageMap`](super::StorageMap). /// /// It proves inclusion of a certain storage item in the map. +/// +/// ## Guarantees +/// +/// This type guarantees that the raw key-value pairs it contains are all present in the +/// contained SMT proof. Note that the inverse is not necessarily true. The proof may contain more +/// entries than the map because to prove inclusion of a given raw key A an +/// [`SmtLeaf::Multiple`](miden_crypto::merkle::SmtLeaf::Multiple) may be present that contains both +/// keys hash(A) and hash(B). However, B may not be present in the key-value pairs and this is a +/// valid state. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct StorageMapWitness(SmtProof); +pub struct StorageMapWitness { + proof: SmtProof, + /// The entries of the map where the key is the raw user-chosen one. + /// + /// It is an invariant of this type that the map's entries are always consistent with the SMT's + /// entries and vice-versa. + entries: BTreeMap, +} impl StorageMapWitness { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Creates a new [`StorageMapWitness`] from an SMT proof. - pub fn new(smt_proof: SmtProof) -> Self { - Self(smt_proof) + /// Creates a new [`StorageMapWitness`] from an SMT proof and a provided set of map keys. + /// + /// # Errors + /// + /// Returns an error if: + /// - Any of the map keys is not contained in the proof. + pub fn new( + proof: SmtProof, + raw_keys: impl IntoIterator, + ) -> Result { + let mut entries = BTreeMap::new(); + + for raw_key in raw_keys.into_iter() { + let hashed_map_key = StorageMap::hash_key(raw_key); + let value = + proof.get(&hashed_map_key).ok_or(StorageMapError::MissingKey { raw_key })?; + entries.insert(raw_key, value); + } + + Ok(Self { proof, entries }) + } + + /// Creates a new [`StorageMapWitness`] from an SMT proof and a set of raw key value pairs. + /// + /// # Warning + /// + /// This does not validate any of the guarantees of this type. See the type-level docs for more + /// details. + pub fn new_unchecked( + proof: SmtProof, + raw_key_values: impl IntoIterator, + ) -> Self { + Self { + proof, + entries: raw_key_values.into_iter().collect(), + } } // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Searches for a value in the witness with the given `map_key`. - pub fn find(&self, hashed_map_key: Word) -> Option { - self.entries() - .find_map(|(key, value)| if *key == hashed_map_key { Some(*value) } else { None }) + /// Returns a reference to the underlying [`SmtProof`]. + pub fn proof(&self) -> &SmtProof { + &self.proof + } + + /// Returns the value corresponding to the key or [`Word::empty`] if the key is not + /// associated with a value. + pub fn get(&self, raw_key: &Word) -> Word { + self.entries.get(raw_key).copied().unwrap_or_default() } /// Returns an iterator over the key-value pairs in this witness. /// - /// Note that the returned key is the hashed map key. + /// Note that the returned key is the raw map key. pub fn entries(&self) -> impl Iterator { - // Convert &(Word, Word) into (&Word, &Word) as it is more flexible. - self.0.leaf().entries().into_iter().map(|(key, value)| (key, value)) + self.entries.iter() } /// Returns an iterator over every inner node of this witness' merkle path. pub fn authenticated_nodes(&self) -> impl Iterator + '_ { - self.0 + self.proof .path() - .authenticated_nodes(self.0.leaf().index().value(), self.0.leaf().hash()) + .authenticated_nodes(self.proof.leaf().index().value(), self.proof.leaf().hash()) .expect("leaf index is u64 and should be less than 2^SMT_DEPTH") } } impl From for SmtProof { fn from(witness: StorageMapWitness) -> Self { - witness.0 + witness.proof + } +} + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + + use super::*; + use crate::account::StorageMap; + + #[test] + fn creating_witness_fails_on_missing_key() { + // Create a storage map with one key-value pair + let key1 = Word::from([1, 2, 3, 4u32]); + let value1 = Word::from([10, 20, 30, 40u32]); + let entries = [(key1, value1)]; + let storage_map = StorageMap::with_entries(entries).unwrap(); + + // Create a proof for the existing key + let proof = storage_map.open(&key1).into(); + + // Try to create a witness for a different key that's not in the proof + let missing_key = Word::from([5, 6, 7, 8u32]); + let result = StorageMapWitness::new(proof, [missing_key]); + + assert_matches!(result, Err(StorageMapError::MissingKey { raw_key }) => { + assert_eq!(raw_key, missing_key); + }); } } diff --git a/crates/miden-objects/src/account/storage/partial.rs b/crates/miden-objects/src/account/storage/partial.rs index f433e1b3f4..87cdee0884 100644 --- a/crates/miden-objects/src/account/storage/partial.rs +++ b/crates/miden-objects/src/account/storage/partial.rs @@ -131,12 +131,12 @@ impl Deserializable for PartialStorage { mod tests { use anyhow::Context; use miden_core::Word; - use miden_crypto::merkle::PartialSmt; use crate::account::{ AccountStorage, AccountStorageHeader, PartialStorage, + PartialStorageMap, StorageMap, StorageSlot, }; @@ -156,10 +156,10 @@ mod tests { // Create partial storage with validation of one map key let storage_header = AccountStorageHeader::from(&storage); let witness = map_1.open(&map_key_present); - let partial_smt = PartialSmt::from_proofs([witness.into()])?; - let partial_storage = PartialStorage::new(storage_header, [partial_smt.into()]) - .context("creating partial storage")?; + let partial_storage = + PartialStorage::new(storage_header, [PartialStorageMap::from_witnesses([witness])?]) + .context("creating partial storage")?; let retrieved_map = partial_storage.maps.get(&partial_storage.header.slot(0)?.1).unwrap(); assert!(retrieved_map.open(&map_key_absent).is_err()); diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index 0d2c90c145..4a7dcb4d95 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -335,6 +335,8 @@ pub enum AccountDeltaError { pub enum StorageMapError { #[error("map entries contain key {key} twice with values {value0} and {value1}")] DuplicateKey { key: Word, value0: Word, value1: Word }, + #[error("map key {raw_key} is not present in provided SMT proof")] + MissingKey { raw_key: Word }, } // BATCH ACCOUNT UPDATE ERROR diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 6f44b946a5..0023723f44 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -16,7 +16,7 @@ use miden_lib::testing::account_component::MockAccountComponent; use miden_lib::testing::mock_account::MockAccountExt; use miden_lib::transaction::TransactionKernel; use miden_lib::utils::ScriptBuilder; -use miden_objects::StarkField; +use miden_objects::account::delta::AccountUpdateDetails; use miden_objects::account::{ Account, AccountBuilder, @@ -41,8 +41,9 @@ use miden_objects::testing::account_id::{ }; use miden_objects::testing::storage::STORAGE_LEAVES_2; use miden_objects::transaction::{ExecutedTransaction, TransactionScript}; +use miden_objects::{LexicographicWord, StarkField}; use miden_processor::{EMPTY_WORD, ExecutionError, Word}; -use miden_tx::TransactionExecutorError; +use miden_tx::{LocalTransactionProver, TransactionExecutorError}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; @@ -965,6 +966,82 @@ fn test_compute_storage_commitment() -> anyhow::Result<()> { Ok(()) } +/// Tests that the storage map updates for a _new public_ account in an executed and proven +/// transaction match up. +/// +/// This is an interesting test case because for new public accounts the prover converts the partial +/// account into a full account as a temporary measure. Because of the additional hashing of map +/// keys in storage maps, this test ensures that the partial storage map is correctly converted into +/// a full storage map. If we end up representing new public accounts as account deltas, this test +/// can likely go away. +#[test] +fn proven_tx_storage_map_matches_executed_tx_for_new_account() -> anyhow::Result<()> { + // Build a public account so the proven transaction includes the account update. + let mock_slots = AccountStorage::mock_storage_slots(); + let mut account = AccountBuilder::new([1; 32]) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(Auth::IncrNonce) + .with_component(MockAccountComponent::with_slots(mock_slots.clone())) + .build()?; + + // The index of the mock map in account storage is 2. + let map_index = 2u8; + // Fetch a random existing key from the map. + let StorageSlot::Map(mock_map) = &mock_slots[map_index as usize] else { + panic!("expected map"); + }; + let existing_key = mock_map.entries().next().unwrap().0; + + let value0 = Word::from([3, 4, 5, 6u32]); + + let code = format!( + " + use.mock::account + + begin + # Update an existing key. + push.{value0} + push.{existing_key} + push.{map_index} + # => [index, KEY, VALUE] + call.account::set_map_item + + exec.::std::sys::truncate_stack + end + " + ); + + let builder = ScriptBuilder::with_mock_libraries()?; + let source_manager = builder.source_manager(); + let tx_script = builder.compile_tx_script(code)?; + + let tx = TransactionContextBuilder::new(account.clone()) + .tx_script(tx_script) + .with_source_manager(source_manager) + .build()? + .execute_blocking()?; + + let map_delta = tx.account_delta().storage().maps().get(&map_index).unwrap(); + assert_eq!( + map_delta.entries().get(&LexicographicWord::new(*existing_key)).unwrap(), + &value0 + ); + + let proven_tx = LocalTransactionProver::default().prove_dummy(tx.clone())?; + + let AccountUpdateDetails::New(new_account) = proven_tx.account_update().details() else { + panic!("expected delta"); + }; + + account.apply_delta(tx.account_delta())?; + + for (idx, slot) in new_account.storage().slots().iter().enumerate() { + assert_eq!(slot, &account.storage().slots()[idx], "slot {idx} did not match"); + } + + Ok(()) +} + // ACCOUNT VAULT TESTS // ================================================================================================ diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index 3139adb5a2..164fa69e96 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -179,7 +179,7 @@ fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { let non_existent_key = Word::from([5, 5, 5, 5u32]); assert!( - mock_map.open(&non_existent_key).find(non_existent_key).is_none(), + mock_map.open(&non_existent_key).get(&non_existent_key) == Word::empty(), "test setup requires that the non existent key does not exist" ); @@ -243,7 +243,7 @@ fn getting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { let non_existent_key = Word::from([5, 5, 5, 5u32]); assert!( - mock_map.open(&non_existent_key).find(non_existent_key).is_none(), + mock_map.open(&non_existent_key).get(&non_existent_key) == Word::empty(), "test setup requires that the non existent key does not exist" ); diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index fd69320781..f6bf33a5e5 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -371,8 +371,10 @@ fn minimal_partial_account(account: &Account) -> anyhow::Result account.storage().slots().iter().filter_map(|storage_slot| match storage_slot { StorageSlot::Map(storage_map) => { let mut partial_storage_map = PartialStorageMap::default(); + let key = Word::empty(); + let witness = storage_map.open(&key); partial_storage_map - .add(storage_map.open(&Word::empty())) + .add(witness) .expect("adding the first proof should never error"); Some(partial_storage_map) }, diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index d909045192..3b820c1c67 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -324,13 +324,11 @@ impl DataStore for TransactionContext { )) })?; - let smt_proof = map.open(&map_key).map_err(|err| { + map.open(&map_key).map_err(|err| { DataStoreError::other_with_source(format!( "failed to open {map_key} in storage map of foreign account {account_id}" ), err) - })?; - - Ok(StorageMapWitness::new(smt_proof)) + }) } } } diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index fbac6a713d..e2afb67a82 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -16,7 +16,6 @@ use miden_objects::account::{ }; use miden_objects::asset::{Asset, AssetVault}; use miden_objects::block::BlockNumber; -use miden_objects::crypto::merkle::SmtLeaf; use miden_objects::transaction::{ ExecutedTransaction, InputNote, @@ -234,9 +233,7 @@ fn partial_storage_to_full(partial_storage: PartialStorage) -> AccountStorage { fn partial_storage_map_to_storage_map(partial_storage_map: PartialStorageMap) -> StorageMap { let mut storage_map = StorageMap::new(); - for (key, value) in - partial_storage_map.leaves().map(|(_, leaf)| leaf).flat_map(SmtLeaf::entries) - { + for (key, value) in partial_storage_map.entries() { storage_map.insert(*key, *value); } storage_map From 413ffab9de4b2e5bc2d811f8419fb8c3caf97cd7 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Mon, 22 Sep 2025 21:44:10 +0300 Subject: [PATCH 048/133] Merge `bench-prover` and `bench-tx` crates (#1894) * refactor: merge executing and proving benchmarks * chore: update changelog * chore: remove outdated crates from makefile * refactor: update code, rename crate * chore: update makefile * chore: update readme * feat: expose the cycle count of the authentication procedure in the transaction benchmark * refactor: update benchmark to measure execution and exec+prove times * chore: update benchmark name, update values in bench-tx.json after merge * refactor: add concurrent feature to the bench-tx benchmark * refactor: update README, update folder names * refactor: remove clone from TransactionContext, use iter_batched * chore: update event values * chore: update event values in masm * chore: update README * chore: add concurrent features to README --------- Co-authored-by: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com> --- CHANGELOG.md | 3 +- Cargo.lock | 19 +-- Cargo.toml | 5 +- Makefile | 14 +- bin/bench-note-checker/Cargo.toml | 2 +- bin/bench-prover/Cargo.toml | 33 ----- bin/bench-prover/README.md | 92 ------------ bin/bench-prover/benches/benchmarks.rs | 43 ------ bin/bench-prover/src/bench_functions.rs | 107 -------------- bin/bench-prover/src/lib.rs | 8 -- bin/bench-prover/src/main.rs | 53 ------- bin/bench-prover/src/utils.rs | 119 ---------------- .../Cargo.toml | 18 ++- bin/bench-transaction/README.md | 67 +++++++++ bin/bench-transaction/bench-tx.json | 40 ++++++ bin/bench-transaction/src/context_setups.rs | 123 ++++++++++++++++ .../src/cycle_counting_benchmarks/mod.rs | 20 +++ .../src/cycle_counting_benchmarks}/utils.rs | 92 +++++------- bin/bench-transaction/src/lib.rs | 1 + bin/bench-transaction/src/main.rs | 54 ++++++++ .../src/time_counting_benchmarks/prove.rs | 131 ++++++++++++++++++ bin/bench-tx/README.md | 25 ---- bin/bench-tx/bench-tx.json | 23 --- bin/bench-tx/src/main.rs | 123 ---------------- .../asm/kernels/transaction/lib/epilogue.masm | 10 ++ crates/miden-lib/src/transaction/events.rs | 19 ++- .../src/transaction/executed_tx.rs | 4 + crates/miden-tx/src/host/mod.rs | 10 +- crates/miden-tx/src/host/tx_progress.rs | 17 +++ 29 files changed, 551 insertions(+), 724 deletions(-) delete mode 100644 bin/bench-prover/Cargo.toml delete mode 100644 bin/bench-prover/README.md delete mode 100644 bin/bench-prover/benches/benchmarks.rs delete mode 100644 bin/bench-prover/src/bench_functions.rs delete mode 100644 bin/bench-prover/src/lib.rs delete mode 100644 bin/bench-prover/src/main.rs delete mode 100644 bin/bench-prover/src/utils.rs rename bin/{bench-tx => bench-transaction}/Cargo.toml (65%) create mode 100644 bin/bench-transaction/README.md create mode 100644 bin/bench-transaction/bench-tx.json create mode 100644 bin/bench-transaction/src/context_setups.rs create mode 100644 bin/bench-transaction/src/cycle_counting_benchmarks/mod.rs rename bin/{bench-tx/src => bench-transaction/src/cycle_counting_benchmarks}/utils.rs (51%) create mode 100644 bin/bench-transaction/src/lib.rs create mode 100644 bin/bench-transaction/src/main.rs create mode 100644 bin/bench-transaction/src/time_counting_benchmarks/prove.rs delete mode 100644 bin/bench-tx/README.md delete mode 100644 bin/bench-tx/bench-tx.json delete mode 100644 bin/bench-tx/src/main.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c633fea88d..ac615fe6d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,8 +40,9 @@ - [BREAKING] Changed `PartialStorageMap` to track the correct set of key+value pairings ([#1878](https://github.com/0xMiden/miden-base/pull/1878)). - Change terminology of "current note" to "active note" ([#1863](https://github.com/0xMiden/miden-base/issues/1863)). - [BREAKING] Move and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). -- [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). +- Merge `bench-prover` into `bench-tx` crate ([#1894](https://github.com/0xMiden/miden-base/pull/1894)). - Replace `eqw` usages with `exec.word::test_eq` and `exec.word::eq`, remove `is_key_greater` and `is_key_less` from `link_map` module ([#1897](https://github.com/0xMiden/miden-base/pull/1897)). +- [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). ## 0.11.4 (2025-09-17) diff --git a/Cargo.lock b/Cargo.lock index a05b8540b8..2993319e6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,15 +214,17 @@ dependencies = [ ] [[package]] -name = "bench-prover" +name = "bench-transaction" version = "0.1.0" dependencies = [ "anyhow", "criterion 0.6.0", + "miden-lib", "miden-objects", "miden-processor", "miden-testing", "miden-tx", + "rand_chacha", "serde", "serde_json", ] @@ -1107,21 +1109,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "miden-bench-tx" -version = "0.1.0" -dependencies = [ - "anyhow", - "miden-lib", - "miden-objects", - "miden-processor", - "miden-testing", - "miden-tx", - "rand_chacha", - "serde", - "serde_json", -] - [[package]] name = "miden-block-prover" version = "0.12.0" diff --git a/Cargo.toml b/Cargo.toml index c6ed189a96..d839a19062 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,7 @@ [workspace] members = [ "bin/bench-note-checker", - "bin/bench-prover", - "bin/bench-tx", + "bin/bench-transaction", "crates/miden-block-prover", "crates/miden-lib", "crates/miden-objects", @@ -60,7 +59,7 @@ miden-utils-sync = { default-features = false, version = "0.17" } miden-verifier = { default-features = false, version = "0.17" } # External dependencies -anyhow = { default-features = false, version = "1.0" } +anyhow = { default-features = false, features = ["backtrace", "std"], version = "1.0" } assert_matches = { default-features = false, version = "1.5" } rand = { default-features = false, version = "0.9" } rstest = { version = "0.26" } diff --git a/Makefile b/Makefile index 575a5490ac..b88d2d05da 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ clippy: ## Runs Clippy with configs .PHONY: clippy-no-std clippy-no-std: ## Runs Clippy with configs - cargo clippy --no-default-features --target wasm32-unknown-unknown --workspace --lib --exclude bench-prover -- -D warnings + cargo clippy --no-default-features --target wasm32-unknown-unknown --workspace --lib -- -D warnings .PHONY: fix @@ -116,23 +116,19 @@ build: ## By default we should build in release mode .PHONY: build-no-std build-no-std: ## Build without the standard library - $(BUILD_GENERATED_FILES_IN_SRC) cargo build --no-default-features --target wasm32-unknown-unknown --workspace --lib --exclude bench-prover + $(BUILD_GENERATED_FILES_IN_SRC) cargo build --no-default-features --target wasm32-unknown-unknown --workspace --lib .PHONY: build-no-std-testing build-no-std-testing: ## Build without the standard library. Includes the `testing` feature - $(BUILD_GENERATED_FILES_IN_SRC) cargo build --no-default-features --target wasm32-unknown-unknown --workspace --exclude miden-bench-tx --features testing --exclude bench-prover + $(BUILD_GENERATED_FILES_IN_SRC) cargo build --no-default-features --target wasm32-unknown-unknown --workspace --exclude bench-transaction --features testing # --- benchmarking -------------------------------------------------------------------------------- .PHONY: bench-tx bench-tx: ## Run transaction benchmarks - cargo run --bin bench-tx - -.PHONY: bench-prover -bench-prover: ## Run prover benchmarks and consolidate results. - cargo bench --bin bench-prover --bench benches - cargo run --bin bench-prover + cargo run --bin bench-transaction --features concurrent + cargo bench --bin bench-transaction --bench time_counting_benchmarks --features concurrent .PHONY: bench-note-checker bench-note-checker: ## Run note checker benchmarks diff --git a/bin/bench-note-checker/Cargo.toml b/bin/bench-note-checker/Cargo.toml index 9c81202e8c..9496b74ecb 100644 --- a/bin/bench-note-checker/Cargo.toml +++ b/bin/bench-note-checker/Cargo.toml @@ -18,7 +18,7 @@ miden-testing = { workspace = true } miden-tx = { workspace = true } # External dependencies -anyhow = "1.0" +anyhow = { workspace = true } serde = { features = ["derive"], version = "1.0" } tokio = { features = ["macros", "rt"], version = "1.0" } diff --git a/bin/bench-prover/Cargo.toml b/bin/bench-prover/Cargo.toml deleted file mode 100644 index f8a705fffb..0000000000 --- a/bin/bench-prover/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -authors.workspace = true -edition.workspace = true -exclude.workspace = true -homepage.workspace = true -license.workspace = true -name = "bench-prover" -publish = false -repository.workspace = true -rust-version.workspace = true -version = "0.1.0" - -[dependencies] -# Workspace dependencies -miden-objects = { features = ["testing"], workspace = true } -miden-testing = { workspace = true } -miden-tx = { workspace = true } - -# Miden dependencies -miden-processor = { workspace = true } - -# External dependencies -anyhow = "1.0" -serde = { features = ["derive"], version = "1.0" } -serde_json = "1.0" - -[dev-dependencies] -criterion = { features = ["html_reports"], version = "0.6" } - -[[bench]] -harness = false -name = "benches" -path = "benches/benchmarks.rs" diff --git a/bin/bench-prover/README.md b/bin/bench-prover/README.md deleted file mode 100644 index d5df280557..0000000000 --- a/bin/bench-prover/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Miden Prover Benchmarking - -This document describes how to run and analyze benchmarks for the Miden prover. - -## Running Benchmarks - -You can run the benchmarks in two ways: - -### Option 1: Using Make (from miden-base directory) - -```bash -make bench-prover -``` - -### Option 2: Running Directly (from bench-prover directory) - -```bash -# Run the benchmarks -cargo bench - -# Process the results -cargo run -``` - -## How It Works - -1. `cargo bench` uses [Criterion.rs](https://github.com/bheisler/criterion.rs) to run performance benchmarks -2. By default, Criterion stores raw benchmark results in `target/criterion/` -3. `cargo run` parses these results and creates a consolidated summary in `consolidated_benchmarks.json` - -## Viewing Results - -### HTML Reports - -Criterion automatically generates HTML reports with its built-in reporting feature. After running the benchmarks, you can find these reports in the Criterion directory by default under `target/criterion/{BENCHMARK_GROUP}/index.html` - - -### Consolidated JSON Summary - -The `consolidated_benchmarks.json` file contains a summary of all proving benchmarks in a structured format: - -Example `consolidated_benchmarks.json` structure: -```json -{ - "prove_consume_note_with_new_account": { - "id": "miden_proving/prove_consume_note_with_new_account", - // average time per trial in seconds - "mean_sec": 2.9489723874, - // lower bound of 95% confidence interval for mean - "mean_lower_bound_sec": 2.924891996, - // upper bound of 95% confidence interval for mean - "mean_upper_bound_sec": 2.9777331873, - // standard deviation of time per trial - "std_dev_sec": 0.04551027448900068, - // denotes time for each trial - "times_sec": [ - 2.98336025, - 3.051340166, - 2.972870583, - 2.943372125, - 2.923954958, - 2.939220542, - 2.945244416, - 2.890069959, - 2.9041745, - 2.936116375 - ], - // total number of trials benchmark completed - "trial_count": 10 - }, - "prove_consume_multiple_notes": { - "id": "miden_proving/prove_consume_multiple_notes", - "mean_sec": 2.0523832292, - "mean_lower_bound_sec": 2.0268005916, - "mean_upper_bound_sec": 2.0808349876, - "std_dev_sec": 0.04648980316499867, - "times_sec": [ - 2.118166333, - 2.121326834, - 2.112017625, - 2.028088083, - 2.014474333, - 2.000334667, - 2.018519542, - 2.024895417, - 2.043308542, - 2.042700916 - ], - "trial_count": 10 - } -} -``` diff --git a/bin/bench-prover/benches/benchmarks.rs b/bin/bench-prover/benches/benchmarks.rs deleted file mode 100644 index 0c58590eb4..0000000000 --- a/bin/bench-prover/benches/benchmarks.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::hint::black_box; -use std::time::Duration; - -use bench_prover::bench_functions::{ - prove_transaction, - setup_consume_multiple_notes, - setup_consume_note_with_new_account, -}; -use bench_prover::benchmark_names::{ - BENCH_CONSUME_MULTIPLE_NOTES, - BENCH_CONSUME_NOTE_NEW_ACCOUNT, - BENCH_GROUP, -}; -use criterion::{Criterion, SamplingMode, criterion_group, criterion_main}; - -fn core_benchmarks(c: &mut Criterion) { - let mut group = c.benchmark_group(BENCH_GROUP); - - group - .sampling_mode(SamplingMode::Flat) - .sample_size(10) - .warm_up_time(Duration::from_millis(1000)); - - group.bench_function(BENCH_CONSUME_NOTE_NEW_ACCOUNT, |b| { - let executed_transaction = setup_consume_note_with_new_account() - .expect("Failed to set up transaction for consuming note with new account"); - - // Only benchmark proving and verification - b.iter(|| black_box(prove_transaction(executed_transaction.clone()))); - }); - - group.bench_function(BENCH_CONSUME_MULTIPLE_NOTES, |b| { - let executed_transaction = setup_consume_multiple_notes() - .expect("Failed to set up transaction for consuming multiple notes"); - - // Only benchmark the proving and verification - b.iter(|| black_box(prove_transaction(executed_transaction.clone()))); - }); - - group.finish(); -} -criterion_group!(benches, core_benchmarks); -criterion_main!(benches); diff --git a/bin/bench-prover/src/bench_functions.rs b/bin/bench-prover/src/bench_functions.rs deleted file mode 100644 index ca6f999106..0000000000 --- a/bin/bench-prover/src/bench_functions.rs +++ /dev/null @@ -1,107 +0,0 @@ -use anyhow::Result; -use miden_objects::Felt; -use miden_objects::account::Account; -use miden_objects::asset::{Asset, AssetVault, FungibleAsset}; -use miden_objects::note::NoteType; -use miden_objects::testing::account_id::ACCOUNT_ID_SENDER; -use miden_objects::transaction::ExecutedTransaction; -use miden_testing::{Auth, MockChain}; -use miden_tx::{LocalTransactionProver, ProvingOptions}; - -pub fn setup_consume_note_with_new_account() -> Result { - // Create assets - let fungible_asset: Asset = FungibleAsset::mock(123); - - let mut builder = MockChain::builder(); - - // Create target account - let target_account = builder.create_new_wallet(Auth::BasicAuth)?; - - // Create the note - let note = builder - .add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - target_account.id(), - &[fungible_asset], - NoteType::Public, - ) - .unwrap(); - - let mock_chain = builder.build()?; - - // CONSTRUCT AND EXECUTE TX (Success) - // -------------------------------------------------------------------------------------------- - - // Execute the transaction and get the witness - let executed_transaction = mock_chain - .build_tx_context(target_account.clone(), &[note.id()], &[])? - .build()? - .execute_blocking()?; - - // Apply delta to the target account to verify it is no longer new - let target_account_after: Account = Account::new_existing( - target_account.id(), - AssetVault::new(&[fungible_asset]).unwrap(), - target_account.storage().clone(), - target_account.code().clone(), - Felt::new(1), - ); - - assert_eq!( - executed_transaction.final_account().commitment(), - target_account_after.commitment() - ); - - Ok(executed_transaction) -} - -pub fn setup_consume_multiple_notes() -> Result { - let mut builder = MockChain::builder(); - - let mut account = builder.add_existing_wallet(Auth::BasicAuth)?; - let fungible_asset_1: Asset = FungibleAsset::mock(100); - let fungible_asset_2: Asset = FungibleAsset::mock(23); - - let note_1 = builder.add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[fungible_asset_1], - NoteType::Private, - )?; - let note_2 = builder.add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[fungible_asset_2], - NoteType::Private, - )?; - - let mock_chain = builder.build()?; - - let tx_context = mock_chain - .build_tx_context(account.id(), &[note_1.id(), note_2.id()], &[])? - .build()?; - - let executed_transaction = tx_context.execute_blocking().unwrap(); - - account.apply_delta(executed_transaction.account_delta()).unwrap(); - let resulting_asset = account.vault().assets().next().unwrap(); - if let Asset::Fungible(asset) = resulting_asset { - assert_eq!(asset.amount(), 123u64); - } else { - panic!("Resulting asset should be fungible"); - } - - Ok(executed_transaction) -} - -pub fn prove_transaction(executed_transaction: ExecutedTransaction) -> Result<()> { - let executed_transaction_id = executed_transaction.id(); - - let proof_options = ProvingOptions::default(); - let prover = LocalTransactionProver::new(proof_options); - let proven_transaction: miden_objects::transaction::ProvenTransaction = - prover.prove(executed_transaction.into()).unwrap(); - - assert_eq!(proven_transaction.id(), executed_transaction_id); - Ok(()) -} diff --git a/bin/bench-prover/src/lib.rs b/bin/bench-prover/src/lib.rs deleted file mode 100644 index 15cd947eea..0000000000 --- a/bin/bench-prover/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod bench_functions; -pub mod utils; - -pub mod benchmark_names { - pub const BENCH_CONSUME_NOTE_NEW_ACCOUNT: &str = "prove_consume_note_with_new_account"; - pub const BENCH_CONSUME_MULTIPLE_NOTES: &str = "prove_consume_multiple_notes"; - pub const BENCH_GROUP: &str = "miden_proving"; -} diff --git a/bin/bench-prover/src/main.rs b/bin/bench-prover/src/main.rs deleted file mode 100644 index 91974e0bc4..0000000000 --- a/bin/bench-prover/src/main.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::path::PathBuf; - -use anyhow::{Context, Result}; -use bench_prover::benchmark_names::{ - BENCH_CONSUME_MULTIPLE_NOTES, - BENCH_CONSUME_NOTE_NEW_ACCOUNT, - BENCH_GROUP, -}; -use bench_prover::utils::{cargo_target_directory, process_benchmark_data, save_json_to_file}; -use serde_json::json; - -fn main() -> Result<()> { - let target_dir = cargo_target_directory().unwrap_or_else(|| PathBuf::from("target")); - let base_path = target_dir.join("criterion").join(BENCH_GROUP); - - println!("Looking for benchmark results in: {}", base_path.display()); - - let benchmarks = [BENCH_CONSUME_NOTE_NEW_ACCOUNT, BENCH_CONSUME_MULTIPLE_NOTES]; - - let mut consolidated_results = json!({}); - - for benchmark in benchmarks { - let benchmark_path = base_path.join(benchmark).join("new"); - - println!("\nProcessing benchmark: {benchmark}"); - - if !benchmark_path.exists() { - return Err(anyhow::anyhow!( - "Benchmark directory does not exist: {}", - benchmark_path.display() - )); - } - - match process_benchmark_data(&benchmark_path) { - Ok(benchmark_data) => { - consolidated_results[benchmark] = benchmark_data; - }, - Err(err) => { - return Err(err) - .with_context(|| format!("Failed to process benchmark: {benchmark}")); - }, - } - } - - // Rest of the code remains the same - let output_path = target_dir.join("criterion").join("consolidated_benchmarks.json"); - println!("Writing consolidated file to {}", output_path.display()); - - save_json_to_file(&consolidated_results, &output_path) - .with_context(|| format!("Failed to save results to {}", output_path.display()))?; - - Ok(()) -} diff --git a/bin/bench-prover/src/utils.rs b/bin/bench-prover/src/utils.rs deleted file mode 100644 index 71a8f437e0..0000000000 --- a/bin/bench-prover/src/utils.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::env; -use std::fs::{self}; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::process::Command; - -use anyhow::{Context, Result}; -use serde::Deserialize; -use serde_json::{Value, json}; - -// replicating this function from the criterion lib: -// https://github.com/bheisler/criterion.rs/blob/ccccbcc15237233af22af4c76751a7aa184609b3/src/lib.rs#L366. -pub fn cargo_target_directory() -> Option { - #[derive(Deserialize)] - struct Metadata { - target_directory: PathBuf, - } - - env::var_os("CARGO_TARGET_DIR").map(PathBuf::from).or_else(|| { - let output = Command::new(env::var_os("CARGO")?) - .args(["metadata", "--format-version", "1"]) - .output() - .ok()?; - let metadata: Metadata = serde_json::from_slice(&output.stdout).ok()?; - Some(metadata.target_directory) - }) -} -const NANOS_PER_SEC: f64 = 1_000_000_000.0; - -/// Processes Criterion benchmark output files (benchmark.json, estimates.json, sample.json) -/// and extracts relevant performance metrics into a single JSON structure. -/// Converts nanosecond measurements to seconds and includes mean, confidence intervals, -/// standard deviation, and individual sample times. -pub fn process_benchmark_data(benchmark_path: &Path) -> Result { - let mut benchmark_data = json!({}); - - // Process benchmark.json - let benchmark_file = benchmark_path.join("benchmark.json"); - let benchmark_content = fs::read_to_string(&benchmark_file).with_context(|| { - format!("Failed to read benchmark file at {}", benchmark_file.display()) - })?; - - let json: Value = serde_json::from_str(&benchmark_content).with_context(|| { - format!("Failed to parse benchmark.json at {}", benchmark_file.display()) - })?; - benchmark_data["id"] = json["full_id"].clone(); - - // Process estimates.json - let estimates_file = benchmark_path.join("estimates.json"); - let estimates_content = fs::read_to_string(&estimates_file).with_context(|| { - format!("Failed to read estimates file at {}", estimates_file.display()) - })?; - - let json: Value = serde_json::from_str(&estimates_content).with_context(|| { - format!("Failed to parse estimates.json at {}", estimates_file.display()) - })?; - - // Extract metrics - let mean = json["mean"]["point_estimate"] - .as_f64() - .with_context(|| "Missing or invalid mean point estimate in estimates.json")?; - benchmark_data["mean_sec"] = json!(mean / NANOS_PER_SEC); - - let lower = json["mean"]["confidence_interval"]["lower_bound"] - .as_f64() - .with_context(|| "Missing or invalid lower bound in estimates.json")?; - benchmark_data["mean_lower_bound_sec"] = json!(lower / NANOS_PER_SEC); - - let upper = json["mean"]["confidence_interval"]["upper_bound"] - .as_f64() - .with_context(|| "Missing or invalid upper bound in estimates.json")?; - benchmark_data["mean_upper_bound_sec"] = json!(upper / NANOS_PER_SEC); - - let std_dev = json["std_dev"]["point_estimate"] - .as_f64() - .with_context(|| "Missing or invalid std_dev point estimate in estimates.json")?; - benchmark_data["std_dev_sec"] = json!(std_dev / NANOS_PER_SEC); - - // Process sample.json - let sample_file = benchmark_path.join("sample.json"); - let sample_content = fs::read_to_string(&sample_file) - .with_context(|| format!("Failed to read sample file at {}", sample_file.display()))?; - - let json: Value = serde_json::from_str(&sample_content) - .with_context(|| format!("Failed to parse sample.json at {}", sample_file.display()))?; - - let times_array = json["times"] - .as_array() - .with_context(|| "Missing or invalid time values in sample.json")?; - - let times_sec: Vec = times_array - .iter() - .map(|v| v.as_f64().ok_or_else(|| anyhow::anyhow!("Invalid time values"))) - .collect::>>()? - .into_iter() - .map(|t| t / NANOS_PER_SEC) - .collect(); - - benchmark_data["times_sec"] = json!(times_sec); - - // Do the same for trials - let trials_array = json["iters"] - .as_array() - .with_context(|| "Missing or invalid iters array in sample.json")?; - benchmark_data["trial_count"] = json!(trials_array.len()); - - Ok(benchmark_data) -} - -// Update signature for save_json_to_file to use anyhow -pub fn save_json_to_file(data: &Value, file_path: &Path) -> Result<()> { - let mut file = fs::File::create(file_path) - .with_context(|| format!("Failed to create file at {}", file_path.display()))?; - let json_string = - serde_json::to_string_pretty(data).context("Failed to convert data to JSON string")?; - file.write_all(json_string.as_bytes()) - .with_context(|| format!("Failed to write JSON to file at {}", file_path.display()))?; - Ok(()) -} diff --git a/bin/bench-tx/Cargo.toml b/bin/bench-transaction/Cargo.toml similarity index 65% rename from bin/bench-tx/Cargo.toml rename to bin/bench-transaction/Cargo.toml index 12b29e2e9e..7c2639af60 100644 --- a/bin/bench-tx/Cargo.toml +++ b/bin/bench-transaction/Cargo.toml @@ -4,28 +4,32 @@ edition.workspace = true exclude.workspace = true homepage.workspace = true license.workspace = true -name = "miden-bench-tx" +name = "bench-transaction" publish = false repository.workspace = true rust-version.workspace = true version = "0.1.0" -[[bin]] -name = "bench-tx" -path = "src/main.rs" +[[bench]] +harness = false +name = "time_counting_benchmarks" +path = "src/time_counting_benchmarks/prove.rs" [dependencies] # Workspace dependencies miden-lib = { workspace = true } -miden-objects = { workspace = true } +miden-objects = { features = ["testing"], workspace = true } miden-testing = { workspace = true } -miden-tx = { features = ["testing"], workspace = true } +miden-tx = { workspace = true } # Miden dependencies miden-processor = { workspace = true } # External dependencies -anyhow = { features = ["backtrace", "std"], version = "1.0" } +anyhow = { workspace = true } rand_chacha = { default-features = false, version = "0.9" } serde = { features = ["derive"], version = "1.0" } serde_json = { features = ["preserve_order"], package = "serde_json", version = "1.0" } + +[dev-dependencies] +criterion = { features = ["html_reports"], version = "0.6" } diff --git a/bin/bench-transaction/README.md b/bin/bench-transaction/README.md new file mode 100644 index 0000000000..6d229c8c78 --- /dev/null +++ b/bin/bench-transaction/README.md @@ -0,0 +1,67 @@ +# Miden Transaction Benchmarking + +Below we describe how to benchmark Miden transactions. + +Benchmarks consist of two groups: +- Benchmarking the transaction execution. + + For each transaction, data is collected on the number of cycles required to complete: + - Prologue + - All notes processing + - Each note execution + - Transaction script processing + - Epilogue: + - Total number of cycles + - Authentication procedure + - After tx cycles were obtained (The number of cycles the epilogue took to execute after the number of transaction cycles were obtained) + + Results of this benchmark will be stored in the [`bin/bench-tx/bench-tx.json`](bench-tx.json) file. +- Benchmarking the transaction execution and proving. + For each transaction in this group we measure how much time it takes to execute the transaction and to execute and prove the transaction. + + This group uses the [Criterion.rs](https://github.com/bheisler/criterion.rs) to collect the elapsed time. Results of this benchmark group are printed to the terminal and look like so: + ```zsh + Execute transaction/Execute transaction which consumes single P2ID note + time: [7.2611 ms 7.2772 ms 7.2929 ms] + change: [−0.9131% −0.5837% −0.3058%] (p = 0.00 < 0.05) + Change within noise threshold. + Execute transaction/Execute transaction which consumes two P2ID notes + time: [8.8279 ms 8.8442 ms 8.8633 ms] + change: [−1.2256% −0.7611% −0.3355%] (p = 0.00 < 0.05) + Change within noise threshold. + + Execute and prove transaction/Execute and prove transaction which consumes single P2ID note + time: [698.96 ms 703.92 ms 708.70 ms] + change: [−2.3061% −0.4274% +0.9653%] (p = 0.70 > 0.05) + No change in performance detected. + Execute and prove transaction/Execute and prove transaction which consumes two P2ID notes + time: [706.52 ms 710.91 ms 715.66 ms] + change: [−7.4641% −5.0278% −2.9437%] (p = 0.00 < 0.05) + Performance has improved. + ``` + +## Running Benchmarks + +You can run the benchmarks in two ways: + +### Option 1: Using Make (from miden-base directory) + +```bash +make bench-tx +``` + +This command will run both the cycle counting and the time counting benchmarks. + +### Option 2: Running each benchmark individually (from miden-base directory) + +```bash +# Run the cycle counting benchmarks +cargo run --bin bench-transaction --features concurrent + +# Run the time counting benchmarks +cargo bench --bin bench-transaction --bench time_counting_benchmarks --features concurrent +``` + +## License + +This project is [MIT licensed](../../LICENSE). \ No newline at end of file diff --git a/bin/bench-transaction/bench-tx.json b/bin/bench-transaction/bench-tx.json new file mode 100644 index 0000000000..77adb4a920 --- /dev/null +++ b/bin/bench-transaction/bench-tx.json @@ -0,0 +1,40 @@ +{ + "consume single P2ID note": { + "prologue": 3066, + "notes_processing": 1693, + "note_execution": { + "0xf5b0275c217e79a596a2aa106ec86cc442fa6229f30ccc4a173a90c4d14aad0e": 1659 + }, + "tx_script_processing": 40, + "epilogue": { + "total": 62513, + "auth_procedure": 61352, + "after_tx_cycles_obtained": 500 + } + }, + "consume two P2ID notes": { + "prologue": 3735, + "notes_processing": 3394, + "note_execution": { + "0x147b8b1a9fb4255b1272ef71b5d51b7c3eb423e586873f39743689c7a720ecd0": 1692, + "0xd921698727a028c992fd6192f23b175b95a24859c7de645466e4d3c757b1c08a": 1659 + }, + "tx_script_processing": 40, + "epilogue": { + "total": 62513, + "auth_procedure": 61352, + "after_tx_cycles_obtained": 500 + } + }, + "create single P2ID note": { + "prologue": 1558, + "notes_processing": 26, + "note_execution": {}, + "tx_script_processing": 1484, + "epilogue": { + "total": 63227, + "auth_procedure": 61533, + "after_tx_cycles_obtained": 500 + } + } +} \ No newline at end of file diff --git a/bin/bench-transaction/src/context_setups.rs b/bin/bench-transaction/src/context_setups.rs new file mode 100644 index 0000000000..d784ef52ae --- /dev/null +++ b/bin/bench-transaction/src/context_setups.rs @@ -0,0 +1,123 @@ +use anyhow::Result; +use miden_lib::utils::ScriptBuilder; +use miden_objects::asset::{Asset, FungibleAsset}; +use miden_objects::note::NoteType; +use miden_objects::testing::account_id::ACCOUNT_ID_SENDER; +use miden_objects::transaction::OutputNote; +use miden_objects::{Felt, Word}; +use miden_testing::{Auth, MockChain, TransactionContext}; + +/// Returns the transaction context which could be used to run the transaction which creates a +/// single P2ID note. +pub fn tx_create_single_p2id_note() -> Result { + let mut builder = MockChain::builder(); + let fungible_asset = FungibleAsset::mock(150); + let account = builder.add_existing_wallet_with_assets(Auth::BasicAuth, [fungible_asset])?; + + let output_note = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[fungible_asset], + NoteType::Public, + )?; + + let mock_chain = builder.build()?; + + let tx_note_creation_script = format!( + " + use.miden::output_note + use.std::sys + + begin + # create an output note with fungible asset + push.{RECIPIENT} + push.{note_execution_hint} + push.{note_type} + push.0 # aux + push.{tag} + call.output_note::create + # => [note_idx] + + # move the asset to the note + push.{asset} + call.::miden::contracts::wallets::basic::move_asset_to_note + dropw + # => [note_idx] + + # truncate the stack + exec.sys::truncate_stack + end + ", + RECIPIENT = output_note.recipient().digest(), + note_execution_hint = Felt::from(output_note.metadata().execution_hint()), + note_type = NoteType::Public as u8, + tag = output_note.metadata().tag(), + asset = Word::from(fungible_asset), + ); + + let tx_script = ScriptBuilder::default().compile_tx_script(tx_note_creation_script)?; + + // construct the transaction context + mock_chain + .build_tx_context(account.id(), &[], &[])? + .extend_expected_output_notes(vec![OutputNote::Full(output_note)]) + .tx_script(tx_script) + .build() +} + +/// Returns the transaction context which could be used to run the transaction which consumes a +/// single P2ID note into a new basic wallet. +pub fn tx_consume_single_p2id_note() -> Result { + // Create assets + let fungible_asset: Asset = FungibleAsset::mock(123); + + let mut builder = MockChain::builder(); + + // Create target account + let target_account = builder.create_new_wallet(Auth::BasicAuth)?; + + // Create the note + let note = builder + .add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + target_account.id(), + &[fungible_asset], + NoteType::Public, + ) + .unwrap(); + + let mock_chain = builder.build()?; + + // construct the transaction context + mock_chain.build_tx_context(target_account.clone(), &[note.id()], &[])?.build() +} + +/// Returns the transaction context which could be used to run the transaction which consumes two +/// P2ID notes into an existing basic wallet. +pub fn tx_consume_two_p2id_notes() -> Result { + let mut builder = MockChain::builder(); + + let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let fungible_asset_1: Asset = FungibleAsset::mock(100); + let fungible_asset_2: Asset = FungibleAsset::mock(23); + + let note_1 = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[fungible_asset_1], + NoteType::Private, + )?; + let note_2 = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[fungible_asset_2], + NoteType::Private, + )?; + + let mock_chain = builder.build()?; + + // construct the transaction context + mock_chain + .build_tx_context(account.id(), &[note_1.id(), note_2.id()], &[])? + .build() +} diff --git a/bin/bench-transaction/src/cycle_counting_benchmarks/mod.rs b/bin/bench-transaction/src/cycle_counting_benchmarks/mod.rs new file mode 100644 index 0000000000..24801e09ba --- /dev/null +++ b/bin/bench-transaction/src/cycle_counting_benchmarks/mod.rs @@ -0,0 +1,20 @@ +use core::fmt; + +pub mod utils; + +/// Indicates the type of the transaction execution benchmark +pub enum ExecutionBenchmark { + ConsumeSingleP2ID, + ConsumeTwoP2ID, + CreateSingleP2ID, +} + +impl fmt::Display for ExecutionBenchmark { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ExecutionBenchmark::ConsumeSingleP2ID => write!(f, "consume single P2ID note"), + ExecutionBenchmark::ConsumeTwoP2ID => write!(f, "consume two P2ID notes"), + ExecutionBenchmark::CreateSingleP2ID => write!(f, "create single P2ID note"), + } + } +} diff --git a/bin/bench-tx/src/utils.rs b/bin/bench-transaction/src/cycle_counting_benchmarks/utils.rs similarity index 51% rename from bin/bench-tx/src/utils.rs rename to bin/bench-transaction/src/cycle_counting_benchmarks/utils.rs index df99354322..1a5865ad1f 100644 --- a/bin/bench-tx/src/utils.rs +++ b/bin/bench-transaction/src/cycle_counting_benchmarks/utils.rs @@ -2,46 +2,27 @@ extern crate alloc; pub use alloc::collections::BTreeMap; pub use alloc::string::String; use std::fs::{read_to_string, write}; +use std::path::Path; use anyhow::Context; -use miden_lib::account::auth::AuthRpoFalcon512; -use miden_lib::account::wallets::BasicWallet; -use miden_objects::account::{ - Account, - AccountBuilder, - AccountStorageMode, - AccountType, - AuthSecretKey, -}; -use miden_objects::asset::Asset; -use miden_objects::crypto::dsa::rpo_falcon512::{PublicKey, SecretKey}; use miden_objects::transaction::TransactionMeasurements; -use miden_tx::auth::BasicAuthenticator; -use rand_chacha::ChaCha20Rng; -use rand_chacha::rand_core::SeedableRng; use serde::Serialize; use serde_json::{Value, from_str, to_string_pretty}; -use super::{Benchmark, Path}; - -// CONSTANTS -// ================================================================================================ - -// Copied from miden_objects::testing::account_id. -pub const ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET: u128 = 0x00aa00000000bc200000bc000000de00; -pub const ACCOUNT_ID_SENDER: u128 = 0x00fa00000000bb800000cc000000de00; +use super::ExecutionBenchmark; // MEASUREMENTS PRINTER // ================================================================================================ +/// Helper structure holding the cycle count of each transaction stage which could be easily +/// converted to the JSON file. #[derive(Debug, Clone, Serialize)] pub struct MeasurementsPrinter { prologue: usize, notes_processing: usize, note_execution: BTreeMap, tx_script_processing: usize, - epilogue: usize, - after_tx_cycles_obtained: usize, + epilogue: EpilogueMeasurements, } impl From for MeasurementsPrinter { @@ -57,48 +38,47 @@ impl From for MeasurementsPrinter { notes_processing: tx_measurements.notes_processing, note_execution: note_execution_map, tx_script_processing: tx_measurements.tx_script_processing, - epilogue: tx_measurements.epilogue, - after_tx_cycles_obtained: tx_measurements.after_tx_cycles_obtained, + epilogue: EpilogueMeasurements::from_parts( + tx_measurements.epilogue, + tx_measurements.auth_procedure, + tx_measurements.after_tx_cycles_obtained, + ), } } } -// HELPER FUNCTIONS -// ================================================================================================ - -pub fn get_account_with_basic_authenticated_wallet( - init_seed: [u8; 32], - account_type: AccountType, - storage_mode: AccountStorageMode, - public_key: PublicKey, - assets: Option, -) -> Account { - AccountBuilder::new(init_seed) - .account_type(account_type) - .storage_mode(storage_mode) - .with_assets(assets) - .with_component(BasicWallet) - .with_auth_component(AuthRpoFalcon512::new(public_key)) - .build_existing() - .unwrap() +/// Helper structure holding the cycle count for different intervals in the epilogue, namely: +/// - `total` interval holds the total number of cycles required to execute the epilogue +/// - `auth_procedure` interval holds the number of cycles required to execute the authentication +/// procedure +/// - `after_tx_cycles_obtained` holds the number of cycles which was executed from the moment of +/// the cycle count obtainment in the `epilogue::compute_fee` procedure to the end of the +/// epilogue. +#[derive(Debug, Clone, Serialize)] +struct EpilogueMeasurements { + total: usize, + auth_procedure: usize, + after_tx_cycles_obtained: usize, } -pub fn get_new_pk_and_authenticator() -> (PublicKey, BasicAuthenticator) { - let mut rng = ChaCha20Rng::from_seed(Default::default()); - let sec_key = SecretKey::with_rng(&mut rng); - let pub_key = sec_key.public_key(); - - let authenticator = BasicAuthenticator::::new_with_rng( - &[(pub_key.into(), AuthSecretKey::RpoFalcon512(sec_key))], - rng, - ); - - (pub_key, authenticator) +impl EpilogueMeasurements { + pub fn from_parts( + total: usize, + auth_procedure: usize, + after_tx_cycles_obtained: usize, + ) -> Self { + Self { + total, + auth_procedure, + after_tx_cycles_obtained, + } + } } +/// Writes the provided benchmark results to the JSON file at the provided path. pub fn write_bench_results_to_json( path: &Path, - tx_benchmarks: Vec<(Benchmark, MeasurementsPrinter)>, + tx_benchmarks: Vec<(ExecutionBenchmark, MeasurementsPrinter)>, ) -> anyhow::Result<()> { // convert benchmark file internals to the JSON Value let benchmark_file = read_to_string(path).context("failed to read benchmark file")?; diff --git a/bin/bench-transaction/src/lib.rs b/bin/bench-transaction/src/lib.rs new file mode 100644 index 0000000000..882a85de80 --- /dev/null +++ b/bin/bench-transaction/src/lib.rs @@ -0,0 +1 @@ +pub mod context_setups; diff --git a/bin/bench-transaction/src/main.rs b/bin/bench-transaction/src/main.rs new file mode 100644 index 0000000000..097fd5419c --- /dev/null +++ b/bin/bench-transaction/src/main.rs @@ -0,0 +1,54 @@ +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use anyhow::{Context, Result}; +use miden_objects::transaction::TransactionMeasurements; + +mod context_setups; +use context_setups::{ + tx_consume_single_p2id_note, + tx_consume_two_p2id_notes, + tx_create_single_p2id_note, +}; + +mod cycle_counting_benchmarks; +use cycle_counting_benchmarks::ExecutionBenchmark; +use cycle_counting_benchmarks::utils::write_bench_results_to_json; + +fn main() -> Result<()> { + // create a template file for benchmark results + let path = Path::new("bin/bench-transaction/bench-tx.json"); + let mut file = File::create(path).context("failed to create file")?; + file.write_all(b"{}").context("failed to write to file")?; + + // run all available benchmarks + let benchmark_results = vec![ + ( + ExecutionBenchmark::ConsumeSingleP2ID, + tx_consume_single_p2id_note()? + .execute_blocking() + .map(TransactionMeasurements::from)? + .into(), + ), + ( + ExecutionBenchmark::ConsumeTwoP2ID, + tx_consume_two_p2id_notes()? + .execute_blocking() + .map(TransactionMeasurements::from)? + .into(), + ), + ( + ExecutionBenchmark::CreateSingleP2ID, + tx_create_single_p2id_note()? + .execute_blocking() + .map(TransactionMeasurements::from)? + .into(), + ), + ]; + + // store benchmark results in the JSON file + write_bench_results_to_json(path, benchmark_results)?; + + Ok(()) +} diff --git a/bin/bench-transaction/src/time_counting_benchmarks/prove.rs b/bin/bench-transaction/src/time_counting_benchmarks/prove.rs new file mode 100644 index 0000000000..31cad97365 --- /dev/null +++ b/bin/bench-transaction/src/time_counting_benchmarks/prove.rs @@ -0,0 +1,131 @@ +use std::hint::black_box; +use std::time::Duration; + +use anyhow::Result; +use bench_transaction::context_setups::{tx_consume_single_p2id_note, tx_consume_two_p2id_notes}; +use criterion::{BatchSize, Criterion, SamplingMode, criterion_group, criterion_main}; +use miden_objects::transaction::{ExecutedTransaction, ProvenTransaction}; +use miden_tx::LocalTransactionProver; + +// BENCHMARK NAMES +// ================================================================================================ + +const BENCH_GROUP_EXECUTE: &str = "Execute transaction"; +const BENCH_EXECUTE_TX_CONSUME_SINGLE_P2ID: &str = + "Execute transaction which consumes single P2ID note"; +const BENCH_EXECUTE_TX_CONSUME_TWO_P2ID: &str = "Execute transaction which consumes two P2ID notes"; + +const BENCH_GROUP_EXECUTE_AND_PROVE: &str = "Execute and prove transaction"; +const BENCH_EXECUTE_AND_PROVE_TX_CONSUME_SINGLE_P2ID: &str = + "Execute and prove transaction which consumes single P2ID note"; +const BENCH_EXECUTE_AND_PROVE_TX_CONSUME_TWO_P2ID: &str = + "Execute and prove transaction which consumes two P2ID notes"; + +// CORE PROVING BENCHMARKS +// ================================================================================================ + +fn core_benchmarks(c: &mut Criterion) { + // EXECUTE GROUP + // -------------------------------------------------------------------------------------------- + + let mut execute_group = c.benchmark_group(BENCH_GROUP_EXECUTE); + + execute_group + .sampling_mode(SamplingMode::Flat) + .sample_size(10) + .warm_up_time(Duration::from_millis(1000)); + + execute_group.bench_function(BENCH_EXECUTE_TX_CONSUME_SINGLE_P2ID, |b| { + b.iter_batched( + || { + // prepare the transaction context + tx_consume_single_p2id_note() + .expect("failed to create a context which consumes single P2ID note") + }, + |tx_context| { + // benchmark the transaction execution + black_box(tx_context.execute_blocking()) + }, + BatchSize::SmallInput, + ); + }); + + execute_group.bench_function(BENCH_EXECUTE_TX_CONSUME_TWO_P2ID, |b| { + b.iter_batched( + || { + // prepare the transaction context + tx_consume_two_p2id_notes() + .expect("failed to create a context which consumes two P2ID notes") + }, + |tx_context| { + // benchmark the transaction execution + black_box(tx_context.execute_blocking()) + }, + BatchSize::SmallInput, + ); + }); + + execute_group.finish(); + + // EXECUTE AND PROVE GROUP + // -------------------------------------------------------------------------------------------- + + let mut execute_and_prove_group = c.benchmark_group(BENCH_GROUP_EXECUTE_AND_PROVE); + + execute_and_prove_group + .sampling_mode(SamplingMode::Flat) + .sample_size(10) + .warm_up_time(Duration::from_millis(1000)); + + execute_and_prove_group.bench_function(BENCH_EXECUTE_AND_PROVE_TX_CONSUME_SINGLE_P2ID, |b| { + b.iter_batched( + || { + // prepare the transaction context + tx_consume_single_p2id_note() + .expect("failed to create a context which consumes single P2ID note") + }, + |tx_context| { + // benchmark the transaction execution and proving + black_box(prove_transaction( + tx_context + .execute_blocking() + .expect("execution of the single P2ID note consumption tx failed"), + )) + }, + BatchSize::SmallInput, + ); + }); + + execute_and_prove_group.bench_function(BENCH_EXECUTE_AND_PROVE_TX_CONSUME_TWO_P2ID, |b| { + b.iter_batched( + || { + // prepare the transaction context + tx_consume_two_p2id_notes() + .expect("failed to create a context which consumes two P2ID notes") + }, + |tx_context| { + // benchmark the transaction execution and proving + black_box(prove_transaction( + tx_context + .execute_blocking() + .expect("execution of the two P2ID note consumption tx failed"), + )) + }, + BatchSize::SmallInput, + ); + }); + + execute_and_prove_group.finish(); +} + +fn prove_transaction(executed_transaction: ExecutedTransaction) -> Result<()> { + let executed_transaction_id = executed_transaction.id(); + let proven_transaction: ProvenTransaction = + LocalTransactionProver::default().prove(executed_transaction.into())?; + + assert_eq!(proven_transaction.id(), executed_transaction_id); + Ok(()) +} + +criterion_group!(benches, core_benchmarks); +criterion_main!(benches); diff --git a/bin/bench-tx/README.md b/bin/bench-tx/README.md deleted file mode 100644 index a9b14cb555..0000000000 --- a/bin/bench-tx/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Miden transactions benchmark - -This crate contains an executable used for benchmarking transactions. - -For each transaction, data is collected on the number of cycles required to complete: - -- Prologue -- All notes processing -- Each note execution -- Transaction script processing -- Epilogue - -## Usage - -To run the benchmarks you can run the following command: - -```shell -make bench-tx -``` - -Results of the benchmark are stored in the [bench-tx.json](bench-tx.json) file. - -## License - -This project is [MIT licensed](../../LICENSE). diff --git a/bin/bench-tx/bench-tx.json b/bin/bench-tx/bench-tx.json deleted file mode 100644 index 2a00818d71..0000000000 --- a/bin/bench-tx/bench-tx.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "simple": { - "prologue": 4178, - "notes_processing": 2624, - "note_execution": { - "0x2a96867a0f22d54d1e3530c074c3ad9ba9542b5060edf2f8398202f5d2e3f557": 1308, - "0xc52584ba513fd5eaaec47234263a1b20ce4d848488430e577dbdd6d9254d5bae": 1275 - }, - "tx_script_processing": 39, - "epilogue": 1663, - "after_tx_cycles_obtained": 500 - }, - "p2id": { - "prologue": 2779, - "notes_processing": 1653, - "note_execution": { - "0xb3044cb915c500d7678047e7f6cdfc9581efd8ebd07ff1977ef86f7072c51463": 1620 - }, - "tx_script_processing": 39, - "epilogue": 62466, - "after_tx_cycles_obtained": 500 - } -} \ No newline at end of file diff --git a/bin/bench-tx/src/main.rs b/bin/bench-tx/src/main.rs deleted file mode 100644 index 4b98012b3b..0000000000 --- a/bin/bench-tx/src/main.rs +++ /dev/null @@ -1,123 +0,0 @@ -use core::fmt; -use std::fs::File; -use std::io::Write; -use std::path::Path; - -use anyhow::Context; -use miden_lib::note::create_p2id_note; -use miden_lib::testing::account_component::IncrNonceAuthComponent; -use miden_lib::testing::mock_account::MockAccountExt; -use miden_objects::account::{Account, AccountId, AccountStorageMode, AccountType}; -use miden_objects::asset::{Asset, FungibleAsset}; -use miden_objects::crypto::rand::RpoRandomCoin; -use miden_objects::note::NoteType; -use miden_objects::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; -use miden_objects::transaction::TransactionMeasurements; -use miden_objects::{Felt, Word}; -use miden_testing::TransactionContextBuilder; -use miden_testing::utils::create_p2any_note; - -mod utils; -use utils::{ - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, - ACCOUNT_ID_SENDER, - get_account_with_basic_authenticated_wallet, - get_new_pk_and_authenticator, - write_bench_results_to_json, -}; -pub enum Benchmark { - Simple, - P2ID, -} - -impl fmt::Display for Benchmark { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Benchmark::Simple => write!(f, "simple"), - Benchmark::P2ID => write!(f, "p2id"), - } - } -} - -fn main() -> anyhow::Result<()> { - // create a template file for benchmark results - let path = Path::new("bin/bench-tx/bench-tx.json"); - let mut file = File::create(path).context("failed to create file")?; - file.write_all(b"{}").context("failed to write to file")?; - - // run all available benchmarks - let benchmark_results = vec![ - (Benchmark::Simple, benchmark_default_tx()?.into()), - (Benchmark::P2ID, benchmark_p2id()?.into()), - ]; - - // store benchmark results in the JSON file - write_bench_results_to_json(path, benchmark_results)?; - - Ok(()) -} - -// BENCHMARKS -// ================================================================================================ - -/// Runs the default transaction with empty transaction script and two default notes. -#[allow(clippy::arc_with_non_send_sync)] -pub fn benchmark_default_tx() -> anyhow::Result { - let tx_context = { - let account = - Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, IncrNonceAuthComponent); - - let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); - - let input_note_2 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(150)]); - TransactionContextBuilder::new(account) - .extend_input_notes(vec![input_note_1, input_note_2]) - .build()? - }; - let executed_transaction = - tx_context.execute_blocking().context("failed to execute transaction")?; - - Ok(executed_transaction.into()) -} - -/// Runs the transaction which consumes a P2ID note into a basic wallet. -#[allow(clippy::arc_with_non_send_sync)] -pub fn benchmark_p2id() -> anyhow::Result { - // Create assets - let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; - let fungible_asset: Asset = FungibleAsset::new(faucet_id, 100)?.into(); - - // Create sender and target account - let sender_account_id = AccountId::try_from(ACCOUNT_ID_SENDER)?; - - let (target_pub_key, falcon_auth) = get_new_pk_and_authenticator(); - - let target_account = get_account_with_basic_authenticated_wallet( - [10; 32], - AccountType::RegularAccountUpdatableCode, - AccountStorageMode::Private, - target_pub_key, - None, - ); - - // Create the note - let note = create_p2id_note( - sender_account_id, - target_account.id(), - vec![fungible_asset], - NoteType::Public, - Felt::new(0), - &mut RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])), - )?; - - let tx_context = TransactionContextBuilder::new(target_account.clone()) - .extend_input_notes(vec![note]) - .authenticator(Some(falcon_auth)) - .build()?; - - let executed_transaction = tx_context.execute_blocking()?; - - Ok(executed_transaction.into()) -} diff --git a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm index fff9ad0b36..1f2f39e8f7 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -27,6 +27,12 @@ const.EPILOGUE_AFTER_TX_CYCLES_OBTAINED=131097 # Event emitted to signal that the fee was computed. const.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT=131098 +# Event emitted to signal that an execution of the authentication procedure has started. +const.EPILOGUE_AUTH_PROC_START=131107 + +# Event emitted to signal that an execution of the authentication procedure has ended. +const.EPILOGUE_AUTH_PROC_END=131108 + # An additional number of cyclces to account for the number of cycles that smt::set will take when # removing the computed fee from the asset vault. # Theoretically, this can safely be set to worst_case_cycles - best_case_cycles of smt::set. That's @@ -198,6 +204,8 @@ end #! Inputs: [] #! Outputs: [] proc.execute_auth_procedure + emit.EPILOGUE_AUTH_PROC_START + padw padw padw # get the auth procedure arguments exec.memory::get_auth_args @@ -221,6 +229,8 @@ proc.execute_auth_procedure # clean up auth procedure outputs dropw dropw dropw dropw + + emit.EPILOGUE_AUTH_PROC_END end # FEE PROCEDURES diff --git a/crates/miden-lib/src/transaction/events.rs b/crates/miden-lib/src/transaction/events.rs index a255f1af54..5217145fc5 100644 --- a/crates/miden-lib/src/transaction/events.rs +++ b/crates/miden-lib/src/transaction/events.rs @@ -54,7 +54,7 @@ const TX_SCRIPT_PROCESSING_START: u32 = 0x2_0016; // 131094 const TX_SCRIPT_PROCESSING_END: u32 = 0x2_0017; // 131095 const EPILOGUE_START: u32 = 0x2_0018; // 131096 -const EPILOGUE_TX_CYCLES_OBTAINED: u32 = 0x2_0019; // 131097 +const EPILOGUE_AFTER_TX_CYCLES_OBTAINED: u32 = 0x2_0019; // 131097 const EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT: u32 = 0x2_001a; // 131098 const EPILOGUE_END: u32 = 0x2_001b; // 131099 @@ -63,6 +63,9 @@ const LINK_MAP_GET_EVENT: u32 = 0x2_001d; // 131101 const UNAUTHORIZED_EVENT: u32 = 0x2_001e; // 131102 +const EPILOGUE_AUTH_PROC_START: u32 = 0x2_0023; // 131107 +const EPILOGUE_AUTH_PROC_END: u32 = 0x2_0024; // 131108 + /// Events which may be emitted by a transaction kernel. /// /// The events are emitted via the `emit.` instruction. The event ID is a 32-bit @@ -119,10 +122,14 @@ pub enum TransactionEvent { TxScriptProcessingEnd = TX_SCRIPT_PROCESSING_END, EpilogueStart = EPILOGUE_START, - EpilogueTxCyclesObtained = EPILOGUE_TX_CYCLES_OBTAINED, - EpilogueBeforeTxFeeRemovedFromAccount = EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT, EpilogueEnd = EPILOGUE_END, + EpilogueAuthProcStart = EPILOGUE_AUTH_PROC_START, + EpilogueAuthProcEnd = EPILOGUE_AUTH_PROC_END, + + EpilogueAfterTxCyclesObtained = EPILOGUE_AFTER_TX_CYCLES_OBTAINED, + EpilogueBeforeTxFeeRemovedFromAccount = EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT, + LinkMapSetEvent = LINK_MAP_SET_EVENT, LinkMapGetEvent = LINK_MAP_GET_EVENT, @@ -214,7 +221,11 @@ impl TryFrom for TransactionEvent { TX_SCRIPT_PROCESSING_END => Ok(TransactionEvent::TxScriptProcessingEnd), EPILOGUE_START => Ok(TransactionEvent::EpilogueStart), - EPILOGUE_TX_CYCLES_OBTAINED => Ok(TransactionEvent::EpilogueTxCyclesObtained), + EPILOGUE_AUTH_PROC_START => Ok(TransactionEvent::EpilogueAuthProcStart), + EPILOGUE_AUTH_PROC_END => Ok(TransactionEvent::EpilogueAuthProcEnd), + EPILOGUE_AFTER_TX_CYCLES_OBTAINED => { + Ok(TransactionEvent::EpilogueAfterTxCyclesObtained) + }, EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT => { Ok(TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount) }, diff --git a/crates/miden-objects/src/transaction/executed_tx.rs b/crates/miden-objects/src/transaction/executed_tx.rs index 3906dd7cf6..d1eaf5cfc9 100644 --- a/crates/miden-objects/src/transaction/executed_tx.rs +++ b/crates/miden-objects/src/transaction/executed_tx.rs @@ -246,6 +246,7 @@ pub struct TransactionMeasurements { pub note_execution: Vec<(NoteId, usize)>, pub tx_script_processing: usize, pub epilogue: usize, + pub auth_procedure: usize, /// The number of cycles the epilogue took to execute after compute_fee determined the cycle /// count. /// @@ -275,6 +276,7 @@ impl Serializable for TransactionMeasurements { self.note_execution.write_into(target); self.tx_script_processing.write_into(target); self.epilogue.write_into(target); + self.auth_procedure.write_into(target); self.after_tx_cycles_obtained.write_into(target); } } @@ -286,6 +288,7 @@ impl Deserializable for TransactionMeasurements { let note_execution = Vec::<(NoteId, usize)>::read_from(source)?; let tx_script_processing = usize::read_from(source)?; let epilogue = usize::read_from(source)?; + let auth_procedure = usize::read_from(source)?; let after_tx_cycles_obtained = usize::read_from(source)?; Ok(Self { @@ -294,6 +297,7 @@ impl Deserializable for TransactionMeasurements { note_execution, tx_script_processing, epilogue, + auth_procedure, after_tx_cycles_obtained, }) } diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index e4e2fad51a..40406b55e5 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -333,7 +333,15 @@ where self.tx_progress.start_epilogue(process.clk()); Ok(TransactionEventHandling::Handled(Vec::new())) } - TransactionEvent::EpilogueTxCyclesObtained => { + TransactionEvent::EpilogueAuthProcStart => { + self.tx_progress.start_auth_procedure(process.clk()); + Ok(TransactionEventHandling::Handled(Vec::new())) + } + TransactionEvent::EpilogueAuthProcEnd => { + self.tx_progress.end_auth_procedure(process.clk()); + Ok(TransactionEventHandling::Handled(Vec::new())) + } + TransactionEvent::EpilogueAfterTxCyclesObtained => { self.tx_progress.epilogue_after_tx_cycles_obtained(process.clk()); Ok(TransactionEventHandling::Handled(vec![])) } diff --git a/crates/miden-tx/src/host/tx_progress.rs b/crates/miden-tx/src/host/tx_progress.rs index 9c54eb11dc..fc8be54d65 100644 --- a/crates/miden-tx/src/host/tx_progress.rs +++ b/crates/miden-tx/src/host/tx_progress.rs @@ -14,6 +14,7 @@ pub struct TransactionProgress { note_execution: Vec<(NoteId, CycleInterval)>, tx_script_processing: CycleInterval, epilogue: CycleInterval, + auth_procedure: CycleInterval, /// The cycle count of the processor at the point where compute_fee called clk to obtain the /// transaction's cycle count. /// @@ -34,6 +35,7 @@ impl TransactionProgress { note_execution: Vec::new(), tx_script_processing: CycleInterval::default(), epilogue: CycleInterval::default(), + auth_procedure: CycleInterval::default(), epilogue_after_tx_cycles_obtained: None, } } @@ -61,6 +63,10 @@ impl TransactionProgress { &self.epilogue } + pub fn auth_procedure(&self) -> &CycleInterval { + &self.auth_procedure + } + // STATE MUTATORS // -------------------------------------------------------------------------------------------- @@ -102,6 +108,14 @@ impl TransactionProgress { self.epilogue.set_start(cycle); } + pub fn start_auth_procedure(&mut self, cycle: RowIndex) { + self.auth_procedure.set_start(cycle); + } + + pub fn end_auth_procedure(&mut self, cycle: RowIndex) { + self.auth_procedure.set_end(cycle); + } + pub fn epilogue_after_tx_cycles_obtained(&mut self, cycle: RowIndex) { self.epilogue_after_tx_cycles_obtained = Some(cycle); } @@ -133,6 +147,8 @@ impl From for TransactionMeasurements { let epilogue = tx_progress.epilogue().len(); + let auth_procedure = tx_progress.auth_procedure().len(); + // Compute the number of cycles that where not captured by the call to clk. let after_tx_cycles_obtained = if let Some(epilogue_after_tx_cycles_obtained) = tx_progress.epilogue_after_tx_cycles_obtained @@ -149,6 +165,7 @@ impl From for TransactionMeasurements { note_execution, tx_script_processing, epilogue, + auth_procedure, after_tx_cycles_obtained, } } From c11cc8135e02f263e03455b133798e44c8e79f5b Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Tue, 23 Sep 2025 06:58:53 +0900 Subject: [PATCH 049/133] refactor: remove account_seed from AccountFile (#1917) --- CHANGELOG.md | 1 + crates/miden-objects/src/account/file.rs | 32 ++++++------------------ 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac615fe6d1..251e014e70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - Merge `bench-prover` into `bench-tx` crate ([#1894](https://github.com/0xMiden/miden-base/pull/1894)). - Replace `eqw` usages with `exec.word::test_eq` and `exec.word::eq`, remove `is_key_greater` and `is_key_less` from `link_map` module ([#1897](https://github.com/0xMiden/miden-base/pull/1897)). - [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). +- [BREAKING] Remove account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). ## 0.11.4 (2025-09-17) diff --git a/crates/miden-objects/src/account/file.rs b/crates/miden-objects/src/account/file.rs index 532d2c4508..1d57f35b0e 100644 --- a/crates/miden-objects/src/account/file.rs +++ b/crates/miden-objects/src/account/file.rs @@ -15,7 +15,7 @@ use super::super::utils::serde::{ DeserializationError, Serializable, }; -use super::{Account, AuthSecretKey, Word}; +use super::{Account, AuthSecretKey}; const MAGIC: &str = "acct"; @@ -33,21 +33,12 @@ const MAGIC: &str = "acct"; #[derive(Debug, Clone)] pub struct AccountFile { pub account: Account, - pub account_seed: Option, pub auth_secret_keys: Vec, } impl AccountFile { - pub fn new( - account: Account, - account_seed: Option, - auth_keys: Vec, - ) -> Self { - Self { - account, - account_seed, - auth_secret_keys: auth_keys, - } + pub fn new(account: Account, auth_keys: Vec) -> Self { + Self { account, auth_secret_keys: auth_keys } } } @@ -76,14 +67,9 @@ impl AccountFile { impl Serializable for AccountFile { fn write_into(&self, target: &mut W) { target.write_bytes(MAGIC.as_bytes()); - let AccountFile { - account, - account_seed, - auth_secret_keys: auth, - } = self; + let AccountFile { account, auth_secret_keys: auth } = self; account.write_into(target); - account_seed.write_into(target); auth.write_into(target); } } @@ -97,10 +83,9 @@ impl Deserializable for AccountFile { ))); } let account = Account::read_from(source)?; - let account_seed = >::read_from(source)?; let auth_secret_keys = >::read_from(source)?; - Ok(Self::new(account, account_seed, auth_secret_keys)) + Ok(Self::new(account, auth_secret_keys)) } fn read_from_bytes(bytes: &[u8]) -> Result { @@ -120,7 +105,7 @@ mod tests { use tempfile::tempdir; use super::AccountFile; - use crate::account::{Account, AccountCode, AccountId, AuthSecretKey, Felt, Word, storage}; + use crate::account::{Account, AccountCode, AccountId, AuthSecretKey, Felt, storage}; use crate::asset::AssetVault; use crate::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE; @@ -133,11 +118,10 @@ mod tests { let storage = AccountStorage::new(vec![]).unwrap(); let nonce = Felt::new(1); let account = Account::new_existing(id, vault, storage, code, nonce); - let account_seed = Some(Word::empty()); let auth_secret_key = AuthSecretKey::RpoFalcon512(SecretKey::new()); let auth_secret_key_2 = AuthSecretKey::RpoFalcon512(SecretKey::new()); - AccountFile::new(account, account_seed, vec![auth_secret_key, auth_secret_key_2]) + AccountFile::new(account, vec![auth_secret_key, auth_secret_key_2]) } #[test] @@ -146,7 +130,6 @@ mod tests { let serialized = account_file.to_bytes(); let deserialized = AccountFile::read_from_bytes(&serialized).unwrap(); assert_eq!(deserialized.account, account_file.account); - assert_eq!(deserialized.account_seed, account_file.account_seed); assert_eq!( deserialized.auth_secret_keys.to_bytes(), account_file.auth_secret_keys.to_bytes() @@ -164,7 +147,6 @@ mod tests { let deserialized = AccountFile::read(filepath.as_path()).unwrap(); assert_eq!(deserialized.account, account_file.account); - assert_eq!(deserialized.account_seed, account_file.account_seed); assert_eq!( deserialized.auth_secret_keys.to_bytes(), account_file.auth_secret_keys.to_bytes() From 6e4bb71b37b175c68d14c816d85d94765f97a9a1 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 23 Sep 2025 11:09:39 +0200 Subject: [PATCH 050/133] fix: `{PartialStorageMap, StorageMapWitness}::get` API (#1921) * fix: `PartialStorageMap` and `StorageMapWitness` `get` API * chore: add changelog --- CHANGELOG.md | 2 +- .../miden-objects/src/account/storage/map/partial.rs | 12 ++++++++---- .../miden-objects/src/account/storage/map/witness.rs | 11 +++++++---- .../src/kernel_tests/tx/test_lazy_loading.rs | 4 ++-- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 251e014e70..03cdc98937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ - [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). - [BREAKING] Move `TransactionKernelError` to miden-tx ([#1859](https://github.com/0xMiden/miden-base/pull/1859)). -- [BREAKING] Changed `PartialStorageMap` to track the correct set of key+value pairings ([#1878](https://github.com/0xMiden/miden-base/pull/1878)). +- [BREAKING] Changed `PartialStorageMap` to track the correct set of key+value pairings ([#1878](https://github.com/0xMiden/miden-base/pull/1878), [#1921](https://github.com/0xMiden/miden-base/pull/1921)). - Change terminology of "current note" to "active note" ([#1863](https://github.com/0xMiden/miden-base/issues/1863)). - [BREAKING] Move and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). - Merge `bench-prover` into `bench-tx` crate ([#1894](https://github.com/0xMiden/miden-base/pull/1894)). diff --git a/crates/miden-objects/src/account/storage/map/partial.rs b/crates/miden-objects/src/account/storage/map/partial.rs index 3f3633f677..2b4b967734 100644 --- a/crates/miden-objects/src/account/storage/map/partial.rs +++ b/crates/miden-objects/src/account/storage/map/partial.rs @@ -73,10 +73,14 @@ impl PartialStorageMap { self.partial_smt.root() } - /// Returns the value corresponding to the key or [`Word::empty`] if the key is not - /// associated with a value. - pub fn get(&self, raw_key: &Word) -> Word { - self.entries.get(raw_key).copied().unwrap_or_default() + /// Looks up the provided key in this map and returns: + /// - a non-empty [`Word`] if the key is tracked by this map and exists in it, + /// - [`Word::empty`] if the key is tracked by this map and does not exist, + /// - `None` if the key is not tracked by this map. + pub fn get(&self, raw_key: &Word) -> Option { + let hashed_key = StorageMap::hash_key(*raw_key); + // This returns an error if the key is not tracked which we map to a `None`. + self.partial_smt.get_value(&hashed_key).ok() } /// Returns an opening of the leaf associated with the raw key. diff --git a/crates/miden-objects/src/account/storage/map/witness.rs b/crates/miden-objects/src/account/storage/map/witness.rs index 8ae08d7ea4..401d8345a4 100644 --- a/crates/miden-objects/src/account/storage/map/witness.rs +++ b/crates/miden-objects/src/account/storage/map/witness.rs @@ -78,10 +78,13 @@ impl StorageMapWitness { &self.proof } - /// Returns the value corresponding to the key or [`Word::empty`] if the key is not - /// associated with a value. - pub fn get(&self, raw_key: &Word) -> Word { - self.entries.get(raw_key).copied().unwrap_or_default() + /// Looks up the provided key in this witness and returns: + /// - a non-empty [`Word`] if the key is tracked by this witness and exists in it, + /// - [`Word::empty`] if the key is tracked by this witness and does not exist, + /// - `None` if the key is not tracked by this witness. + pub fn get(&self, raw_key: &Word) -> Option { + let hashed_key = StorageMap::hash_key(*raw_key); + self.proof.get(&hashed_key) } /// Returns an iterator over the key-value pairs in this witness. diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index 164fa69e96..bf3e617dcd 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -179,7 +179,7 @@ fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { let non_existent_key = Word::from([5, 5, 5, 5u32]); assert!( - mock_map.open(&non_existent_key).get(&non_existent_key) == Word::empty(), + mock_map.open(&non_existent_key).get(&non_existent_key).unwrap() == Word::empty(), "test setup requires that the non existent key does not exist" ); @@ -243,7 +243,7 @@ fn getting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { let non_existent_key = Word::from([5, 5, 5, 5u32]); assert!( - mock_map.open(&non_existent_key).get(&non_existent_key) == Word::empty(), + mock_map.open(&non_existent_key).get(&non_existent_key).unwrap() == Word::empty(), "test setup requires that the non existent key does not exist" ); From c5a7457c9fdf042e541db5bf5b33c1cb75ea4217 Mon Sep 17 00:00:00 2001 From: Serge Radinovich <47865535+sergerad@users.noreply.github.com> Date: Wed, 24 Sep 2025 09:32:16 +1200 Subject: [PATCH 051/133] chore: Remove unnecessary repetition in transaction preparation (#1918) --- .../src/transaction/inputs/mod.rs | 32 +++++--- crates/miden-tx/src/executor/mod.rs | 70 ++++++++++-------- crates/miden-tx/src/executor/notes_checker.rs | 74 +++++++------------ 3 files changed, 85 insertions(+), 91 deletions(-) diff --git a/crates/miden-objects/src/transaction/inputs/mod.rs b/crates/miden-objects/src/transaction/inputs/mod.rs index baf44af7f5..8ea70ef7cf 100644 --- a/crates/miden-objects/src/transaction/inputs/mod.rs +++ b/crates/miden-objects/src/transaction/inputs/mod.rs @@ -46,38 +46,35 @@ impl TransactionInputs { pub fn new( partial_account: impl Into, block_header: BlockHeader, - block_chain: PartialBlockchain, + blockchain: PartialBlockchain, input_notes: InputNotes, ) -> Result { - // check the block_chain and block_header are consistent + // Check that the partial blockchain and block header are consistent. let block_num = block_header.block_num(); - if block_chain.chain_length() != block_header.block_num() { + if blockchain.chain_length() != block_header.block_num() { return Err(TransactionInputError::InconsistentChainLength { expected: block_header.block_num(), - actual: block_chain.chain_length(), + actual: blockchain.chain_length(), }); } - - if block_chain.peaks().hash_peaks() != block_header.chain_commitment() { + if blockchain.peaks().hash_peaks() != block_header.chain_commitment() { return Err(TransactionInputError::InconsistentChainCommitment { expected: block_header.chain_commitment(), - actual: block_chain.peaks().hash_peaks(), + actual: blockchain.peaks().hash_peaks(), }); } - // check the authentication paths of the input notes. + // Validate the authentication paths of the input notes. for note in input_notes.iter() { if let InputNote::Authenticated { note, proof } = note { let note_block_num = proof.location().block_num(); - let block_header = if note_block_num == block_num { &block_header } else { - block_chain.get_block(note_block_num).ok_or( + blockchain.get_block(note_block_num).ok_or( TransactionInputError::InputNoteBlockNotInPartialBlockchain(note.id()), )? }; - validate_is_in_block(note, proof, block_header)?; } } @@ -85,11 +82,22 @@ impl TransactionInputs { Ok(Self { account: partial_account.into(), block_header, - blockchain: block_chain, + blockchain, input_notes, }) } + /// Updates the input notes for the transaction. + /// + /// # Warning + /// + /// This method does not validate the notes against the data already in this + /// [`TransactionInputs`]. It should only be called with notes that have been validated by + /// the constructor. + pub fn set_input_notes_unchecked(&mut self, new_notes: InputNotes) { + self.input_notes = new_notes; + } + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index 5fb3604426..a0e013ef77 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -181,8 +181,11 @@ where notes: InputNotes, tx_args: TransactionArgs, ) -> Result { - let (mut host, tx_inputs, stack_inputs, advice_inputs) = - self.prepare_transaction(account_id, block_ref, notes, &tx_args, None).await?; + let tx_inputs = + self.prepare_transaction_inputs(account_id, block_ref, notes, &tx_args).await?; + + let (mut host, stack_inputs, advice_inputs) = + self.prepare_transaction(&tx_inputs, &tx_args, None).await?; let processor = FastProcessor::new_debug(stack_inputs.as_slice(), advice_inputs); let (stack_outputs, advice_provider) = processor @@ -223,15 +226,12 @@ where let tx_args = TransactionArgs::new(Default::default(), foreign_account_inputs) .with_tx_script(tx_script); - let (mut host, _, stack_inputs, advice_inputs) = self - .prepare_transaction( - account_id, - block_ref, - InputNotes::default(), - &tx_args, - Some(advice_inputs), - ) - .await?; + let notes = InputNotes::default(); + let tx_inputs = + self.prepare_transaction_inputs(account_id, block_ref, notes, &tx_args).await?; + + let (mut host, stack_inputs, advice_inputs) = + self.prepare_transaction(&tx_inputs, &tx_args, Some(advice_inputs)).await?; let processor = FastProcessor::new_with_advice_inputs(stack_inputs.as_slice(), advice_inputs); @@ -246,27 +246,19 @@ where // HELPER METHODS // -------------------------------------------------------------------------------------------- - /// Prepares the data needed for transaction execution. - /// - /// Preparation includes loading transaction inputs from the data store, validating them, and - /// instantiating a transaction host. - async fn prepare_transaction( + // Validates input notes and account inputs after retrieving transaction inputs from the store. + // + // This method has a one-to-many call relationship with the `prepare_transaction` method. This + // method needs to be called only once in order to allow many transactions to be prepared based + // on the transaction inputs returned by this method. + async fn prepare_transaction_inputs( &self, account_id: AccountId, block_ref: BlockNumber, - notes: InputNotes, + input_notes: InputNotes, tx_args: &TransactionArgs, - init_advice_inputs: Option, - ) -> Result< - ( - TransactionExecutorHost<'store, 'auth, STORE, AUTH>, - TransactionInputs, - StackInputs, - AdviceInputs, - ), - TransactionExecutorError, - > { - let mut ref_blocks = validate_input_notes(¬es, block_ref)?; + ) -> Result { + let mut ref_blocks = validate_input_notes(&input_notes, block_ref)?; ref_blocks.insert(block_ref); let (account, ref_block, mmr) = self @@ -277,11 +269,27 @@ where validate_account_inputs(tx_args, &ref_block)?; - let tx_inputs = TransactionInputs::new(account, ref_block, mmr, notes) + let tx_inputs = TransactionInputs::new(account, ref_block, mmr, input_notes) .map_err(TransactionExecutorError::InvalidTransactionInputs)?; + Ok(tx_inputs) + } + + /// Prepares the data needed for transaction execution. + /// + /// Preparation includes loading transaction inputs from the data store, validating them, and + /// instantiating a transaction host. + async fn prepare_transaction( + &self, + tx_inputs: &TransactionInputs, + tx_args: &TransactionArgs, + init_advice_inputs: Option, + ) -> Result< + (TransactionExecutorHost<'store, 'auth, STORE, AUTH>, StackInputs, AdviceInputs), + TransactionExecutorError, + > { let (stack_inputs, tx_advice_inputs) = - TransactionKernel::prepare_inputs(&tx_inputs, tx_args, init_advice_inputs) + TransactionKernel::prepare_inputs(tx_inputs, tx_args, init_advice_inputs) .map_err(TransactionExecutorError::ConflictingAdviceMapEntry)?; // This reverses the stack inputs (even though it doesn't look like it does) because the @@ -317,7 +325,7 @@ where let advice_inputs = tx_advice_inputs.into_advice_inputs(); - Ok((host, tx_inputs, stack_inputs, advice_inputs)) + Ok((host, stack_inputs, advice_inputs)) } } diff --git a/crates/miden-tx/src/executor/notes_checker.rs b/crates/miden-tx/src/executor/notes_checker.rs index 1bd2379470..b105d245f9 100644 --- a/crates/miden-tx/src/executor/notes_checker.rs +++ b/crates/miden-tx/src/executor/notes_checker.rs @@ -6,7 +6,7 @@ use miden_lib::transaction::TransactionKernel; use miden_objects::account::AccountId; use miden_objects::block::BlockNumber; use miden_objects::note::Note; -use miden_objects::transaction::{InputNote, InputNotes, TransactionArgs}; +use miden_objects::transaction::{InputNotes, TransactionArgs, TransactionInputs}; use miden_processor::fast::FastProcessor; use super::TransactionExecutor; @@ -116,9 +116,15 @@ where // Ensure well-known notes are ordered first. notes.sort_unstable_by_key(|note| WellKnownNote::from_note(note).is_none()); - // Attempt to find an executable set of notes. - self.find_executable_notes_by_elimination(target_account_id, block_ref, notes, tx_args) + let notes = InputNotes::from(notes); + let tx_inputs = self + .0 + .prepare_transaction_inputs(target_account_id, block_ref, notes, &tx_args) .await + .map_err(NoteCheckerError::TransactionPreparation)?; + + // Attempt to find an executable set of notes. + self.find_executable_notes_by_elimination(tx_inputs, tx_args).await } // HELPER METHODS @@ -130,12 +136,14 @@ where /// succeeded or failed to execute. async fn find_executable_notes_by_elimination( &self, - target_account_id: AccountId, - block_ref: BlockNumber, - notes: Vec, + mut tx_inputs: TransactionInputs, tx_args: TransactionArgs, ) -> Result { - let mut candidate_notes = notes; + let mut candidate_notes = tx_inputs + .input_notes() + .iter() + .map(|note| note.clone().into_note()) + .collect::>(); let mut failed_notes = Vec::new(); // Attempt to execute notes in a loop. Reduce the set of notes based on failures until @@ -143,15 +151,8 @@ where // further reduced. loop { // Execute the candidate notes. - match self - .try_execute_notes( - target_account_id, - block_ref, - candidate_notes.clone().into(), - &tx_args, - ) - .await - { + tx_inputs.set_input_notes_unchecked(candidate_notes.clone().into()); + match self.try_execute_notes(&tx_inputs, &tx_args).await { Ok(()) => { // A full set of successful notes has been found. let successful = candidate_notes; @@ -171,10 +172,9 @@ where Err(TransactionCheckerError::EpilogueExecution(_)) => { let consumption_info = self .find_largest_executable_combination( - target_account_id, - block_ref, candidate_notes, failed_notes, + tx_inputs, &tx_args, ) .await; @@ -198,14 +198,12 @@ where /// set. async fn find_largest_executable_combination( &self, - target_account_id: AccountId, - block_ref: BlockNumber, - input_notes: Vec, + mut remaining_notes: Vec, mut failed_notes: Vec, + mut tx_inputs: TransactionInputs, tx_args: &TransactionArgs, ) -> NoteConsumptionInfo { let mut successful_notes = Vec::new(); - let mut remaining_notes = input_notes; let mut failed_note_index = BTreeMap::new(); // Iterate by note count: try 1 note, then 2, then 3, etc. @@ -219,21 +217,8 @@ where for (idx, note) in remaining_notes.iter().enumerate() { successful_notes.push(note.clone()); - match self - .try_execute_notes( - target_account_id, - block_ref, - InputNotes::::new_unchecked( - successful_notes - .iter() - .cloned() - .map(InputNote::unauthenticated) - .collect::>(), - ), - tx_args, - ) - .await - { + tx_inputs.set_input_notes_unchecked(successful_notes.clone().into()); + match self.try_execute_notes(&tx_inputs, tx_args).await { Ok(()) => { // The successfully added note might have failed earlier. Remove it from the // failed list. @@ -269,23 +254,16 @@ where /// or a specific [`NoteExecutionError`] indicating where and why the execution failed. async fn try_execute_notes( &self, - account_id: AccountId, - block_ref: BlockNumber, - notes: InputNotes, + tx_inputs: &TransactionInputs, tx_args: &TransactionArgs, ) -> Result<(), TransactionCheckerError> { - if notes.is_empty() { + if tx_inputs.input_notes().is_empty() { return Ok(()); } - // TODO: ideally, we should prepare the inputs only once for the whole note consumption - // check (rather than doing this every time when we try to execute some subset of notes), - // but we currently cannot do this because transaction preparation includes input notes; - // we should refactor the preparation process to separate input note preparation from the - // rest, and then we can prepare the rest of the inputs once for the whole check. - let (mut host, _, stack_inputs, advice_inputs) = self + let (mut host, stack_inputs, advice_inputs) = self .0 - .prepare_transaction(account_id, block_ref, notes, tx_args, None) + .prepare_transaction(tx_inputs, tx_args, None) .await .map_err(TransactionCheckerError::TransactionPreparation)?; From e4cb00c93064101265e90039ae84a669c3d2f9d7 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 24 Sep 2025 11:08:11 +0200 Subject: [PATCH 052/133] chore: Remove `MockChain::add_pending_note` (#1903) * chore: Remove `add_pending_note` calls from rpo falcon ACL tests * chore: Replace more occurences of `add_pending_note` * chore: Use setup_chain_builder in `proposed_block_success` * chore: Use MockChainBuilder directly in proposed_block_success * chore: Remove `add_pending_note` in `proposed_block_errors` * chore: Add `MockChainBuilder::create_p2id_note` * chore: Remove `add_pending_note` from `proven_block_success` * chore: Remove `generate_tracked_note` * chore: Remove more APIs using `add_pending_note` * chore: Remove `TestSetup` * feat: Remove `MockChain::add_pending_note` * chore: Improve test setup and formatting * feat: Parametrize `create_p2any_note` * chore: Clean up block kernel test utils * chore: Return errors in mock chain builder ext methods * feat: Add/Rename `add_{private,public}_p2any_note` * chore: Rename `create_public_p2any_note` * chore: add changelog * chore: Correct extension trait name * chore: Improve `create_` note method docs * chore: Use `assets` instead of `asset` * chore: Make `add_p2any_note` take `NoteType` parameter --- CHANGELOG.md | 1 + crates/miden-lib/src/testing/note.rs | 6 + .../src/kernel_tests/batch/proposed_batch.rs | 5 +- .../block/proposed_block_errors.rs | 382 +++++++++--------- .../block/proposed_block_success.rs | 167 ++++---- .../kernel_tests/block/proven_block_error.rs | 59 +-- .../block/proven_block_success.rs | 163 ++++---- .../src/kernel_tests/block/utils.rs | 288 ++++--------- .../src/kernel_tests/tx/test_account_delta.rs | 10 +- .../kernel_tests/tx/test_account_interface.rs | 4 +- .../src/kernel_tests/tx/test_active_note.rs | 8 +- .../src/kernel_tests/tx/test_epilogue.rs | 18 +- .../src/kernel_tests/tx/test_faucet.rs | 4 +- .../src/kernel_tests/tx/test_fee.rs | 9 +- .../src/kernel_tests/tx/test_output_note.rs | 22 +- .../src/kernel_tests/tx/test_prologue.rs | 20 +- .../src/kernel_tests/tx/test_tx.rs | 11 +- crates/miden-testing/src/mock_chain/chain.rs | 10 +- .../src/mock_chain/chain_builder.rs | 66 ++- .../miden-testing/src/tx_context/builder.rs | 5 +- crates/miden-testing/src/utils.rs | 25 +- .../tests/auth/rpo_falcon_acl.rs | 18 +- crates/miden-testing/tests/scripts/faucet.rs | 23 +- 23 files changed, 653 insertions(+), 671 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03cdc98937..ea4b357315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ - [BREAKING] Move and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). - Merge `bench-prover` into `bench-tx` crate ([#1894](https://github.com/0xMiden/miden-base/pull/1894)). - Replace `eqw` usages with `exec.word::test_eq` and `exec.word::eq`, remove `is_key_greater` and `is_key_less` from `link_map` module ([#1897](https://github.com/0xMiden/miden-base/pull/1897)). +- [BREAKING] Remove `MockChain::add_pending_note` to simplify mock chain internals ([#1903](https://github.com/0xMiden/miden-base/pull/1903)). - [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). - [BREAKING] Remove account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). diff --git a/crates/miden-lib/src/testing/note.rs b/crates/miden-lib/src/testing/note.rs index 7805499b86..34c9e372b7 100644 --- a/crates/miden-lib/src/testing/note.rs +++ b/crates/miden-lib/src/testing/note.rs @@ -103,6 +103,12 @@ impl NoteBuilder { self } + /// Overwrites the generated serial number with a custom one. + pub fn serial_number(mut self, serial_number: Word) -> Self { + self.serial_num = serial_number; + self + } + pub fn aux(mut self, aux: Felt) -> Self { self.aux = aux; self diff --git a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs index 9040d978d2..c09aac5386 100644 --- a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs +++ b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs @@ -17,7 +17,6 @@ use rand::rngs::SmallRng; use rand::{Rng, SeedableRng}; use super::proven_tx_builder::MockProvenTxBuilder; -use crate::kernel_tests::block::utils::generate_untracked_note; use crate::{AccountState, Auth, MockChain, MockChainBuilder}; fn mock_account_id(num: u8) -> AccountId { @@ -272,8 +271,8 @@ fn duplicate_output_notes() -> anyhow::Result<()> { async fn unauthenticated_note_converted_to_authenticated() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account1 = generate_account(&mut builder); - let note1 = generate_untracked_note(account1.id(), account1.id()); - let note2 = generate_untracked_note(account1.id(), account1.id()); + let note1 = builder.create_p2any_note(account1.id(), NoteType::Public, [])?; + let note2 = builder.create_p2any_note(account1.id(), NoteType::Public, [])?; let spawn_note = builder.add_spawn_note([¬e1, ¬e2])?; let mut chain = builder.build()?; diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs index ee1bcbd2b3..0c803d31a8 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs @@ -3,42 +3,49 @@ use std::collections::BTreeMap; use std::vec::Vec; use assert_matches::assert_matches; -use miden_objects::account::AccountId; +use miden_objects::asset::FungibleAsset; use miden_objects::block::{BlockInputs, BlockNumber, ProposedBlock}; use miden_objects::crypto::merkle::SparseMerklePath; -use miden_objects::note::NoteInclusionProof; -use miden_objects::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET; -use miden_objects::transaction::OutputNote; +use miden_objects::note::{NoteInclusionProof, NoteType}; use miden_objects::{MAX_BATCHES_PER_BLOCK, ProposedBlockError}; use miden_processor::crypto::MerklePath; use miden_tx::LocalTransactionProver; -use super::utils::{ - TestSetup, - generate_batch, - generate_executed_tx_with_authenticated_notes, - generate_fungible_asset, - generate_output_note, - generate_tracked_note, - generate_tracked_note_with_asset, - generate_tx_with_authenticated_notes, - generate_tx_with_expiration, - generate_tx_with_unauthenticated_notes, - generate_untracked_note, - setup_chain, -}; -use crate::utils::create_spawn_note; +use crate::kernel_tests::block::utils::MockChainBlockExt; +use crate::{Auth, MockChain}; /// Tests that too many batches produce an error. #[test] fn proposed_block_fails_on_too_many_batches() -> anyhow::Result<()> { let count = MAX_BATCHES_PER_BLOCK + 1; - let TestSetup { mut chain, mut txs, .. } = setup_chain(count); - let mut batches = Vec::with_capacity(count); - for i in 0..count { - batches.push(generate_batch(&mut chain, vec![txs.remove(&i).unwrap()])); - } + let (chain, batches) = { + let mut builder = MockChain::builder(); + let mut accounts = Vec::new(); + let mut notes = Vec::new(); + for _ in 0..count { + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let note = builder.add_p2any_note( + account.id(), + NoteType::Public, + [FungibleAsset::mock(42)], + )?; + + accounts.push(account); + notes.push(note); + } + + let chain = builder.build()?; + + let mut batches = Vec::with_capacity(count); + for i in 0..count { + let proven_tx = + chain.create_authenticated_notes_proven_tx(accounts[i].id(), [notes[i].id()])?; + batches.push(chain.create_batch(vec![proven_tx])?); + } + + (chain, batches) + }; let block_inputs = BlockInputs::new( chain.latest_block_header(), @@ -58,9 +65,15 @@ fn proposed_block_fails_on_too_many_batches() -> anyhow::Result<()> { /// Tests that duplicate batches produce an error. #[test] fn proposed_block_fails_on_duplicate_batches() -> anyhow::Result<()> { - let TestSetup { mut chain, mut txs, .. } = setup_chain(1); - let proven_tx0 = txs.remove(&0).unwrap(); - let batch0 = generate_batch(&mut chain, vec![proven_tx0]); + let mut builder = MockChain::builder(); + let sender_account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let note = + builder.add_p2any_note(sender_account.id(), NoteType::Public, [FungibleAsset::mock(42)])?; + let chain = builder.build()?; + + let proven_tx0 = + chain.create_authenticated_notes_proven_tx(sender_account.id(), [note.id()])?; + let batch0 = chain.create_batch(vec![proven_tx0])?; let batches = vec![batch0.clone(), batch0.clone()]; @@ -82,16 +95,19 @@ fn proposed_block_fails_on_duplicate_batches() -> anyhow::Result<()> { /// Tests that an expired batch produces an error. #[test] fn proposed_block_fails_on_expired_batches() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(2); + let mut builder = MockChain::builder(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let mut chain = builder.build()?; + + chain.prove_next_block()?; let block1_num = chain.block_header(1).block_num(); - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - let tx0 = generate_tx_with_expiration(&mut chain, account0.id(), block1_num + 5); - let tx1 = generate_tx_with_expiration(&mut chain, account1.id(), block1_num + 1); + let tx0 = chain.create_expiring_proven_tx(account0.id(), block1_num + 5)?; + let tx1 = chain.create_expiring_proven_tx(account1.id(), block1_num + 1)?; - let batch0 = generate_batch(&mut chain, vec![tx0]); - let batch1 = generate_batch(&mut chain, vec![tx1]); + let batch0 = chain.create_batch(vec![tx0])?; + let batch1 = chain.create_batch(vec![tx1])?; let _block2 = chain.prove_next_block()?; @@ -119,9 +135,12 @@ fn proposed_block_fails_on_expired_batches() -> anyhow::Result<()> { /// Tests that a timestamp at or before the previous block header produces an error. #[test] fn proposed_block_fails_on_timestamp_not_increasing_monotonically() -> anyhow::Result<()> { - let TestSetup { mut chain, mut txs, .. } = setup_chain(1); - let proven_tx0 = txs.remove(&0).unwrap(); - let batch0 = generate_batch(&mut chain, vec![proven_tx0]); + let mut builder = MockChain::builder(); + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let chain = builder.build()?; + let proven_tx0 = chain.create_authenticated_notes_proven_tx(account, [])?; + + let batch0 = chain.create_batch(vec![proven_tx0])?; let batches = vec![batch0]; // Mock BlockInputs. let block_inputs = BlockInputs::new( @@ -149,9 +168,12 @@ fn proposed_block_fails_on_timestamp_not_increasing_monotonically() -> anyhow::R /// an error. #[test] fn proposed_block_fails_on_partial_blockchain_and_prev_block_inconsistency() -> anyhow::Result<()> { - let TestSetup { mut chain, mut txs, .. } = setup_chain(1); - let proven_tx0 = txs.remove(&0).unwrap(); - let batch0 = generate_batch(&mut chain, vec![proven_tx0]); + let mut builder = MockChain::builder(); + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let chain = builder.build()?; + let proven_tx0 = chain.create_authenticated_notes_proven_tx(account, [])?; + + let batch0 = chain.create_batch(vec![proven_tx0])?; let batches = vec![batch0]; // Select the partial blockchain which is valid for the current block but pass the next block in @@ -202,17 +224,21 @@ fn proposed_block_fails_on_partial_blockchain_and_prev_block_inconsistency() -> /// produces an error. #[test] fn proposed_block_fails_on_missing_batch_reference_block() -> anyhow::Result<()> { - let TestSetup { mut chain, mut txs, .. } = setup_chain(1); - let proven_tx0 = txs.remove(&0).unwrap(); + let mut builder = MockChain::builder(); + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let mut chain = builder.build()?; + chain.prove_next_block()?; + + let proven_tx0 = chain.create_authenticated_notes_proven_tx(account, [])?; // This batch will reference the latest block with number 1. - let batch0 = generate_batch(&mut chain, vec![proven_tx0.clone()]); + let batch0 = chain.create_batch(vec![proven_tx0.clone()])?; let batches = vec![batch0.clone()]; let block2 = chain.prove_next_block()?; let (_, partial_blockchain) = - chain.latest_selective_partial_blockchain([BlockNumber::from(0)]).unwrap(); + chain.latest_selective_partial_blockchain([BlockNumber::GENESIS])?; // The proposed block references block 2 but the partial blockchain only contains block 0 but // not block 1 which is referenced by the batch. @@ -240,13 +266,13 @@ fn proposed_block_fails_on_missing_batch_reference_block() -> anyhow::Result<()> /// Tests that duplicate input notes across batches produce an error. #[test] fn proposed_block_fails_on_duplicate_input_note() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(2); - - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); + let mut builder = MockChain::builder(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let note0 = builder.add_p2any_note(account0.id(), NoteType::Public, [])?; + let note1 = builder.add_p2any_note(account0.id(), NoteType::Public, [])?; + let mut chain = builder.build()?; - let note0 = generate_tracked_note(&mut chain, account0.id(), account1.id()); - let note1 = generate_tracked_note(&mut chain, account0.id(), account1.id()); // These notes should have different IDs. assert_ne!(note0.id(), note1.id()); @@ -255,11 +281,11 @@ fn proposed_block_fails_on_duplicate_input_note() -> anyhow::Result<()> { // Create two different transactions against the same account consuming the same note. let tx0 = - generate_tx_with_authenticated_notes(&mut chain, account1.id(), &[note0.id(), note1.id()]); - let tx1 = generate_tx_with_authenticated_notes(&mut chain, account1.id(), &[note0.id()]); + chain.create_authenticated_notes_proven_tx(account1.id(), [note0.id(), note1.id()])?; + let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note0.id()])?; - let batch0 = generate_batch(&mut chain, vec![tx0]); - let batch1 = generate_batch(&mut chain, vec![tx1]); + let batch0 = chain.create_batch(vec![tx0])?; + let batch1 = chain.create_batch(vec![tx1])?; let batches = vec![batch0.clone(), batch1.clone()]; @@ -274,30 +300,27 @@ fn proposed_block_fails_on_duplicate_input_note() -> anyhow::Result<()> { /// Tests that duplicate output notes across batches produce an error. #[test] fn proposed_block_fails_on_duplicate_output_note() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(1); - let account = accounts.remove(&0).unwrap(); - - let output_note = generate_output_note(account.id(), [10; 32]); + let mut builder = MockChain::builder(); + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let output_note = builder.create_p2any_note(account.id(), NoteType::Private, [])?; // Create two different notes that will create the same output note. Their IDs will be different // due to having a different serial number generated from contained RNG. - let note0 = create_spawn_note([&output_note])?; - let note1 = create_spawn_note([&output_note])?; - - chain.add_pending_note(OutputNote::Full(note0.clone())); - chain.add_pending_note(OutputNote::Full(note1.clone())); + let note0 = builder.add_spawn_note([&output_note])?; + let note1 = builder.add_spawn_note([&output_note])?; + let mut chain = builder.build()?; chain.prove_next_block()?; // Create two different transactions against the same account creating the same note. // We use the same account because the sender of the created output note is set to the account // of the transaction, so it is essential we use the same account to produce a duplicate output // note. - let tx0 = generate_tx_with_authenticated_notes(&mut chain, account.id(), &[note0.id()]); - let tx1 = generate_tx_with_authenticated_notes(&mut chain, account.id(), &[note1.id()]); + let tx0 = chain.create_authenticated_notes_proven_tx(account.id(), [note0.id()])?; + let tx1 = chain.create_authenticated_notes_proven_tx(account.id(), [note1.id()])?; - let batch0 = generate_batch(&mut chain, vec![tx0]); - let batch1 = generate_batch(&mut chain, vec![tx1]); + let batch0 = chain.create_batch(vec![tx0])?; + let batch1 = chain.create_batch(vec![tx1])?; let batches = vec![batch0.clone(), batch1.clone()]; @@ -315,22 +338,30 @@ fn proposed_block_fails_on_duplicate_output_note() -> anyhow::Result<()> { #[test] fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_block() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(2); - - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - - let note0 = generate_untracked_note(account0.id(), account1.id()); + let mut builder = MockChain::builder(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let p2id_note = + builder.create_p2id_note(account0.id(), account1.id(), [], NoteType::Private)?; + let spawn_note = builder.add_spawn_note([&p2id_note])?; + let mut chain = builder.build()?; // This tx will use block1 as the reference block. let tx0 = - generate_tx_with_unauthenticated_notes(&mut chain, account1.id(), slice::from_ref(¬e0)); + chain.create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(&p2id_note))?; // This batch will use block1 as the reference block. - let batch0 = generate_batch(&mut chain, vec![tx0]); - - // Add the note to the chain so we can retrieve an inclusion proof for it. - chain.add_pending_note(OutputNote::Full(note0.clone())); + // With this setup, the block inputs need to contain a reference to block2 in order to prove + // inclusion of the unauthenticated note. + let batch0 = chain.create_batch(vec![tx0])?; + + // Add the P2ID note to the chain by consuming the SPAWN note. The note will hence be created as + // part of block 2 and the note inclusion proof references that block. + let tx = chain + .build_tx_context(account0.id(), &[spawn_note.id()], &[])? + .build()? + .execute_blocking()?; + chain.add_pending_executed_transaction(&tx)?; let block2 = chain.prove_next_block()?; // Seal another block so that the next block will use this one as the reference block and block2 @@ -357,14 +388,19 @@ fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_blo .expect("block2 should have been fetched"); let error = ProposedBlock::new(invalid_block_inputs, batches.clone()).unwrap_err(); - assert_matches!(error, ProposedBlockError::UnauthenticatedInputNoteBlockNotInPartialBlockchain { block_number, note_id } if block_number == block2.header().block_num() && note_id == note0.id()); + assert_matches!(error, ProposedBlockError::UnauthenticatedInputNoteBlockNotInPartialBlockchain { + block_number, note_id + } => { + assert_eq!(block_number, block2.header().block_num()); + assert_eq!(note_id, p2id_note.id()); + }); // Error: Invalid note inclusion proof. // -------------------------------------------------------------------------------------------- let original_note_proof = original_block_inputs .unauthenticated_note_proofs() - .get(¬e0.id()) + .get(&p2id_note.id()) .expect("note proof should have been fetched") .clone(); let mut original_merkle_path = MerklePath::from(original_note_proof.note_path().clone()); @@ -380,10 +416,10 @@ fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_blo let mut invalid_block_inputs = original_block_inputs.clone(); invalid_block_inputs .unauthenticated_note_proofs_mut() - .insert(note0.id(), invalid_note_proof); + .insert(p2id_note.id(), invalid_note_proof); let error = ProposedBlock::new(invalid_block_inputs, batches.clone()).unwrap_err(); - assert_matches!(error, ProposedBlockError::UnauthenticatedNoteAuthenticationFailed { block_num, note_id, .. } if block_num == block2.header().block_num() && note_id == note0.id()); + assert_matches!(error, ProposedBlockError::UnauthenticatedNoteAuthenticationFailed { block_num, note_id, .. } if block_num == block2.header().block_num() && note_id == p2id_note.id()); Ok(()) } @@ -391,17 +427,17 @@ fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_blo /// Tests that a missing note inclusion proof produces an error. #[test] fn proposed_block_fails_on_missing_note_inclusion_proof() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(2); - - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - - let note0 = generate_tracked_note(&mut chain, account0.id(), account1.id()); + let mut builder = MockChain::builder(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + // Note that this note is not added to the chain state. + let note0 = builder.create_p2any_note(account0.id(), NoteType::Private, [])?; + let chain = builder.build()?; let tx0 = - generate_tx_with_unauthenticated_notes(&mut chain, account1.id(), slice::from_ref(¬e0)); + chain.create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(¬e0))?; - let batch0 = generate_batch(&mut chain, vec![tx0]); + let batch0 = chain.create_batch(vec![tx0])?; let batches = vec![batch0.clone()]; @@ -418,23 +454,19 @@ fn proposed_block_fails_on_missing_note_inclusion_proof() -> anyhow::Result<()> /// Tests that a missing nullifier witness produces an error. #[test] fn proposed_block_fails_on_missing_nullifier_witness() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(2); - - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - - let note0 = generate_untracked_note(account0.id(), account1.id()); + let mut builder = MockChain::builder(); + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let p2id_note = + builder.add_p2any_note(account.id(), NoteType::Public, [FungibleAsset::mock(50)])?; + let mut chain = builder.build()?; + chain.prove_next_block()?; // This tx will use block1 as the reference block. let tx0 = - generate_tx_with_unauthenticated_notes(&mut chain, account1.id(), slice::from_ref(¬e0)); + chain.create_unauthenticated_notes_proven_tx(account.id(), slice::from_ref(&p2id_note))?; // This batch will use block1 as the reference block. - let batch0 = generate_batch(&mut chain, vec![tx0]); - - // Add the note to the chain so we can retrieve an inclusion proof for it. - chain.add_pending_note(OutputNote::Full(note0.clone())); - let _block2 = chain.prove_next_block()?; + let batch0 = chain.create_batch(vec![tx0])?; let batches = vec![batch0.clone()]; @@ -446,11 +478,13 @@ fn proposed_block_fails_on_missing_nullifier_witness() -> anyhow::Result<()> { let mut invalid_block_inputs = block_inputs.clone(); invalid_block_inputs .nullifier_witnesses_mut() - .remove(¬e0.nullifier()) + .remove(&p2id_note.nullifier()) .expect("nullifier should have been fetched"); let error = ProposedBlock::new(invalid_block_inputs, batches.clone()).unwrap_err(); - assert_matches!(error, ProposedBlockError::NullifierProofMissing(nullifier) if nullifier == note0.nullifier()); + assert_matches!(error, ProposedBlockError::NullifierProofMissing(nullifier) => { + assert_eq!(nullifier, p2id_note.nullifier()); + }); Ok(()) } @@ -458,80 +492,63 @@ fn proposed_block_fails_on_missing_nullifier_witness() -> anyhow::Result<()> { /// Tests that a nullifier witness pointing to a spent nullifier produces an error. #[test] fn proposed_block_fails_on_spent_nullifier_witness() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(2); - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - - let note0 = generate_untracked_note(account0.id(), account1.id()); - - // This tx will use block1 as the reference block. - let tx0 = - generate_tx_with_unauthenticated_notes(&mut chain, account1.id(), slice::from_ref(¬e0)); - - // This batch will use block1 as the reference block. - let batch0 = generate_batch(&mut chain, vec![tx0]); - - // Add the note to the chain so we can consume it in the next step. - chain.add_pending_note(OutputNote::Full(note0.clone())); - let _block2 = chain.prove_next_block()?; + let mut builder = MockChain::builder(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let p2any_note = + builder.add_p2any_note(account0.id(), NoteType::Public, [FungibleAsset::mock(50)])?; + let mut chain = builder.build()?; + chain.prove_next_block()?; - // Create an alternative chain where we consume the note so it is marked as spent in the - // nullifier tree. - let mut alternative_chain = chain.clone(); - let transaction = generate_executed_tx_with_authenticated_notes( - &alternative_chain, - account1.id(), - &[note0.id()], - ); - alternative_chain.add_pending_executed_transaction(&transaction)?; - alternative_chain.prove_next_block()?; - let spent_proof = alternative_chain.nullifier_tree().open(¬e0.nullifier()); + // Consume the note with account 0 and add the transaction to a block. + let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [p2any_note.id()])?; + chain.add_pending_proven_transaction(tx0); + chain.prove_next_block()?; - let batches = vec![batch0.clone()]; - let mut block_inputs = chain.get_block_inputs(&batches)?; + // Consume the (already consumed) note with account 1 and build a batch from it. + let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [p2any_note.id()])?; + let batch1 = chain.create_batch(vec![tx1])?; + let batches = vec![batch1]; + let block_inputs = chain.get_block_inputs(&batches)?; - // Insert the spent nullifier proof from the alternative chain into the block inputs from the - // actual chain. - block_inputs.nullifier_witnesses_mut().insert(note0.nullifier(), spent_proof); + // The block inputs should contain a nullifier witness for the P2ANY note. + assert!(block_inputs.nullifier_witnesses().contains_key(&p2any_note.nullifier())); let error = ProposedBlock::new(block_inputs, batches).unwrap_err(); - assert_matches!(error, ProposedBlockError::NullifierSpent(nullifier) if nullifier == note0.nullifier()); + assert_matches!(error, ProposedBlockError::NullifierSpent(nullifier) => { + assert_eq!(nullifier, p2any_note.nullifier()) + }); Ok(()) } /// Tests that multiple transactions against the same account that start from the same initial state /// commitment but produce different final state commitments produce an error. -/// We test this simply by putting the same transaction in different batches and ensuring that the -/// batch IDs will be unique to avoid triggering the duplicate batches check. #[test] fn proposed_block_fails_on_conflicting_transactions_updating_same_account() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, mut txs, .. } = setup_chain(2); - - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - let random_tx = txs.remove(&0).unwrap(); + let mut builder = MockChain::builder(); + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let note0 = + builder.add_p2any_note(account1.id(), NoteType::Public, [FungibleAsset::mock(100)])?; + let note1 = + builder.add_p2any_note(account1.id(), NoteType::Public, [FungibleAsset::mock(200)])?; + let chain = builder.build()?; - let note0 = generate_tracked_note(&mut chain, account0.id(), account1.id()); - let note1 = generate_tracked_note(&mut chain, account0.id(), account1.id()); // These notes should have different IDs. assert_ne!(note0.id(), note1.id()); - // Add notes to the chain. - chain.prove_next_block()?; - - // Create two different transactions against the same account consuming the same note. - let tx0 = generate_tx_with_authenticated_notes(&mut chain, account1.id(), &[]); + // Create two different transactions against the same account consuming a different note so they + // result in a different final state commitment for the account. + let tx0 = chain.create_authenticated_notes_proven_tx(account1.id(), [note0.id()])?; + let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()])?; - // Add a random tx to batch0 to make it unique. - let batch0 = generate_batch(&mut chain, vec![tx0.clone(), random_tx]); - let batch1 = generate_batch(&mut chain, vec![tx0]); + let batch0 = chain.create_batch(vec![tx0])?; + let batch1 = chain.create_batch(vec![tx1])?; let batches = vec![batch0.clone(), batch1.clone()]; - let block_inputs = chain.get_block_inputs(&batches).expect("failed to get block inputs"); - let error = ProposedBlock::new(block_inputs.clone(), batches.clone()).unwrap_err(); + let error = ProposedBlock::new(block_inputs.clone(), batches).unwrap_err(); assert_matches!(error, ProposedBlockError::ConflictingBatchesUpdateSameAccount { account_id, initial_state_commitment, @@ -549,11 +566,12 @@ fn proposed_block_fails_on_conflicting_transactions_updating_same_account() -> a /// Tests that a missing account witness produces an error. #[test] fn proposed_block_fails_on_missing_account_witness() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, mut txs, .. } = setup_chain(2); - let account0 = accounts.remove(&0).unwrap(); - let tx0 = txs.remove(&0).unwrap(); + let mut builder = MockChain::builder(); + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let chain = builder.build()?; + let tx0 = chain.create_authenticated_notes_proven_tx(account.id(), [])?; - let batch0 = generate_batch(&mut chain, vec![tx0]); + let batch0 = chain.create_batch(vec![tx0])?; let batches = vec![batch0.clone()]; @@ -562,11 +580,11 @@ fn proposed_block_fails_on_missing_account_witness() -> anyhow::Result<()> { let mut block_inputs = chain.get_block_inputs(&batches)?; block_inputs .account_witnesses_mut() - .remove(&account0.id()) + .remove(&account.id()) .expect("account witness should have been fetched"); let error = ProposedBlock::new(block_inputs, batches.clone()).unwrap_err(); - assert_matches!(error, ProposedBlockError::MissingAccountWitness(account_id) if account_id == account0.id()); + assert_matches!(error, ProposedBlockError::MissingAccountWitness(account_id) if account_id == account.id()); Ok(()) } @@ -575,43 +593,33 @@ fn proposed_block_fails_on_missing_account_witness() -> anyhow::Result<()> { /// build on top of each other produce an error when tx 1 is missing from the block. #[test] fn proposed_block_fails_on_inconsistent_account_state_transition() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(2); - let asset = generate_fungible_asset( - 100, - AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(), - ); + let asset = FungibleAsset::mock(200); - let account0 = accounts.remove(&0).unwrap(); - let mut account1 = accounts.remove(&1).unwrap(); - - let note0 = generate_tracked_note_with_asset(&mut chain, account0.id(), account1.id(), asset); - let note1 = generate_tracked_note_with_asset(&mut chain, account0.id(), account1.id(), asset); - let note2 = generate_tracked_note_with_asset(&mut chain, account0.id(), account1.id(), asset); - - // Add notes to the chain. - chain.prove_next_block()?; + let mut builder = MockChain::builder(); + let mut account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let note0 = builder.add_p2any_note(account.id(), NoteType::Public, [asset])?; + let note1 = builder.add_p2any_note(account.id(), NoteType::Public, [asset])?; + let note2 = builder.add_p2any_note(account.id(), NoteType::Public, [asset])?; + let chain = builder.build()?; // Create three transactions on the same account that build on top of each other. - let executed_tx0 = - generate_executed_tx_with_authenticated_notes(&chain, account1.clone(), &[note0.id()]); + let executed_tx0 = chain.create_authenticated_notes_tx(account.clone(), [note0.id()])?; - account1.apply_delta(executed_tx0.account_delta())?; + account.apply_delta(executed_tx0.account_delta())?; // Builds a tx on top of the account state from tx0. - let executed_tx1 = - generate_executed_tx_with_authenticated_notes(&chain, account1.clone(), &[note1.id()]); + let executed_tx1 = chain.create_authenticated_notes_tx(account.clone(), [note1.id()])?; - account1.apply_delta(executed_tx1.account_delta())?; + account.apply_delta(executed_tx1.account_delta())?; // Builds a tx on top of the account state from tx1. - let executed_tx2 = - generate_executed_tx_with_authenticated_notes(&chain, account1.clone(), &[note2.id()]); + let executed_tx2 = chain.create_authenticated_notes_tx(account.clone(), [note2.id()])?; // We will only include tx0 and tx2 and leave out tx1, which will trigger the error condition // that there is no transition from tx0 -> tx2. let tx0 = LocalTransactionProver::default().prove_dummy(executed_tx0.clone())?; let tx2 = LocalTransactionProver::default().prove_dummy(executed_tx2.clone())?; - let batch0 = generate_batch(&mut chain, vec![tx0]); - let batch1 = generate_batch(&mut chain, vec![tx2]); + let batch0 = chain.create_batch(vec![tx0])?; + let batch1 = chain.create_batch(vec![tx2])?; let batches = vec![batch0.clone(), batch1.clone()]; let block_inputs = chain.get_block_inputs(&batches)?; @@ -621,7 +629,7 @@ fn proposed_block_fails_on_inconsistent_account_state_transition() -> anyhow::Re account_id, state_commitment, remaining_state_commitments - } if account_id == account1.id() && + } if account_id == account.id() && state_commitment == executed_tx0.final_account().commitment() && remaining_state_commitments == [executed_tx2.initial_account().commitment()] ); diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs index ef01f1a293..802de2e1b2 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs @@ -5,32 +5,26 @@ use std::vec::Vec; use anyhow::Context; use assert_matches::assert_matches; use miden_lib::testing::account_component::MockAccountComponent; +use miden_lib::testing::note::NoteBuilder; use miden_objects::account::delta::AccountUpdateDetails; use miden_objects::account::{Account, AccountId, AccountStorageMode}; +use miden_objects::asset::FungibleAsset; use miden_objects::block::{BlockInputs, ProposedBlock}; -use miden_objects::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET; -use miden_objects::transaction::{OutputNote, TransactionHeader}; +use miden_objects::note::{Note, NoteType}; +use miden_objects::testing::account_id::ACCOUNT_ID_SENDER; +use miden_objects::transaction::{ExecutedTransaction, OutputNote, TransactionHeader}; +use miden_objects::{Felt, FieldElement}; use miden_tx::LocalTransactionProver; use rand::Rng; -use super::utils::{ - TestSetup, - generate_batch, - generate_executed_tx_with_authenticated_notes, - generate_fungible_asset, - generate_tracked_note_with_asset, - generate_tx_with_expiration, - generate_tx_with_unauthenticated_notes, - generate_untracked_note, - setup_chain, -}; -use crate::kernel_tests::block::utils::generate_conditional_tx; -use crate::{AccountState, Auth, MockChain}; +use super::utils::MockChainBlockExt; +use crate::{AccountState, Auth, MockChain, TxContextInput}; /// Tests that we can build empty blocks. #[test] fn proposed_block_succeeds_with_empty_batches() -> anyhow::Result<()> { - let TestSetup { chain, .. } = setup_chain(2); + let mut chain = MockChain::builder().build()?; + chain.prove_next_block()?; let block_inputs = BlockInputs::new( chain.latest_block_header(), @@ -53,14 +47,20 @@ fn proposed_block_succeeds_with_empty_batches() -> anyhow::Result<()> { /// built. #[test] fn proposed_block_basic_success() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, mut txs, .. } = setup_chain(2); - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - let proven_tx0 = txs.remove(&0).unwrap(); - let proven_tx1 = txs.remove(&1).unwrap(); + let mut builder = MockChain::builder(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let note0 = + builder.add_p2any_note(account0.id(), NoteType::Public, [FungibleAsset::mock(42)])?; + let note1 = + builder.add_p2any_note(account1.id(), NoteType::Public, [FungibleAsset::mock(42)])?; + let chain = builder.build()?; + + let proven_tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()])?; + let proven_tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()])?; - let batch0 = generate_batch(&mut chain, vec![proven_tx0.clone()]); - let batch1 = generate_batch(&mut chain, vec![proven_tx1.clone()]); + let batch0 = chain.create_batch(vec![proven_tx0.clone()])?; + let batch1 = chain.create_batch(vec![proven_tx1.clone()])?; let batches = [batch0, batch1]; let block_inputs = chain.get_block_inputs(&batches)?; @@ -112,34 +112,27 @@ fn proposed_block_basic_success() -> anyhow::Result<()> { /// Tests that account updates are correctly aggregated into a block-level account update. #[test] fn proposed_block_aggregates_account_state_transition() -> anyhow::Result<()> { - // We need authentication because we're modifying accounts with the input notes. - let TestSetup { mut chain, mut accounts, .. } = setup_chain(2); - let asset = generate_fungible_asset( - 100, - AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(), - ); - - let account0 = accounts.remove(&0).unwrap(); - let mut account1 = accounts.remove(&1).unwrap(); + let asset = FungibleAsset::mock(100); + let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER)?; - let note0 = generate_tracked_note_with_asset(&mut chain, account0.id(), account1.id(), asset); - let note1 = generate_tracked_note_with_asset(&mut chain, account0.id(), account1.id(), asset); - let note2 = generate_tracked_note_with_asset(&mut chain, account0.id(), account1.id(), asset); + let mut builder = MockChain::builder(); + let mut account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let note0 = builder.add_p2id_note(sender_id, account1.id(), &[asset], NoteType::Private)?; + let note1 = builder.add_p2id_note(sender_id, account1.id(), &[asset], NoteType::Public)?; + let note2 = builder.add_p2id_note(sender_id, account1.id(), &[asset], NoteType::Public)?; + let mut chain = builder.build()?; // Add notes to the chain. chain.prove_next_block()?; // Create three transactions on the same account that build on top of each other. - let executed_tx0 = - generate_executed_tx_with_authenticated_notes(&chain, account1.id(), &[note0.id()]); + let executed_tx0 = chain.create_authenticated_notes_tx(account1.id(), [note0.id()])?; account1.apply_delta(executed_tx0.account_delta())?; - let executed_tx1 = - generate_executed_tx_with_authenticated_notes(&chain, account1.clone(), &[note1.id()]); + let executed_tx1 = chain.create_authenticated_notes_tx(account1.clone(), [note1.id()])?; account1.apply_delta(executed_tx1.account_delta())?; - let executed_tx2 = - generate_executed_tx_with_authenticated_notes(&chain, account1.clone(), &[note2.id()]); + let executed_tx2 = chain.create_authenticated_notes_tx(account1.clone(), [note2.id()])?; let [tx0, tx1, tx2] = [executed_tx0, executed_tx1, executed_tx2] .into_iter() @@ -148,8 +141,8 @@ fn proposed_block_aggregates_account_state_transition() -> anyhow::Result<()> { .try_into() .expect("we should have provided three executed txs"); - let batch0 = generate_batch(&mut chain, vec![tx2.clone()]); - let batch1 = generate_batch(&mut chain, vec![tx0.clone(), tx1.clone()]); + let batch0 = chain.create_batch(vec![tx2.clone()])?; + let batch1 = chain.create_batch(vec![tx0.clone(), tx1.clone()])?; let batches = vec![batch0.clone(), batch1.clone()]; let block_inputs = chain.get_block_inputs(&batches).unwrap(); @@ -185,27 +178,24 @@ fn proposed_block_aggregates_account_state_transition() -> anyhow::Result<()> { /// Tests that unauthenticated notes can be authenticated when inclusion proofs are provided. #[test] fn proposed_block_authenticating_unauthenticated_notes() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(3); - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - let account2 = accounts.remove(&2).unwrap(); + let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER)?; - let note0 = generate_untracked_note(account0.id(), account1.id()); - let note1 = generate_untracked_note(account0.id(), account2.id()); + let mut builder = MockChain::builder(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let note0 = builder.add_p2id_note(sender_id, account0.id(), &[], NoteType::Private)?; + let note1 = builder.add_p2id_note(sender_id, account1.id(), &[], NoteType::Public)?; + let chain = builder.build()?; // These txs will use block1 as the reference block. let tx0 = - generate_tx_with_unauthenticated_notes(&mut chain, account1.id(), slice::from_ref(¬e0)); + chain.create_unauthenticated_notes_proven_tx(account0.id(), slice::from_ref(¬e0))?; let tx1 = - generate_tx_with_unauthenticated_notes(&mut chain, account2.id(), slice::from_ref(¬e1)); + chain.create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(¬e1))?; // These batches will use block1 as the reference block. - let batch0 = generate_batch(&mut chain, vec![tx0.clone()]); - let batch1 = generate_batch(&mut chain, vec![tx1.clone()]); - - chain.add_pending_note(OutputNote::Full(note0.clone())); - chain.add_pending_note(OutputNote::Full(note1.clone())); - chain.prove_next_block()?; + let batch0 = chain.create_batch(vec![tx0.clone()])?; + let batch1 = chain.create_batch(vec![tx1.clone()])?; let batches = [batch0, batch1]; // This block will use block2 as the reference block. @@ -236,16 +226,19 @@ fn proposed_block_authenticating_unauthenticated_notes() -> anyhow::Result<()> { /// Tests that a batch that expires at the block being proposed is still accepted. #[test] fn proposed_block_with_batch_at_expiration_limit() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(2); + let mut builder = MockChain::builder(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let mut chain = builder.build()?; + + chain.prove_next_block()?; let block1_num = chain.block_header(1).block_num(); - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - let tx0 = generate_tx_with_expiration(&mut chain, account0.id(), block1_num + 5); - let tx1 = generate_tx_with_expiration(&mut chain, account1.id(), block1_num + 2); + let tx0 = chain.create_expiring_proven_tx(account0.id(), block1_num + 5)?; + let tx1 = chain.create_expiring_proven_tx(account1.id(), block1_num + 2)?; - let batch0 = generate_batch(&mut chain, vec![tx0]); - let batch1 = generate_batch(&mut chain, vec![tx1]); + let batch0 = chain.create_batch(vec![tx0])?; + let batch1 = chain.create_batch(vec![tx1])?; // sanity check: batch 1 should expire at block 3. assert_eq!(batch1.batch_expiration_block_num().as_u32(), 3); @@ -272,18 +265,23 @@ fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow: .with_component(MockAccountComponent::with_empty_slots()); let mut builder = MockChain::builder(); - let mut account0 = builder.add_account_from_builder( Auth::Conditional, account_builder, AccountState::Exists, )?; + let noop_note0 = + NoteBuilder::new(ACCOUNT_ID_SENDER.try_into().unwrap(), &mut rand::rng()).build()?; + let noop_note1 = + NoteBuilder::new(ACCOUNT_ID_SENDER.try_into().unwrap(), &mut rand::rng()).build()?; + builder.add_note(OutputNote::Full(noop_note0.clone())); + builder.add_note(OutputNote::Full(noop_note1.clone())); let mut chain = builder.build()?; - let noop_tx = generate_conditional_tx(&mut chain, account0.id(), false); + let noop_tx = generate_conditional_tx(&mut chain, account0.id(), noop_note0, false); account0.apply_delta(noop_tx.account_delta())?; - let state_updating_tx = generate_conditional_tx(&mut chain, account0.clone(), true); + let state_updating_tx = generate_conditional_tx(&mut chain, account0.clone(), noop_note1, true); // sanity check: NOOP transaction's init and final commitment should be the same. assert_eq!(noop_tx.initial_account().commitment(), noop_tx.final_account().commitment()); @@ -297,8 +295,8 @@ fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow: let tx0 = LocalTransactionProver::default().prove_dummy(noop_tx)?; let tx1 = LocalTransactionProver::default().prove_dummy(state_updating_tx)?; - let batch0 = generate_batch(&mut chain, vec![tx0]); - let batch1 = generate_batch(&mut chain, vec![tx1.clone()]); + let batch0 = chain.create_batch(vec![tx0])?; + let batch1 = chain.create_batch(vec![tx1.clone()])?; let batches = vec![batch0.clone(), batch1.clone()]; @@ -311,3 +309,34 @@ fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow: Ok(()) } + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Generates a transaction, which depending on the `modify_storage` flag, does the following: +/// - if `modify_storage` is true, it increments the storage item of the account. +/// - if `modify_storage` is false, it does nothing (NOOP). +/// +/// To make this transaction (always) non-empty, it consumes one "noop note", which does nothing. +fn generate_conditional_tx( + chain: &mut MockChain, + input: impl Into, + noop_note: Note, + modify_storage: bool, +) -> ExecutedTransaction { + let auth_args = [ + // increment nonce if modify_storage is true + if modify_storage { Felt::ONE } else { Felt::ZERO }, + Felt::new(99), + Felt::new(98), + Felt::new(97), + ]; + + let tx_context = chain + .build_tx_context(input.into(), &[noop_note.id()], &[]) + .unwrap() + .auth_args(auth_args.into()) + .build() + .unwrap(); + tx_context.execute_blocking().unwrap() +} diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs index 76d362e342..93e518a889 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs @@ -10,19 +10,14 @@ use miden_objects::account::{Account, AccountBuilder, AccountComponent, AccountI use miden_objects::asset::FungibleAsset; use miden_objects::batch::ProvenBatch; use miden_objects::block::{BlockInputs, BlockNumber, ProposedBlock}; +use miden_objects::note::NoteType; use miden_objects::transaction::ProvenTransactionBuilder; use miden_objects::vm::ExecutionProof; use miden_objects::{AccountTreeError, NullifierTreeError, Word}; use miden_tx::LocalTransactionProver; use winterfell::Proof; -use super::utils::{ - TestSetup, - generate_batch, - generate_executed_tx_with_authenticated_notes, - generate_tracked_note, - setup_chain, -}; +use crate::kernel_tests::block::utils::MockChainBlockExt; use crate::{Auth, MockChain, TransactionContextBuilder}; struct WitnessTestSetup { @@ -34,21 +29,27 @@ struct WitnessTestSetup { /// Setup for a test which returns two inputs for the same block. The valid inputs match the /// commitments of the latest block and the stale inputs match the commitments of the latest block /// minus 1. -fn witness_test_setup() -> WitnessTestSetup { - let TestSetup { mut chain, mut accounts, mut txs, .. } = setup_chain(4); +fn witness_test_setup() -> anyhow::Result { + let mut builder = MockChain::builder(); - let account0 = accounts.remove(&0).context("failed to remove account 0").unwrap(); - let account1 = accounts.remove(&1).context("failed to remove account 1").unwrap(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account2 = builder.add_existing_mock_account(Auth::IncrNonce)?; - let note = generate_tracked_note(&mut chain, account1.id(), account0.id()); - // Add note to chain. - chain.prove_next_block().unwrap(); + let note0 = + builder.add_p2any_note(account0.id(), NoteType::Public, [FungibleAsset::mock(100)])?; + let note1 = + builder.add_p2any_note(account0.id(), NoteType::Public, [FungibleAsset::mock(100)])?; + let note2 = + builder.add_p2any_note(account0.id(), NoteType::Public, [FungibleAsset::mock(100)])?; + + let mut chain = builder.build()?; - let tx0 = generate_executed_tx_with_authenticated_notes(&chain, account0.id(), &[note.id()]); - let tx1 = txs.remove(&1).context("failed to remove tx 1").unwrap(); - let tx2 = txs.remove(&2).context("failed to remove tx 2").unwrap(); + let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()])?; + let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()])?; + let tx2 = chain.create_authenticated_notes_proven_tx(account2.id(), [note2.id()])?; - let batch1 = generate_batch(&mut chain, vec![tx1, tx2]); + let batch1 = chain.create_batch(vec![tx1, tx2])?; let batches = vec![batch1]; let stale_block_inputs = chain.get_block_inputs(&batches).unwrap(); @@ -56,7 +57,7 @@ fn witness_test_setup() -> WitnessTestSetup { let nullifier_root0 = chain.nullifier_tree().root(); // Apply the executed tx and seal a block. This invalidates the block inputs we've just fetched. - chain.add_pending_executed_transaction(&tx0).unwrap(); + chain.add_pending_proven_transaction(tx0); chain.prove_next_block().unwrap(); let valid_block_inputs = chain.get_block_inputs(&batches).unwrap(); @@ -66,11 +67,11 @@ fn witness_test_setup() -> WitnessTestSetup { assert_ne!(chain.account_tree().root(), account_root0); assert_ne!(chain.nullifier_tree().root(), nullifier_root0); - WitnessTestSetup { + Ok(WitnessTestSetup { stale_block_inputs, valid_block_inputs, batches, - } + }) } /// Tests that a proven block cannot be built if witnesses from a stale account tree are used @@ -84,7 +85,7 @@ fn proven_block_fails_on_stale_account_witnesses() -> anyhow::Result<()> { stale_block_inputs, valid_block_inputs, batches, - } = witness_test_setup(); + } = witness_test_setup()?; // Account tree root mismatch. // -------------------------------------------------------------------------------------------- @@ -121,7 +122,7 @@ fn proven_block_fails_on_stale_nullifier_witnesses() -> anyhow::Result<()> { stale_block_inputs, valid_block_inputs, batches, - } = witness_test_setup(); + } = witness_test_setup()?; // Nullifier tree root mismatch. // -------------------------------------------------------------------------------------------- @@ -158,7 +159,7 @@ fn proven_block_fails_on_account_tree_root_mismatch() -> anyhow::Result<()> { mut stale_block_inputs, valid_block_inputs, batches, - } = witness_test_setup(); + } = witness_test_setup()?; // Stale and current account witnesses used together. // -------------------------------------------------------------------------------------------- @@ -204,7 +205,7 @@ fn proven_block_fails_on_nullifier_tree_root_mismatch() -> anyhow::Result<()> { mut stale_block_inputs, valid_block_inputs, batches, - } = witness_test_setup(); + } = witness_test_setup()?; // Stale and current nullifier witnesses used together. // -------------------------------------------------------------------------------------------- @@ -283,7 +284,7 @@ fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> a let existing_account = Account::mock(existing_id.into(), auth_component); builder.add_account(existing_account.clone())?; - let mut mock_chain = builder.build()?; + let mock_chain = builder.build()?; // Execute the account-creating transaction. // -------------------------------------------------------------------------------------------- @@ -293,7 +294,7 @@ fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> a let tx = tx_context.execute_blocking().context("failed to execute account creating tx")?; let tx = LocalTransactionProver::default().prove_dummy(tx)?; - let batch = generate_batch(&mut mock_chain, vec![tx]); + let batch = mock_chain.create_batch(vec![tx])?; let batches = [batch]; let block_inputs = mock_chain.get_block_inputs(batches.iter())?; @@ -338,7 +339,7 @@ fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> a fn proven_block_fails_on_creating_account_with_duplicate_account_id_prefix() -> anyhow::Result<()> { // Construct a new account. // -------------------------------------------------------------------------------------------- - let mut mock_chain = MockChain::new(); + let mock_chain = MockChain::new(); let account = AccountBuilder::new([5; 32]) .with_auth_component(Auth::IncrNonce) .with_component(MockAccountComponent::with_slots(vec![StorageSlot::Value(Word::from( @@ -392,7 +393,7 @@ fn proven_block_fails_on_creating_account_with_duplicate_account_id_prefix() -> // Build a batch from these transactions and attempt to prove a block. // -------------------------------------------------------------------------------------------- - let batch = generate_batch(&mut mock_chain, vec![tx0, tx1]); + let batch = mock_chain.create_batch(vec![tx0, tx1])?; let batches = [batch]; // Sanity check: The block inputs should contain two account witnesses that point to the same diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs index 024096f7e5..f29db75c62 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs @@ -5,6 +5,7 @@ use std::vec::Vec; use anyhow::Context; use miden_block_prover::LocalBlockProver; use miden_objects::MIN_PROOF_SECURITY_LEVEL; +use miden_objects::asset::FungibleAsset; use miden_objects::batch::BatchNoteTree; use miden_objects::block::{ AccountTree, @@ -14,20 +15,11 @@ use miden_objects::block::{ ProposedBlock, }; use miden_objects::crypto::merkle::Smt; -use miden_objects::transaction::{InputNoteCommitment, OutputNote}; -use rand::Rng; - -use super::utils::{ - TestSetup, - generate_batch, - generate_executed_tx_with_authenticated_notes, - generate_output_note, - generate_tracked_note, - generate_tx_with_authenticated_notes, - generate_tx_with_unauthenticated_notes, - setup_chain, -}; -use crate::utils::create_spawn_note; +use miden_objects::note::NoteType; +use miden_objects::transaction::InputNoteCommitment; + +use crate::kernel_tests::block::utils::MockChainBlockExt; +use crate::{Auth, MockChain}; /// Tests the outputs of a proven block with transactions that consume notes, create output notes /// and modify the account's state. @@ -37,37 +29,38 @@ fn proven_block_success() -> anyhow::Result<()> { // computation. // -------------------------------------------------------------------------------------------- - let TestSetup { mut chain, mut accounts, .. } = setup_chain(4); - - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - let account2 = accounts.remove(&2).unwrap(); - let account3 = accounts.remove(&3).unwrap(); - - let output_note0 = generate_output_note(account0.id(), [0; 32]); - let output_note1 = generate_output_note(account1.id(), [1; 32]); - let output_note2 = generate_output_note(account2.id(), [2; 32]); - let output_note3 = generate_output_note(account3.id(), [3; 32]); - - let input_note0 = create_spawn_note([&output_note0])?; - let input_note1 = create_spawn_note([&output_note1])?; - let input_note2 = create_spawn_note([&output_note2])?; - let input_note3 = create_spawn_note([&output_note3])?; - - // Add input notes to chain so we can consume them. - chain.add_pending_note(OutputNote::Full(input_note0.clone())); - chain.add_pending_note(OutputNote::Full(input_note1.clone())); - chain.add_pending_note(OutputNote::Full(input_note2.clone())); - chain.add_pending_note(OutputNote::Full(input_note3.clone())); + let asset = FungibleAsset::mock(100); + let mut builder = MockChain::builder(); + + let account0 = builder.add_existing_mock_account_with_assets(Auth::IncrNonce, [asset])?; + let account1 = builder.add_existing_mock_account_with_assets(Auth::IncrNonce, [asset])?; + let account2 = builder.add_existing_mock_account_with_assets(Auth::IncrNonce, [asset])?; + let account3 = builder.add_existing_mock_account_with_assets(Auth::IncrNonce, [asset])?; + + let output_note0 = + builder.create_p2id_note(account0.id(), account0.id(), [asset], NoteType::Private)?; + let output_note1 = + builder.create_p2id_note(account1.id(), account1.id(), [asset], NoteType::Private)?; + let output_note2 = + builder.create_p2id_note(account2.id(), account2.id(), [asset], NoteType::Private)?; + let output_note3 = + builder.create_p2id_note(account3.id(), account3.id(), [asset], NoteType::Private)?; + + let input_note0 = builder.add_spawn_note([&output_note0])?; + let input_note1 = builder.add_spawn_note([&output_note1])?; + let input_note2 = builder.add_spawn_note([&output_note2])?; + let input_note3 = builder.add_spawn_note([&output_note3])?; + + let mut chain = builder.build()?; chain.prove_next_block()?; - let tx0 = generate_tx_with_authenticated_notes(&mut chain, account0.id(), &[input_note0.id()]); - let tx1 = generate_tx_with_authenticated_notes(&mut chain, account1.id(), &[input_note1.id()]); - let tx2 = generate_tx_with_authenticated_notes(&mut chain, account2.id(), &[input_note2.id()]); - let tx3 = generate_tx_with_authenticated_notes(&mut chain, account3.id(), &[input_note3.id()]); + let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [input_note0.id()])?; + let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [input_note1.id()])?; + let tx2 = chain.create_authenticated_notes_proven_tx(account2.id(), [input_note2.id()])?; + let tx3 = chain.create_authenticated_notes_proven_tx(account3.id(), [input_note3.id()])?; - let batch0 = generate_batch(&mut chain, [tx0.clone(), tx1.clone()].to_vec()); - let batch1 = generate_batch(&mut chain, [tx2.clone(), tx3.clone()].to_vec()); + let batch0 = chain.create_batch(vec![tx0.clone(), tx1.clone()])?; + let batch1 = chain.create_batch(vec![tx2.clone(), tx3.clone()])?; // Sanity check: Batches should have two output notes each. assert_eq!(batch0.output_notes().len(), 2); @@ -205,39 +198,34 @@ fn proven_block_success() -> anyhow::Result<()> { /// of the subtree of the overall block note tree computed from the block's output notes. #[test] fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { - let TestSetup { mut chain, mut accounts, .. } = setup_chain(4); - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - let account2 = accounts.remove(&2).unwrap(); - let account3 = accounts.remove(&3).unwrap(); - - // Use an Rng to randomize the note IDs and therefore their position in the output note batches. - // This is useful to test that the block note tree is correctly computed no matter at what index - // the erased note ends up in. - let mut rng = rand::rng(); - let output_note0 = generate_output_note(account0.id(), rng.random()); - let output_note2 = generate_output_note(account2.id(), rng.random()); - let output_note3 = generate_output_note(account3.id(), rng.random()); + let mut builder = MockChain::builder(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account2 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account3 = builder.add_existing_mock_account(Auth::IncrNonce)?; + + // The builder will use an rng which randomizes the note IDs and therefore their position in the + // output note batches. This is useful to test that the block note tree is correctly + // computed no matter at what index the erased note ends up in. + let output_note0 = builder.create_p2any_note(account0.id(), NoteType::Private, [])?; + let output_note2 = builder.create_p2any_note(account2.id(), NoteType::Private, [])?; + let output_note3 = builder.create_p2any_note(account3.id(), NoteType::Private, [])?; + + // Sanity check that these notes have different IDs. + assert_ne!(output_note0.id(), output_note2.id()); + assert_ne!(output_note2.id(), output_note3.id()); // Create notes that, when consumed, will create the above corresponding output notes. - let note0 = create_spawn_note([&output_note0])?; - let note2 = create_spawn_note([&output_note2])?; - let note3 = create_spawn_note([&output_note3])?; - - // Add note{0,2,3} to the chain so we can consume them. - chain.add_pending_note(OutputNote::Full(note0.clone())); - chain.add_pending_note(OutputNote::Full(note2.clone())); - chain.add_pending_note(OutputNote::Full(note3.clone())); - chain.prove_next_block()?; + let note0 = builder.add_spawn_note([&output_note0])?; + let note2 = builder.add_spawn_note([&output_note2])?; + let note3 = builder.add_spawn_note([&output_note3])?; + let chain = builder.build()?; - let tx0 = generate_tx_with_authenticated_notes(&mut chain, account0.id(), &[note0.id()]); - let tx1 = generate_tx_with_unauthenticated_notes( - &mut chain, - account1.id(), - slice::from_ref(&output_note0), - ); - let tx2 = generate_tx_with_authenticated_notes(&mut chain, account2.id(), &[note2.id()]); - let tx3 = generate_tx_with_authenticated_notes(&mut chain, account3.id(), &[note3.id()]); + let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()])?; + let tx1 = chain + .create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(&output_note0))?; + let tx2 = chain.create_authenticated_notes_proven_tx(account2.id(), [note2.id()])?; + let tx3 = chain.create_authenticated_notes_proven_tx(account3.id(), [note3.id()])?; assert_eq!(tx0.input_notes().num_notes(), 1); assert_eq!(tx0.output_notes().num_notes(), 1); @@ -251,8 +239,8 @@ fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { tx1.input_notes().get_note(0).header().unwrap().id() ); - let batch0 = generate_batch(&mut chain, vec![tx2.clone(), tx0.clone(), tx3.clone()]); - let batch1 = generate_batch(&mut chain, vec![tx1.clone()]); + let batch0 = chain.create_batch(vec![tx2.clone(), tx0.clone(), tx3.clone()])?; + let batch1 = chain.create_batch(vec![tx1.clone()])?; // Sanity check: The batches and contained transactions should have the same input notes (sorted // by nullifier). @@ -316,7 +304,7 @@ fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { .find_map(|(idx, note)| (note.id() == output_note0.id()).then_some(idx)) .copied() .unwrap(); - expected_output_notes_batch0.remove(erased_note_idx as usize); + expected_output_notes_batch0.remove(erased_note_idx); let output_notes_batch0 = &proposed_block.output_note_batches()[0]; // The first batch creates three notes, one of which is erased, so we expect 2 notes in the @@ -350,21 +338,20 @@ fn proven_block_succeeds_with_empty_batches() -> anyhow::Result<()> { // Setup a chain with a non-empty nullifier tree by consuming some notes. // -------------------------------------------------------------------------------------------- - let TestSetup { mut chain, mut accounts, .. } = setup_chain(2); - - let account0 = accounts.remove(&0).unwrap(); - let account1 = accounts.remove(&1).unwrap(); - - // Add notes to the chain we can consume. - let note0 = generate_tracked_note(&mut chain, account1.id(), account0.id()); - let note1 = generate_tracked_note(&mut chain, account0.id(), account1.id()); - chain.prove_next_block()?; + let mut builder = MockChain::builder(); + let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; + let note0 = + builder.add_p2any_note(account0.id(), NoteType::Public, [FungibleAsset::mock(100)])?; + let note1 = + builder.add_p2any_note(account1.id(), NoteType::Public, [FungibleAsset::mock(100)])?; + let mut chain = builder.build()?; - let tx0 = generate_executed_tx_with_authenticated_notes(&chain, account0.id(), &[note0.id()]); - let tx1 = generate_executed_tx_with_authenticated_notes(&chain, account1.id(), &[note1.id()]); + let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()])?; + let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()])?; - chain.add_pending_executed_transaction(&tx0)?; - chain.add_pending_executed_transaction(&tx1)?; + chain.add_pending_proven_transaction(tx0); + chain.add_pending_proven_transaction(tx1); let blockx = chain.prove_next_block()?; // Build a block with empty inputs whose account tree and nullifier tree root are not the empty diff --git a/crates/miden-testing/src/kernel_tests/block/utils.rs b/crates/miden-testing/src/kernel_tests/block/utils.rs index cd1662a412..66e59db91f 100644 --- a/crates/miden-testing/src/kernel_tests/block/utils.rs +++ b/crates/miden-testing/src/kernel_tests/block/utils.rs @@ -1,179 +1,102 @@ -use std::collections::BTreeMap; -use std::vec; use std::vec::Vec; -use miden_lib::note::create_p2id_note; -use miden_lib::testing::note::NoteBuilder; use miden_lib::utils::ScriptBuilder; -use miden_objects::account::{Account, AccountId}; -use miden_objects::asset::{Asset, FungibleAsset}; +use miden_objects::account::AccountId; use miden_objects::batch::ProvenBatch; use miden_objects::block::BlockNumber; -use miden_objects::crypto::rand::RpoRandomCoin; -use miden_objects::note::{Note, NoteId, NoteTag, NoteType}; -use miden_objects::testing::account_id::ACCOUNT_ID_SENDER; -use miden_objects::transaction::{ - ExecutedTransaction, - OutputNote, - ProvenTransaction, - TransactionScript, -}; -use miden_objects::{Felt, ONE, Word, ZERO}; +use miden_objects::note::{Note, NoteId}; +use miden_objects::transaction::{ExecutedTransaction, ProvenTransaction, TransactionScript}; use miden_tx::LocalTransactionProver; -use rand::rngs::SmallRng; -use rand::{Rng, SeedableRng}; -use crate::{Auth, MockChain, TxContextInput}; - -pub struct TestSetup { - pub chain: MockChain, - pub accounts: BTreeMap, - pub txs: BTreeMap, -} - -pub fn generate_tracked_note( - chain: &mut MockChain, - sender: AccountId, - receiver: AccountId, -) -> Note { - let note = generate_untracked_note_internal(sender, receiver, vec![]); - chain.add_pending_note(OutputNote::Full(note.clone())); - note -} - -pub fn generate_tracked_note_with_asset( - chain: &mut MockChain, - sender: AccountId, - receiver: AccountId, - asset: Asset, -) -> Note { - let note = generate_untracked_note_internal(sender, receiver, vec![asset]); - chain.add_pending_note(OutputNote::Full(note.clone())); - note -} - -pub fn generate_untracked_note(sender: AccountId, receiver: AccountId) -> Note { - generate_untracked_note_internal(sender, receiver, vec![]) -} - -/// Creates a NOP output note sent by the given sender. -pub fn generate_output_note(sender: AccountId, seed: [u8; 32]) -> Note { - let mut rng = SmallRng::from_seed(seed); - NoteBuilder::new(sender, &mut rng) - .note_type(NoteType::Private) - .tag(NoteTag::for_local_use_case(0, 0).unwrap().into()) - .build() - .unwrap() -} - -fn generate_untracked_note_internal( - sender: AccountId, - receiver: AccountId, - asset: Vec, -) -> Note { - // Use OS-randomness so that notes with the same sender and target have different note IDs. - let mut rng = RpoRandomCoin::new(Word::new([ - Felt::new(rand::rng().random()), - Felt::new(rand::rng().random()), - Felt::new(rand::rng().random()), - Felt::new(rand::rng().random()), - ])); - create_p2id_note(sender, receiver, asset, NoteType::Public, Default::default(), &mut rng) - .unwrap() +use crate::{MockChain, TxContextInput}; + +// MOCK CHAIN BUILDER EXTENSION +// ================================================================================================ + +/// Provides convenience methods for testing. +pub trait MockChainBlockExt { + fn create_authenticated_notes_tx( + &self, + input: impl Into, + notes: impl IntoIterator, + ) -> anyhow::Result; + + fn create_authenticated_notes_proven_tx( + &self, + input: impl Into, + notes: impl IntoIterator, + ) -> anyhow::Result; + + fn create_unauthenticated_notes_proven_tx( + &self, + account_id: AccountId, + notes: &[Note], + ) -> anyhow::Result; + + fn create_expiring_proven_tx( + &self, + input: impl Into, + expiration_block: BlockNumber, + ) -> anyhow::Result; + + fn create_batch(&self, txs: Vec) -> anyhow::Result; } -pub fn generate_fungible_asset(amount: u64, faucet_id: AccountId) -> Asset { - FungibleAsset::new(faucet_id, amount).unwrap().into() -} +impl MockChainBlockExt for MockChain { + fn create_authenticated_notes_tx( + &self, + input: impl Into, + notes: impl IntoIterator, + ) -> anyhow::Result { + let notes = notes.into_iter().collect::>(); + let tx_context = self.build_tx_context(input, ¬es, &[])?.build()?; + tx_context.execute_blocking().map_err(From::from) + } -pub fn generate_executed_tx_with_authenticated_notes( - chain: &MockChain, - input: impl Into, - notes: &[NoteId], -) -> ExecutedTransaction { - let tx_context = chain - .build_tx_context(input, notes, &[]) - .expect("failed to build tx context") - .build() - .unwrap(); - tx_context.execute_blocking().unwrap() -} + fn create_authenticated_notes_proven_tx( + &self, + input: impl Into, + notes: impl IntoIterator, + ) -> anyhow::Result { + let executed_tx = self.create_authenticated_notes_tx(input, notes)?; + LocalTransactionProver::default().prove_dummy(executed_tx).map_err(From::from) + } -pub fn generate_tx_with_authenticated_notes( - chain: &mut MockChain, - account_id: AccountId, - notes: &[NoteId], -) -> ProvenTransaction { - let executed_tx = generate_executed_tx_with_authenticated_notes(chain, account_id, notes); - LocalTransactionProver::default().prove_dummy(executed_tx).unwrap() -} + fn create_unauthenticated_notes_proven_tx( + &self, + account_id: AccountId, + notes: &[Note], + ) -> anyhow::Result { + let tx_context = self.build_tx_context(account_id, &[], notes)?.build()?; + let executed_tx = tx_context.execute_blocking()?; + LocalTransactionProver::default().prove_dummy(executed_tx).map_err(From::from) + } -/// Generates a transaction, which depending on the `modify_storage` flag, does the following: -/// - if `modify_storage` is true, it increments the storage item of the account. -/// - if `modify_storage` is false, it does nothing (NOOP). -/// -/// To make this transaction (always) non-empty, it consumes one "noop note", which does nothing. -pub fn generate_conditional_tx( - chain: &mut MockChain, - input: impl Into, - modify_storage: bool, -) -> ExecutedTransaction { - let noop_note = NoteBuilder::new(ACCOUNT_ID_SENDER.try_into().unwrap(), &mut rand::rng()) - .build() - .expect("failed to create the noop note"); - chain.add_pending_note(OutputNote::Full(noop_note.clone())); - chain.prove_next_block().unwrap(); - - let auth_args = [ - if modify_storage { ONE } else { ZERO }, // increment nonce if modify_storage is true - Felt::new(99), - Felt::new(98), - Felt::new(97), - ]; - - let tx_context = chain - .build_tx_context(input.into(), &[noop_note.id()], &[]) - .unwrap() - .extend_input_notes(vec![noop_note]) - .auth_args(auth_args.into()) - .build() - .unwrap(); - tx_context.execute_blocking().unwrap() -} + fn create_expiring_proven_tx( + &self, + input: impl Into, + expiration_block: BlockNumber, + ) -> anyhow::Result { + let expiration_delta = expiration_block + .checked_sub(self.latest_block_header().block_num().as_u32()) + .unwrap(); + + let tx_context = self + .build_tx_context(input, &[], &[])? + .tx_script(update_expiration_tx_script(expiration_delta.as_u32() as u16)) + .build()?; + let executed_tx = tx_context.execute_blocking()?; + LocalTransactionProver::default().prove_dummy(executed_tx).map_err(From::from) + } -/// Generates a transaction that expires at the given block number. -pub fn generate_tx_with_expiration( - chain: &mut MockChain, - input: impl Into, - expiration_block: BlockNumber, -) -> ProvenTransaction { - let expiration_delta = expiration_block - .checked_sub(chain.latest_block_header().block_num().as_u32()) - .unwrap(); - - let tx_context = chain - .build_tx_context(input, &[], &[]) - .expect("failed to build tx context") - .tx_script(update_expiration_tx_script(expiration_delta.as_u32() as u16)) - .build() - .unwrap(); - let executed_tx = tx_context.execute_blocking().unwrap(); - LocalTransactionProver::default().prove_dummy(executed_tx).unwrap() + fn create_batch(&self, txs: Vec) -> anyhow::Result { + self.propose_transaction_batch(txs) + .map(|batch| self.prove_transaction_batch(batch).unwrap()) + } } -pub fn generate_tx_with_unauthenticated_notes( - chain: &mut MockChain, - account_id: AccountId, - notes: &[Note], -) -> ProvenTransaction { - let tx_context = chain - .build_tx_context(account_id, &[], notes) - .expect("failed to build tx context") - .build() - .unwrap(); - let executed_tx = tx_context.execute_blocking().unwrap(); - LocalTransactionProver::default().prove_dummy(executed_tx).unwrap() -} +// HELPER FUNCTIONS +// ================================================================================================ fn update_expiration_tx_script(expiration_delta: u16) -> TransactionScript { let code = format!( @@ -189,46 +112,3 @@ fn update_expiration_tx_script(expiration_delta: u16) -> TransactionScript { ScriptBuilder::default().compile_tx_script(code).unwrap() } - -pub fn generate_batch(chain: &mut MockChain, txs: Vec) -> ProvenBatch { - chain - .propose_transaction_batch(txs) - .map(|batch| chain.prove_transaction_batch(batch).unwrap()) - .unwrap() -} - -/// Setup a test mock chain with the number of accounts, notes and transactions. -/// -/// This is merely generating some valid data for testing purposes. -pub fn setup_chain(num_accounts: usize) -> TestSetup { - let mut builder = MockChain::builder(); - let sender_account = builder - .add_existing_mock_account(Auth::IncrNonce) - .expect("adding account should be valid"); - let mut accounts = BTreeMap::new(); - let mut notes = BTreeMap::new(); - let mut txs = BTreeMap::new(); - - for i in 0..num_accounts { - let account = builder - .add_existing_mock_account(Auth::IncrNonce) - .expect("adding account should be valid"); - let note = builder - .add_p2id_note(sender_account.id(), account.id(), &[], NoteType::Public) - .expect("adding p2id note should be valid"); - accounts.insert(i, account); - notes.insert(i, note); - } - - let mut chain = builder.build().expect("building chain should be valid"); - - chain.prove_next_block().expect("failed to prove block"); - - for i in 0..num_accounts { - let tx = - generate_tx_with_authenticated_notes(&mut chain, accounts[&i].id(), &[notes[&i].id()]); - txs.insert(i, tx); - } - - TestSetup { chain, accounts, txs } -} diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index 63a391006e..98cc4c43dd 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -38,7 +38,7 @@ use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use winter_rand_utils::rand_value; -use crate::utils::create_p2any_note; +use crate::utils::create_public_p2any_note; use crate::{Auth, MockChain, TransactionContextBuilder}; // ACCOUNT DELTA TESTS @@ -56,7 +56,8 @@ use crate::{Auth, MockChain, TransactionContextBuilder}; fn empty_account_delta_commitment_is_empty_word() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::Noop)?; - let p2any_note = builder.add_p2any_note(AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(), [])?; + let p2any_note = + builder.add_p2any_note(AccountId::try_from(ACCOUNT_ID_SENDER)?, NoteType::Public, [])?; let mock_chain = builder.build()?; let executed_tx = mock_chain @@ -669,7 +670,10 @@ fn asset_and_storage_delta() -> anyhow::Result<()> { FungibleAsset::new(faucet_id_3, CONSUMED_ASSET_3_AMOUNT)?.into(); let nonfungible_asset_1: Asset = NonFungibleAsset::mock(&NON_FUNGIBLE_ASSET_DATA_2); - create_p2any_note(account.id(), [fungible_asset_1, fungible_asset_3, nonfungible_asset_1]) + create_public_p2any_note( + account.id(), + [fungible_asset_1, fungible_asset_3, nonfungible_asset_1], + ) }; let tx_context = TransactionContextBuilder::new(account) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index b4d7423943..913aff0fe9 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -28,7 +28,7 @@ use miden_tx::{ use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; -use crate::utils::create_p2any_note; +use crate::utils::create_public_p2any_note; use crate::{Auth, MockChain, TransactionContextBuilder, TxContextInput}; #[tokio::test] @@ -84,7 +84,7 @@ async fn check_note_consumability_well_known_notes_success() -> anyhow::Result<( } #[rstest::rstest] -#[case::one(vec![create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)])])] +#[case::one(vec![create_public_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)])])] #[tokio::test] async fn check_note_consumability_custom_notes_success( #[case] notes: Vec, diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 358c5c657e..062f81905d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -24,7 +24,7 @@ use miden_objects::testing::account_id::{ }; use miden_objects::{EMPTY_WORD, Felt, ONE, WORD_SIZE, Word}; -use crate::utils::create_p2any_note; +use crate::utils::create_public_p2any_note; use crate::{ Auth, MockChain, @@ -78,8 +78,10 @@ fn test_active_note_get_sender() -> anyhow::Result<()> { let tx_context = { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); - let input_note = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); + let input_note = create_public_p2any_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + [FungibleAsset::mock(100)], + ); TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note]) .build()? diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 67231c7ad5..27a073f8af 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -39,7 +39,7 @@ use rand::rng; use super::{ZERO, create_mock_notes_procedure}; use crate::kernel_tests::tx::ProcessMemoryExt; -use crate::utils::{create_p2any_note, create_spawn_note}; +use crate::utils::{create_public_p2any_note, create_spawn_note}; use crate::{ Auth, MockChain, @@ -53,12 +53,16 @@ use crate::{ fn test_epilogue() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let tx_context = { - let output_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); + let output_note_1 = create_public_p2any_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + [FungibleAsset::mock(100)], + ); // input_note_1 is needed for maintaining cohesion of involved assets - let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); + let input_note_1 = create_public_p2any_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + [FungibleAsset::mock(100)], + ); let input_note_2 = create_spawn_note([&output_note_1])?; TransactionContextBuilder::new(account.clone()) .extend_input_notes(vec![input_note_1, input_note_2]) @@ -153,11 +157,11 @@ fn test_compute_output_note_id() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let output_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); + create_public_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); // input_note_1 is needed for maintaining cohesion of involved assets let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); + create_public_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); let input_note_2 = create_spawn_note([&output_note_1])?; TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note_1, input_note_2]) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index 75f3dddf63..05943d2a20 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -39,7 +39,7 @@ use miden_objects::testing::noop_auth_component::NoopAuthComponent; use miden_objects::testing::storage::FAUCET_STORAGE_DATA_SLOT; use miden_objects::{Felt, Word}; -use crate::utils::create_p2any_note; +use crate::utils::create_public_p2any_note; use crate::{TransactionContextBuilder, assert_execution_error, assert_transaction_executor_error}; // FUNGIBLE FAUCET MINT TESTS @@ -336,7 +336,7 @@ fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, Felt::new(FUNGIBLE_FAUCET_INITIAL_BALANCE), ); - let note = create_p2any_note( + let note = create_public_p2any_note( ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::new(account.id(), 100u64).unwrap().into()], ); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fee.rs b/crates/miden-testing/src/kernel_tests/tx/test_fee.rs index 63fbd83bdd..25c66dde45 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fee.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fee.rs @@ -9,7 +9,7 @@ use miden_objects::{self, Felt, Word}; use miden_tx::TransactionExecutorError; use winter_rand_utils::rand_value; -use crate::utils::create_p2any_note; +use crate::utils::create_public_p2any_note; use crate::{Auth, MockChain}; // FEE TESTS @@ -166,11 +166,12 @@ fn create_output_notes() -> anyhow::Result { let note_asset1 = FungibleAsset::mock(500).unwrap_fungible(); // This creates a note that adds the given assets to the account vault. - let asset_note = create_p2any_note(account.id(), [Asset::from(note_asset0.add(note_asset1)?)]); + let asset_note = + create_public_p2any_note(account.id(), [Asset::from(note_asset0.add(note_asset1)?)]); builder.add_note(OutputNote::Full(asset_note.clone())); - let output_note0 = create_p2any_note(account.id(), [note_asset0.into()]); - let output_note1 = create_p2any_note(account.id(), [note_asset1.into()]); + let output_note0 = create_public_p2any_note(account.id(), [note_asset0.into()]); + let output_note1 = create_public_p2any_note(account.id(), [note_asset1.into()]); let spawn_note = builder.add_spawn_note([&output_note0, &output_note1])?; builder diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index b110988b4c..ba836e126b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -48,7 +48,7 @@ use miden_objects::{Felt, Word, ZERO}; use super::{TestSetup, setup_test}; use crate::kernel_tests::tx::ProcessMemoryExt; -use crate::utils::create_p2any_note; +use crate::utils::create_public_p2any_note; use crate::{Auth, MockChain, TransactionContextBuilder, assert_execution_error}; #[test] @@ -213,13 +213,17 @@ fn test_get_output_notes_commitment() -> anyhow::Result<()> { Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let output_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); + create_public_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [FungibleAsset::mock(100)]); - let input_note_1 = - create_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [FungibleAsset::mock(100)]); + let input_note_1 = create_public_p2any_note( + ACCOUNT_ID_PRIVATE_SENDER.try_into()?, + [FungibleAsset::mock(100)], + ); - let input_note_2 = - create_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [FungibleAsset::mock(200)]); + let input_note_2 = create_public_p2any_note( + ACCOUNT_ID_PRIVATE_SENDER.try_into()?, + [FungibleAsset::mock(200)], + ); TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note_1, input_note_2]) @@ -625,8 +629,10 @@ fn test_build_recipient_hash() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); - let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); + let input_note_1 = create_public_p2any_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + [FungibleAsset::mock(100)], + ); TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note_1]) .build()? diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index 39cad1ae48..8e2d306b4d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -95,7 +95,7 @@ use rand_chacha::ChaCha20Rng; use super::{Felt, ZERO}; use crate::kernel_tests::tx::ProcessMemoryExt; -use crate::utils::{create_p2any_note, input_note_data_ptr}; +use crate::utils::{create_public_p2any_note, input_note_data_ptr}; use crate::{ Auth, MockChain, @@ -110,12 +110,18 @@ fn test_transaction_prologue() -> anyhow::Result<()> { let mut tx_context = { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); - let input_note_1 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); - let input_note_2 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(100)]); - let input_note_3 = - create_p2any_note(ACCOUNT_ID_SENDER.try_into().unwrap(), [FungibleAsset::mock(111)]); + let input_note_1 = create_public_p2any_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + [FungibleAsset::mock(100)], + ); + let input_note_2 = create_public_p2any_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + [FungibleAsset::mock(100)], + ); + let input_note_3 = create_public_p2any_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + [FungibleAsset::mock(111)], + ); TransactionContextBuilder::new(account) .extend_input_notes(vec![input_note_1, input_note_2, input_note_3]) .build()? diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 308c1a8f4d..76560ed76e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -60,7 +60,7 @@ use miden_tx::auth::UnreachableAuth; use miden_tx::{TransactionExecutor, TransactionExecutorError}; use super::{Felt, ONE}; -use crate::utils::{create_p2any_note, create_spawn_note}; +use crate::utils::{create_public_p2any_note, create_spawn_note}; use crate::{Auth, MockChain, TransactionContextBuilder}; /// Tests that executing a transaction with a foreign account whose inputs are stale fails. @@ -111,7 +111,7 @@ async fn consuming_note_created_in_future_block_fails() -> anyhow::Result<()> { let asset = FungibleAsset::mock(400); let account1 = builder.add_existing_wallet_with_assets(Auth::BasicAuth, [asset])?; let account2 = builder.add_existing_wallet_with_assets(Auth::BasicAuth, [asset])?; - let output_note = create_p2any_note(account1.id(), [asset]); + let output_note = create_public_p2any_note(account1.id(), [asset]); let spawn_note = builder.add_spawn_note([&output_note])?; let mut mock_chain = builder.build()?; mock_chain.prove_until_block(10u32)?; @@ -505,10 +505,9 @@ fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { )?; let input_note = create_spawn_note(vec![&output_note])?; - let mut mock_chain = MockChain::new(); - - mock_chain.add_pending_note(OutputNote::Full(input_note.clone())); - mock_chain.prove_next_block()?; + let mut builder = MockChain::builder(); + builder.add_note(OutputNote::Full(input_note.clone())); + let mock_chain = builder.build()?; let tx_context = mock_chain.build_tx_context(account, &[input_note.id()], &[])?.build()?; let ref_block_num = tx_context.tx_inputs().block_header().block_num().as_u32(); diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index fe45bf0fa7..dc1f115447 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -115,7 +115,7 @@ use crate::{MockChainBuilder, TransactionContextBuilder}; /// let sender = builder.add_existing_wallet(Auth::IncrNonce)?; /// let fungible_asset = FungibleAsset::mock(10).unwrap_fungible(); /// -/// // Add a pending P2ID note to the chain. +/// // Add a P2ID note to the chain. /// let note = builder.add_p2id_note( /// sender.id(), /// receiver.id(), @@ -846,14 +846,6 @@ impl MockChain { self.pending_transactions.push(transaction); } - /// Adds the given [`OutputNote`] to the list of pending notes. - /// - /// A block has to be created to add the note to that block and make it available in the chain - /// state, e.g. using [`MockChain::prove_next_block`]. - pub fn add_pending_note(&mut self, note: OutputNote) { - self.pending_output_notes.push(note); - } - // PRIVATE HELPERS // ---------------------------------------------------------------------------------------- diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 5a6dd7e9d1..a320e4f546 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -30,6 +30,7 @@ use miden_objects::block::{ OutputNoteBatch, ProvenBlock, }; +use miden_objects::crypto::rand::FeltRng; use miden_objects::note::{Note, NoteDetails, NoteType}; use miden_objects::testing::account_id::ACCOUNT_ID_NATIVE_ASSET_FAUCET; use miden_objects::transaction::{OrderedTransactionHeaders, OutputNote}; @@ -395,7 +396,7 @@ impl MockChainBuilder { Ok(()) } - // NOTE METHODS + // NOTE ADD METHODS // ---------------------------------------------------------------------------------------- /// Adds the provided note to the initial chain state. @@ -403,18 +404,17 @@ impl MockChainBuilder { self.notes.push(note.into()); } - /// Creates a new P2ANY note from the provided parameters and adds it to the list of genesis - /// notes. This note is similar to a P2ID note but can be consumed by any account. + /// Creates a new P2ANY note from the provided parameters and adds it to the list of + /// genesis notes. /// - /// In the created [`MockChain`], the note will be immediately spendable by `target_account_id` - /// and carries no additional reclaim or timelock conditions. + /// This note is similar to a P2ID note but can be consumed by any account. pub fn add_p2any_note( &mut self, sender_account_id: AccountId, - asset: impl IntoIterator, + note_type: NoteType, + assets: impl IntoIterator, ) -> anyhow::Result { - let note = create_p2any_note(sender_account_id, asset); - + let note = self.create_p2any_note(sender_account_id, note_type, assets)?; self.add_note(OutputNote::Full(note.clone())); Ok(note) @@ -432,15 +432,12 @@ impl MockChainBuilder { asset: &[Asset], note_type: NoteType, ) -> Result { - let note = create_p2id_note( + let note = self.create_p2id_note( sender_account_id, target_account_id, - asset.to_vec(), + asset.iter().copied(), note_type, - Default::default(), - &mut self.rng, )?; - self.add_note(OutputNote::Full(note.clone())); Ok(note) @@ -545,6 +542,49 @@ impl MockChainBuilder { Ok(note) } + // NOTE CREATE METHODS + // ---------------------------------------------------------------------------------------- + + /// Creates a new P2ID note from the provided parameters. + /// + /// The note is _not_ added to the list of genesis notes. It must be created by the caller to + /// make it available in the mock chain, e.g. using [`Self::add_spawn_note`]. + /// + /// This is a convenience wrapper around [`create_p2id_note`]. + pub fn create_p2id_note( + &mut self, + sender_account_id: AccountId, + target_account_id: AccountId, + assets: impl IntoIterator, + note_type: NoteType, + ) -> Result { + let note = create_p2id_note( + sender_account_id, + target_account_id, + assets.into_iter().collect::>(), + note_type, + Default::default(), + &mut self.rng, + )?; + + Ok(note) + } + + /// Creates a new P2ANY note from the provided parameters. + /// + /// This note is similar to a P2ID note but can be consumed by any account. + /// + /// The note is _not_ added to the list of genesis notes. It must be created by the caller to + /// make it available in the mock chain, e.g. using [`Self::add_spawn_note`]. + pub fn create_p2any_note( + &mut self, + sender_account_id: AccountId, + note_type: NoteType, + assets: impl IntoIterator, + ) -> anyhow::Result { + Ok(create_p2any_note(sender_account_id, note_type, self.rng.draw_word(), assets)) + } + // HELPER FUNCTIONS // ---------------------------------------------------------------------------------------- diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index f6bf33a5e5..c920ce2984 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -269,10 +269,11 @@ impl TransactionContextBuilder { // If no specific transaction inputs was provided, initialize an ad-hoc mockchain // to generate valid block header/MMR data - let mut mock_chain = MockChain::default(); + let mut builder = MockChain::builder(); for i in self.input_notes { - mock_chain.add_pending_note(OutputNote::Full(i)); + builder.add_note(OutputNote::Full(i)); } + let mut mock_chain = builder.build()?; mock_chain.prove_next_block().context("failed to prove first block")?; mock_chain.prove_next_block().context("failed to prove second block")?; diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index c6b5624dcb..0adee8e3b1 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -3,9 +3,10 @@ use alloc::vec::Vec; use miden_lib::testing::note::NoteBuilder; use miden_lib::transaction::{TransactionKernel, memory}; +use miden_objects::Word; use miden_objects::account::AccountId; use miden_objects::asset::Asset; -use miden_objects::note::Note; +use miden_objects::note::{Note, NoteType}; use miden_objects::testing::storage::prepare_assets; use miden_processor::Felt; use rand::SeedableRng; @@ -73,13 +74,31 @@ pub fn input_note_data_ptr(note_idx: u32) -> memory::MemoryAddress { // HELPER NOTES // ================================================================================================ +/// Creates a public `P2ANY` note. +/// +/// A `P2ANY` note carries `assets` and a script that moves the assets into the executing account's +/// vault. +/// +/// The created note does not require authentication and can be consumed by any account. +pub fn create_public_p2any_note( + sender: AccountId, + assets: impl IntoIterator, +) -> Note { + create_p2any_note(sender, NoteType::Public, Word::from([1, 2, 3, 4u32]), assets) +} + /// Creates a `P2ANY` note. /// /// A `P2ANY` note carries `assets` and a script that moves the assets into the executing account's /// vault. /// /// The created note does not require authentication and can be consumed by any account. -pub fn create_p2any_note(sender: AccountId, assets: impl IntoIterator) -> Note { +pub fn create_p2any_note( + sender: AccountId, + note_type: NoteType, + serial_number: Word, + assets: impl IntoIterator, +) -> Note { let assets: Vec<_> = assets.into_iter().collect(); let mut code_body = String::new(); for i in 0..assets.len() { @@ -133,6 +152,8 @@ pub fn create_p2any_note(sender: AccountId, assets: impl IntoIterator anyhow::Result<(miden_objects::account::Account, MockChain, miden_objects::note::Note)> { +) -> anyhow::Result<(Account, MockChain, Note)> { let component: AccountComponent = MockAccountComponent::with_slots(AccountStorage::mock_storage_slots()).into(); @@ -66,20 +66,19 @@ fn setup_rpo_falcon_acl_test( let mut builder = MockChain::builder(); builder.add_account(account.clone())?; - let mock_chain = builder.build()?; - // Create a mock note to consume (needed to make the transaction non-empty) - let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER)?; - let note = NoteBuilder::new(sender_id, &mut rand::rng()) + let note = NoteBuilder::new(account.id(), &mut rand::rng()) .build() .expect("failed to create mock note"); + builder.add_note(OutputNote::Full(note.clone())); + let mock_chain = builder.build()?; Ok((account, mock_chain, note)) } #[test] fn test_rpo_falcon_acl() -> anyhow::Result<()> { - let (account, mut mock_chain, note) = setup_rpo_falcon_acl_test(false, true)?; + let (account, mock_chain, note) = setup_rpo_falcon_acl_test(false, true)?; // We need to get the authenticator separately for this test let component: AccountComponent = @@ -100,9 +99,6 @@ fn test_rpo_falcon_acl() -> anyhow::Result<()> { } .build_component(); - mock_chain.add_pending_note(OutputNote::Full(note.clone())); - mock_chain.prove_next_block()?; - let tx_script_with_trigger_1 = r#" use.mock::account diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 8a852522ea..3bc3cc217b 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -218,19 +218,9 @@ fn minting_fungible_asset_on_new_faucet_succeeds() -> anyhow::Result<()> { fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let faucet = builder.add_existing_faucet(Auth::BasicAuth, "TST", 200, Some(100))?; - let mut mock_chain = builder.build()?; let fungible_asset = FungibleAsset::new(faucet.id(), 100).unwrap(); - // The Fungible Faucet component is added as the second component after auth, so it's storage - // slot offset will be 2. Check that max_supply at the word's index 0 is 200. The remainder of - // the word is initialized with the metadata of the faucet which we don't need to check. - assert_eq!(faucet.storage().get_item(2).unwrap()[0], Felt::new(200)); - - // Check that the faucet reserved slot has been correctly initialized. - // The already issued amount should be 100. - assert_eq!(faucet.get_token_issuance().unwrap(), Felt::new(100)); - // need to create a note with the fungible asset to be burned let burn_note_script_code = " # burn the asset @@ -254,8 +244,17 @@ fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result< let note = get_note_with_fungible_asset_and_script(fungible_asset, burn_note_script_code); - mock_chain.add_pending_note(OutputNote::Full(note.clone())); - mock_chain.prove_next_block()?; + builder.add_note(OutputNote::Full(note.clone())); + let mock_chain = builder.build()?; + + // The Fungible Faucet component is added as the second component after auth, so it's storage + // slot offset will be 2. Check that max_supply at the word's index 0 is 200. The remainder of + // the word is initialized with the metadata of the faucet which we don't need to check. + assert_eq!(faucet.storage().get_item(2).unwrap()[0], Felt::new(200)); + + // Check that the faucet reserved slot has been correctly initialized. + // The already issued amount should be 100. + assert_eq!(faucet.get_token_issuance().unwrap(), Felt::new(100)); // CONSTRUCT AND EXECUTE TX (Success) // -------------------------------------------------------------------------------------------- From 53778c27a499f1f2414c3851e249df01f1e61194 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Thu, 25 Sep 2025 14:41:01 +0300 Subject: [PATCH 053/133] Extend `miden::{input_note, active_note}` modules (#1933) * feat: impl get_sender in miden::input_note * feat: impl get_metadata for miden::active_note * chore: update changelog --- CHANGELOG.md | 1 + crates/miden-lib/asm/miden/active_note.masm | 59 +++++++++++-------- crates/miden-lib/asm/miden/input_note.masm | 42 +++++++++++++ crates/miden-lib/asm/miden/note.masm | 27 +++++++++ .../src/kernel_tests/tx/test_active_note.rs | 45 ++++++++++++++ .../src/kernel_tests/tx/test_input_note.rs | 49 +++++++++++++++ docs/src/protocol_library.md | 13 ++-- 7 files changed, 205 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea4b357315..4b84bbd59d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [BREAKING] Enabled lazy loading of assets and storage map items for foreign accounts during transaction execution ([#1888](https://github.com/0xMiden/miden-base/pull/1888)). - Added `get_item_init` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). - Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) +- Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933]https://github.com/0xMiden/miden-base/pull/1933). ### Changes diff --git a/crates/miden-lib/asm/miden/active_note.masm b/crates/miden-lib/asm/miden/active_note.masm index 3aed5dca4c..80596b001c 100644 --- a/crates/miden-lib/asm/miden/active_note.masm +++ b/crates/miden-lib/asm/miden/active_note.masm @@ -129,6 +129,38 @@ export.get_inputs # => [num_inputs, dest_ptr] end +#! Returns the metadata of the active note. +#! +#! Inputs: [] +#! Outputs: [METADATA] +#! +#! Where: +#! - METADATA is the metadata of the active note. +#! +#! Panics if: +#! - no note is currently active. +#! +#! Invocation: exec +export.get_metadata + # pad the stack + padw padw padw push.0.0 + # => [pad(14)] + + # push the flag indicating that we want to request metadata from the active note + push.1 + # => [is_active_note = 1, pad(14)] + + exec.kernel_proc_offsets::input_note_get_metadata_offset + # => [offset, is_active_note = 1, pad(14)] + + syscall.exec_kernel_proc + # => [METADATA, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [METADATA] +end + #! Returns the sender of the active note. #! #! Inputs: [] @@ -157,7 +189,7 @@ export.get_sender # => [METADATA, pad(12)] # extract the sender ID from the metadata word - exec.extract_sender_from_metadata + exec.note::extract_sender_from_metadata # => [sender_id_prefix, sender_id_suffix, pad(12)] # clean the stack @@ -340,28 +372,3 @@ proc.write_inputs_to_memory # OS => [num_inputs, dest_ptr] # AS => [] end - -#! Extracts the sender ID from the provided metadata word. -#! -#! Inputs: [METADATA] -#! Outputs: [sender_id_prefix, sender_id_suffix] -#! -#! Where: -#! - METADATA is the metadata of some note. -#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender ID of the note which -#! metadata was provided. -proc.extract_sender_from_metadata - # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_id_prefix] - - # drop aux felt and the felt containing tag, execution hint and payload - drop drop - # => [merged_sender_id_type_hint_tag, sender_id_prefix] - - # extract suffix of sender from merged layout, which means clearing the least significant byte - exec.account_id::shape_suffix - # => [sender_id_suffix, sender_id_prefix] - - # rearrange suffix and prefix - swap - # => [sender_id_prefix, sender_id_suffix] -end diff --git a/crates/miden-lib/asm/miden/input_note.masm b/crates/miden-lib/asm/miden/input_note.masm index f75dda9fd2..228899e11e 100644 --- a/crates/miden-lib/asm/miden/input_note.masm +++ b/crates/miden-lib/asm/miden/input_note.masm @@ -157,6 +157,48 @@ export.get_metadata # => [METADATA] end +#! Returns the sender of the input note with the specified index. +#! +#! Inputs: [note_index] +#! Outputs: [sender_id_prefix, sender_id_suffix] +#! +#! Where: +#! - note_index is the index of the input note whose sender should be returned. +#! - sender_{prefix,suffix} are the prefix and suffix felts of the specified note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! +#! Invocation: exec +export.get_sender + # start padding the stack + push.0 swap + # => [note_index, 0] + + # push the flag indicating that we want to request metadata from the note with the specified + # index + push.0 + # => [is_active_note = 0, note_index, 0] + + exec.kernel_proc_offsets::input_note_get_metadata_offset + # => [offset, is_active_note = 0, note_index, 0] + + # pad the stack + padw swapw padw padw swapdw + # => [offset, is_active_note = 0, note_index, pad(13)] + + syscall.exec_kernel_proc + # => [METADATA, pad(12)] + + # extract the sender ID from the metadata word + exec.note::extract_sender_from_metadata + # => [sender_id_prefix, sender_id_suffix, pad(12)] + + # clean the stack + swapw dropw swapw dropw movdn.5 movdn.5 dropw + # => [sender_id_prefix, sender_id_suffix] +end + #! Returns the inputs commitment and length of the input note with the specified index. #! #! Inputs: [note_index] diff --git a/crates/miden-lib/asm/miden/note.masm b/crates/miden-lib/asm/miden/note.masm index b606c924b3..33fec4af60 100644 --- a/crates/miden-lib/asm/miden/note.masm +++ b/crates/miden-lib/asm/miden/note.masm @@ -1,6 +1,8 @@ use.std::crypto::hashes::rpo use.std::mem +use.miden::account_id + # ERRORS # ================================================================================================= @@ -139,3 +141,28 @@ export.build_recipient_hash swapw hmerge # [RECIPIENT] end + +#! Extracts the sender ID from the provided metadata word. +#! +#! Inputs: [METADATA] +#! Outputs: [sender_id_prefix, sender_id_suffix] +#! +#! Where: +#! - METADATA is the metadata of some note. +#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender ID of the note which +#! metadata was provided. +export.extract_sender_from_metadata + # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_id_prefix] + + # drop aux felt and the felt containing tag, execution hint and payload + drop drop + # => [merged_sender_id_type_hint_tag, sender_id_prefix] + + # extract suffix of sender from merged layout, which means clearing the least significant byte + exec.account_id::shape_suffix + # => [sender_id_suffix, sender_id_prefix] + + # rearrange suffix and prefix + swap + # => [sender_id_prefix, sender_id_suffix] +end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 062f81905d..7fb28abf7b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -73,6 +73,51 @@ fn test_active_note_get_sender_fails_from_tx_script() -> anyhow::Result<()> { Ok(()) } +#[test] +fn test_active_note_get_metadata() -> anyhow::Result<()> { + let tx_context = { + let account = + Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); + let input_note = create_public_p2any_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + [FungibleAsset::mock(100)], + ); + TransactionContextBuilder::new(account) + .extend_input_notes(vec![input_note]) + .build()? + }; + + let code = format!( + r#" + use.$kernel::prologue + use.$kernel::note->note_internal + use.miden::active_note + + begin + exec.prologue::prepare_transaction + exec.note_internal::prepare_note + dropw dropw dropw dropw + + # get the metadata of the active note + exec.active_note::get_metadata + # => [METADATA] + + # assert this metadata + push.{METADATA} + assert_eqw.err="note 0 has incorrect metadata" + + # truncate the stack + swapw dropw + end + "#, + METADATA = Word::from(tx_context.input_notes().get_note(0).note().metadata()) + ); + + tx_context.execute_code(&code)?; + + Ok(()) +} + #[test] fn test_active_note_get_sender() -> anyhow::Result<()> { let tx_context = { diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index 91dc8a85e6..fab581a52e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -144,6 +144,55 @@ fn test_get_recipient_and_metadata() -> anyhow::Result<()> { Ok(()) } +/// Check that a sender of a note with one asset obtained from the `input_note::get_sender` +/// procedure is correct. +#[test] +fn test_get_sender() -> anyhow::Result<()> { + let TestSetup { + mock_chain, + account, + p2id_note_0_assets: _, + p2id_note_1_asset, + p2id_note_2_assets: _, + } = setup_test()?; + + let code = format!( + r#" + use.miden::input_note + + begin + # get the sender from the input note + push.0 + exec.input_note::get_sender + # => [sender_id_prefix, sender_id_suffix] + + # assert the correctness of the prefix + push.{sender_prefix} + assert_eq.err="sender id prefix of the note 0 is incorrect" + # => [sender_id_suffix] + + # assert the correctness of the suffix + push.{sender_suffix} + assert_eq.err="sender id suffix of the note 0 is incorrect" + # => [] + end + "#, + sender_prefix = p2id_note_1_asset.metadata().sender().prefix().as_felt(), + sender_suffix = p2id_note_1_asset.metadata().sender().suffix(), + ); + + let tx_script = ScriptBuilder::default().compile_tx_script(code)?; + + let tx_context = mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note_1_asset])? + .tx_script(tx_script) + .build()?; + + tx_context.execute_blocking()?; + + Ok(()) +} + /// Check that the assets number and assets data obtained from the `input_note::get_assets` /// procedure is correct for each note with zero, one and two different assets. #[test] diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index f72cecddf2..5b5d788d48 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -58,12 +58,13 @@ Active note procedures can be used to fetch data from the note that is currently | Procedure | Description | Context | | --- | --- | --- | -| `get_assets` | Writes the assets of the active note into memory starting at the specified address.

Inputs: `[dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Note | -| `get_recipient` | Returns the recipient of the active note.

Inputs: `[]`
Outputs: `[RECIPIENT]` | Note | -| `get_inputs` | Writes the note's inputs to the specified memory address.

Inputs: `[dest_ptr]`
Outputs: `[num_inputs, dest_ptr]` | Note | +| `get_assets` | Writes the [assets](note.md#assets) of the active note into memory starting at the specified address.

Inputs: `[dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Note | +| `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the active note.

Inputs: `[]`
Outputs: `[RECIPIENT]` | Note | +| `get_inputs` | Writes the note's [inputs](note.md#inputs) to the specified memory address.

Inputs: `[dest_ptr]`
Outputs: `[num_inputs, dest_ptr]` | Note | +| `get_metadata` | Returns the [metadata](note.md#metadata) of the active note.

Inputs: `[]`
Outputs: `[METADATA]` | Note | | `get_sender` | Returns the sender of the active note.

Inputs: `[]`
Outputs: `[sender_id_prefix, sender_id_suffix]` | Note | -| `get_serial_number` | Returns the serial number of the active note.

Inputs: `[]`
Outputs: `[SERIAL_NUMBER]` | Note | -| `get_script_root` | Returns the script root of the active note.

Inputs: `[]`
Outputs: `[SCRIPT_ROOT]` | Note | +| `get_serial_number` | Returns the [serial number](note.md#serial-number) of the active note.

Inputs: `[]`
Outputs: `[SERIAL_NUMBER]` | Note | +| `get_script_root` | Returns the [script root](note.md#script) of the active note.

Inputs: `[]`
Outputs: `[SCRIPT_ROOT]` | Note | | `add_assets_to_account` | Adds all assets from the active note to the account vault.

Inputs: `[]`
Outputs: `[]` | Note | ## Input Note Procedures (`miden::input_note`) @@ -76,6 +77,7 @@ Input note procedures can be used to fetch data on input notes consumed by the t | `get_assets` | Writes the [assets](note.md#assets) of the input note with the specified index into memory starting at the specified address.

Inputs: `[dest_ptr, note_index]`
Outputs: `[num_assets, dest_ptr, note_index]` | Any | | `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[RECIPIENT]` | Any | | `get_metadata` | Returns the [metadata](note.md#metadata) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[METADATA]` | Any | +| `get_sender` | Returns the sender of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[sender_id_prefix, sender_id_suffix]` | Any | | `get_inputs_info` | Returns the [inputs](note.md#inputs) commitment and length of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[NOTE_INPUTS_COMMITMENT, num_inputs]` | Any | | `get_script_root` | Returns the [script root](note.md#script) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[SCRIPT_ROOT]` | Any | | `get_serial_number` | Returns the [serial number](note.md#serial-number) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[SERIAL_NUMBER]` | Any | @@ -104,6 +106,7 @@ Note utility procedures can be used to compute the required utility data or writ | `write_assets_to_memory` | Writes the assets data stored in the advice map to the memory specified by the provided destination pointer.

Inputs: `[ASSETS_COMMITMENT, num_assets, dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Any | | `build_recipient_hash` | Returns the `RECIPIENT` for a specified `SERIAL_NUM`, `SCRIPT_ROOT`, and inputs commitment.

Inputs: `[SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT]`
Outputs: `[RECIPIENT]` | Any | | `build_recipient` | Builds the recipient hash from note inputs, script root, and serial number.

Inputs: `[inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]`
Outputs: `[RECIPIENT]` | Any | +| `extract_sender_from_metadata` | Extracts the sender ID from the provided metadata word.

Inputs: `[METADATA]`
Outputs: `[sender_id_prefix, sender_id_suffix]` | Any | ## Transaction Procedures (`miden::tx`) From 5bfe3d67860056cc50c1e3b71cde7a94739ee0dc Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 25 Sep 2025 18:22:46 +0200 Subject: [PATCH 054/133] feat: Add `StorageMap::{num_entries, num_leaves}` (#1935) --- CHANGELOG.md | 1 + .../src/account/storage/map/mod.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b84bbd59d..ba19432831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Added `get_item_init` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). - Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) - Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933]https://github.com/0xMiden/miden-base/pull/1933). +- Add `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935]https://github.com/0xMiden/miden-base/pull/1935). ### Changes diff --git a/crates/miden-objects/src/account/storage/map/mod.rs b/crates/miden-objects/src/account/storage/map/mod.rs index 3032570eb2..b757c94792 100644 --- a/crates/miden-objects/src/account/storage/map/mod.rs +++ b/crates/miden-objects/src/account/storage/map/mod.rs @@ -113,6 +113,22 @@ impl StorageMap { self.smt.root() } + /// Returns the number of non-empty leaves in this storage map. + /// + /// Note that this may return a different value from [Self::num_entries()] as a single leaf may + /// contain more than one key-value pair. + pub fn num_leaves(&self) -> usize { + self.smt.num_leaves() + } + + /// Returns the number of key-value pairs with non-default values in this storage map. + /// + /// Note that this may return a different value from [Self::num_leaves()] as a single leaf may + /// contain more than one key-value pair. + pub fn num_entries(&self) -> usize { + self.smt.num_entries() + } + /// Returns the value corresponding to the key or [`Self::EMPTY_VALUE`] if the key is not /// associated with a value. pub fn get(&self, raw_key: &Word) -> Word { @@ -244,6 +260,8 @@ mod tests { (Word::from([105, 106, 107, 108u32]), Word::from([5, 6, 7, 8u32])), ]; let storage_map = StorageMap::with_entries(storage_map_leaves_2).unwrap(); + assert_eq!(storage_map.num_entries(), 2); + assert_eq!(storage_map.num_leaves(), 2); let bytes = storage_map.to_bytes(); let deserialized_map = StorageMap::read_from_bytes(&bytes).unwrap(); From 67842dc5356cf2c8cfcc158d0f7579b57a6e7985 Mon Sep 17 00:00:00 2001 From: igamigo Date: Thu, 25 Sep 2025 16:17:00 -0300 Subject: [PATCH 055/133] chore: expose storagemaperror (#1938) --- crates/miden-objects/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/miden-objects/src/lib.rs b/crates/miden-objects/src/lib.rs index 4676db41ef..042b4ddba0 100644 --- a/crates/miden-objects/src/lib.rs +++ b/crates/miden-objects/src/lib.rs @@ -42,6 +42,7 @@ pub use errors::{ ProposedBlockError, ProvenBatchError, ProvenTransactionError, + StorageMapError, TokenSymbolError, TransactionInputError, TransactionOutputError, From 57f5677410850ab3c36447e10908293d9616f9b7 Mon Sep 17 00:00:00 2001 From: juan518munoz <62400508+juan518munoz@users.noreply.github.com> Date: Thu, 25 Sep 2025 17:23:39 -0300 Subject: [PATCH 056/133] feat: add serialization to `Address` type (#1937) * feat: add serialization to `Address` type * ci: fix failing jobs * chore: remove needless repr * chore: simplify test --- CHANGELOG.md | 1 + crates/miden-objects/src/address/mod.rs | 67 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba19432831..09debd5453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Added `get_item_init` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). - Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) - Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933]https://github.com/0xMiden/miden-base/pull/1933). +- Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937)) - Add `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935]https://github.com/0xMiden/miden-base/pull/1935). ### Changes diff --git a/crates/miden-objects/src/address/mod.rs b/crates/miden-objects/src/address/mod.rs index 35df36380d..190029321b 100644 --- a/crates/miden-objects/src/address/mod.rs +++ b/crates/miden-objects/src/address/mod.rs @@ -7,6 +7,8 @@ use alloc::string::{String, ToString}; use bech32::Bech32m; use bech32::primitives::decode::{ByteIter, CheckedHrpstring}; pub use interface::AddressInterface; +use miden_core::utils::{ByteWriter, Deserializable, Serializable}; +use miden_processor::DeserializationError; pub use network_id::{CustomNetworkId, NetworkId}; pub use r#type::AddressType; @@ -114,6 +116,45 @@ impl Address { Ok((network_id, address)) } + + /// Identifier for the internal type of address + fn address_scheme_id(&self) -> u8 { + match self { + Address::AccountId(_) => 0u8, + } + } +} + +impl Serializable for Address { + fn write_into(&self, target: &mut W) { + target.write_u8(self.address_scheme_id()); + match self { + Address::AccountId(addr) => { + let serialized: [u8; AccountIdAddress::SERIALIZED_SIZE] = (*addr).into(); + serialized.write_into(target); + }, + } + } +} + +impl Deserializable for Address { + fn read_from( + source: &mut R, + ) -> Result { + let address_scheme_id: u8 = source.read_u8()?; + match address_scheme_id { + // AccountIdAddress + 0u8 => { + let bytes: [u8; AccountIdAddress::SERIALIZED_SIZE] = source.read_array()?; + let account_id_address = AccountIdAddress::try_from(bytes) + .map_err(|err| DeserializationError::InvalidValue(format!("{}", err)))?; + Ok(Address::AccountId(account_id_address)) + }, + val => { + Err(DeserializationError::InvalidValue(format!("Invalid address scheme ID {val}"))) + }, + } + } } // ACCOUNT ID ADDRESS @@ -468,4 +509,30 @@ mod tests { AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength { .. }) ); } + + /// Tests that an Address can be serialized and deserialized + #[test] + fn address_serialization() { + let rng = &mut rand::rng(); + + for account_type in [ + AccountType::FungibleFaucet, + AccountType::NonFungibleFaucet, + AccountType::RegularAccountImmutableCode, + AccountType::RegularAccountUpdatableCode, + ] + .into_iter() + { + let account_id = AccountIdBuilder::new().account_type(account_type).build_with_rng(rng); + for account_id_address in [ + AccountIdAddress::new(account_id, AddressInterface::BasicWallet), + AccountIdAddress::new(account_id, AddressInterface::Unspecified), + ] { + let address = Address::from(account_id_address); + let serialized = address.to_bytes(); + let deserialized = Address::read_from_bytes(&serialized).unwrap(); + assert_eq!(address, deserialized); + } + } + } } From ad28525b5acc9d6b302267dc1e570ee74b8b3a2a Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Fri, 26 Sep 2025 18:43:29 +0900 Subject: [PATCH 057/133] feat: Make AssetVault and PartialVault APIs more type safe (#1916) * feat: Make AssetVault and PartialVault APIs more type safe * fix: add changelog * fix: make clippy happy * fix: apply suggestions * fix: tests * Update CHANGELOG.md --------- Co-authored-by: Philipp Gackstatter --- CHANGELOG.md | 1 + crates/miden-objects/src/asset/vault/mod.rs | 5 ----- .../miden-objects/src/asset/vault/partial.rs | 20 +++---------------- .../miden-testing/src/tx_context/builder.rs | 2 +- 4 files changed, 5 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09debd5453..908c7fa970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ - [BREAKING] Move and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). - Merge `bench-prover` into `bench-tx` crate ([#1894](https://github.com/0xMiden/miden-base/pull/1894)). - Replace `eqw` usages with `exec.word::test_eq` and `exec.word::eq`, remove `is_key_greater` and `is_key_less` from `link_map` module ([#1897](https://github.com/0xMiden/miden-base/pull/1897)). +- [BREAKING] Make AssetVault and PartialVault APIs more type safe ([#1916](https://github.com/0xMiden/miden-base/pull/1916)). - [BREAKING] Remove `MockChain::add_pending_note` to simplify mock chain internals ([#1903](https://github.com/0xMiden/miden-base/pull/1903)). - [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). - [BREAKING] Remove account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). diff --git a/crates/miden-objects/src/asset/vault/mod.rs b/crates/miden-objects/src/asset/vault/mod.rs index 1d08f8c51d..b5ba9798b2 100644 --- a/crates/miden-objects/src/asset/vault/mod.rs +++ b/crates/miden-objects/src/asset/vault/mod.rs @@ -110,11 +110,6 @@ impl AssetVault { AssetWitness::new_unchecked(smt_proof) } - /// Returns a reference to the Sparse Merkle Tree underling this asset vault. - pub fn asset_tree(&self) -> &Smt { - &self.asset_tree - } - /// Returns a bool indicating whether the vault is empty. pub fn is_empty(&self) -> bool { self.asset_tree.is_empty() diff --git a/crates/miden-objects/src/asset/vault/partial.rs b/crates/miden-objects/src/asset/vault/partial.rs index c1dfa1ac28..5a0eaa5945 100644 --- a/crates/miden-objects/src/asset/vault/partial.rs +++ b/crates/miden-objects/src/asset/vault/partial.rs @@ -100,18 +100,15 @@ impl PartialVault { // MUTATORS // -------------------------------------------------------------------------------------------- - /// Adds an [`SmtProof`] to this [`PartialVault`]. + /// Adds an [`AssetWitness`] to this [`PartialVault`]. /// /// # Errors /// /// Returns an error if: - /// - the provided proof does not prove inclusion of valid [`Asset`]s. - /// - the vault key of the proven asset does not match the vault key derived from the asset. /// - the new root after the insertion of the leaf and the path does not match the existing root /// (except when the first leaf is added). - pub fn add(&mut self, proof: SmtProof) -> Result<(), PartialAssetVaultError> { - Self::validate_entries(proof.leaf().entries())?; - + pub fn add(&mut self, witness: AssetWitness) -> Result<(), PartialAssetVaultError> { + let proof = SmtProof::from(witness); self.partial_smt .add_proof(proof) .map_err(PartialAssetVaultError::FailedToAddProof) @@ -190,11 +187,6 @@ mod tests { assert_eq!(entry, invalid_asset); }); - let err = PartialVault::default().add(proof).unwrap_err(); - assert_matches!(err, PartialAssetVaultError::InvalidAssetInSmt { entry, .. } => { - assert_eq!(entry, invalid_asset); - }); - Ok(()) } @@ -212,12 +204,6 @@ mod tests { assert_eq!(expected, asset.vault_key()); }); - let err = PartialVault::default().add(proof).unwrap_err(); - assert_matches!(err, PartialAssetVaultError::VaultKeyMismatch { expected, actual } => { - assert_eq!(actual, invalid_vault_key); - assert_eq!(expected, asset.vault_key()); - }); - Ok(()) } } diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index c920ce2984..9d6fa4d2c1 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -363,7 +363,7 @@ fn minimal_partial_account(account: &Account) -> anyhow::Result // root as the full vault, but will not add any relevant merkle paths to the // merkle store, which will test lazy loading of assets. let mut partial_vault = PartialVault::default(); - partial_vault.add(account.vault().open(Word::empty()).into())?; + partial_vault.add(account.vault().open(Word::empty()))?; // Construct a partial storage that tracks the empty word in all storage maps, but none // of the other keys, following the same rationale as the partial vault above. From 8fb108c77a5e06befaf2d267fde7e0fadf255703 Mon Sep 17 00:00:00 2001 From: Marti Date: Fri, 26 Sep 2025 14:14:48 +0200 Subject: [PATCH 058/133] feat: introduce a `Signature` wrapper around `rpo_falcon512::Signature` (#1924) * feat: introduce a `Signature` wrapper * chore: refactor the authenticator to use the new `Signature` wrapper` * chore: update changelog * fix: doctest imports and definitions * Update crates/miden-tx/src/auth/signatures/mod.rs * chore: move Signature to miden-objects * chore: (de)serialize Signature * chore: add section separators * Apply suggestion from @mmagician * Apply suggestion from @mmagician * Apply suggestion from @mmagician --------- Co-authored-by: Bobbin Threadbare --- CHANGELOG.md | 2 + crates/miden-objects/src/account/auth.rs | 133 ++++++++++++++++++- crates/miden-objects/src/account/mod.rs | 2 +- crates/miden-tx/src/auth/mod.rs | 2 - crates/miden-tx/src/auth/signatures/mod.rs | 67 ---------- crates/miden-tx/src/auth/tx_authenticator.rs | 14 +- 6 files changed, 140 insertions(+), 80 deletions(-) delete mode 100644 crates/miden-tx/src/auth/signatures/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 908c7fa970..e35f305cee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - [BREAKING] Enabled lazy loading of assets and storage map items for foreign accounts during transaction execution ([#1888](https://github.com/0xMiden/miden-base/pull/1888)). - Added `get_item_init` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). - Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) +- [BREAKING] Removed `get_falcon_signature` from `miden-tx` crate ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). +- Created a `Signature` wrapper to simplify the preparation of "native" signatures for use in the VM ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933]https://github.com/0xMiden/miden-base/pull/1933). - Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937)) - Add `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935]https://github.com/0xMiden/miden-base/pull/1935). diff --git a/crates/miden-objects/src/account/auth.rs b/crates/miden-objects/src/account/auth.rs index 694b165c2e..93432e8bde 100644 --- a/crates/miden-objects/src/account/auth.rs +++ b/crates/miden-objects/src/account/auth.rs @@ -1,8 +1,6 @@ -// AUTH SECRET KEY -// ================================================================================================ - -use miden_crypto::dsa::rpo_falcon512::{self, SecretKey}; +use alloc::vec::Vec; +use crate::crypto::dsa::rpo_falcon512::{self, Polynomial, SecretKey}; use crate::utils::serde::{ ByteReader, ByteWriter, @@ -10,6 +8,10 @@ use crate::utils::serde::{ DeserializationError, Serializable, }; +use crate::{Felt, Hasher}; + +// AUTH SECRET KEY +// ================================================================================================ /// Types of secret keys used for signing messages #[derive(Clone, Debug)] @@ -51,3 +53,126 @@ impl Deserializable for AuthSecretKey { } } } + +// SIGNATURE +// ================================================================================================ + +/// Represents a signature object ready for native verification. +/// +/// In order to use this signature within the Miden VM, a preparation step may be necessary to +/// convert the native signature into a vector of field elements that can be loaded into the advice +/// provider. To prepare the signature, use the provided `to_prepared_signature` method: +/// ```rust,no_run +/// use miden_objects::account::auth::Signature; +/// use miden_objects::crypto::dsa::rpo_falcon512::SecretKey; +/// use miden_objects::{Felt, Word}; +/// +/// let secret_key = SecretKey::new(); +/// let message = Word::default(); +/// let signature: Signature = secret_key.sign(message).into(); +/// let prepared_signature: Vec = signature.to_prepared_signature(); +/// ``` +#[derive(Clone, Debug)] +#[repr(u8)] +pub enum Signature { + RpoFalcon512(rpo_falcon512::Signature) = 0, +} + +impl Signature { + pub fn to_prepared_signature(&self) -> Vec { + match self { + Signature::RpoFalcon512(signature) => prepare_rpo_falcon512_signature(signature), + } + } +} + +impl From for Signature { + fn from(signature: rpo_falcon512::Signature) -> Self { + Signature::RpoFalcon512(signature) + } +} + +impl Signature { + /// Identifier for the type of signature scheme + pub fn signature_scheme_id(&self) -> u8 { + match self { + Signature::RpoFalcon512(_) => 0u8, + } + } +} + +impl Serializable for Signature { + fn write_into(&self, target: &mut W) { + target.write_u8(self.signature_scheme_id()); + match self { + Signature::RpoFalcon512(signature) => { + signature.write_into(target); + }, + } + } +} + +impl Deserializable for Signature { + fn read_from(source: &mut R) -> Result { + let signature_scheme_id: u8 = source.read_u8()?; + match signature_scheme_id { + // RpoFalcon512 + 0u8 => { + let signature = rpo_falcon512::Signature::read_from(source)?; + Ok(Signature::RpoFalcon512(signature)) + }, + val => Err(DeserializationError::InvalidValue(format!( + "Invalid signature scheme ID {val}" + ))), + } + } +} + +// SIGNATURE PREPARATION +// ================================================================================================ + +/// Converts a Falcon [rpo_falcon512::Signature] to a vector of values to be pushed onto the +/// advice stack. The values are the ones required for a Falcon signature verification inside the VM +/// and they are: +/// +/// 1. The challenge point at which we evaluate the polynomials in the subsequent three bullet +/// points, i.e. `h`, `s2` and `pi`, to check the product relationship. +/// 2. The expanded public key represented as the coefficients of a polynomial `h` of degree < 512. +/// 3. The signature represented as the coefficients of a polynomial `s2` of degree < 512. +/// 4. The product of the above two polynomials `pi` in the ring of polynomials with coefficients in +/// the Miden field. +/// 5. The nonce represented as 8 field elements. +fn prepare_rpo_falcon512_signature(sig: &rpo_falcon512::Signature) -> Vec { + // The signature is composed of a nonce and a polynomial s2 + // The nonce is represented as 8 field elements. + let nonce = sig.nonce(); + // We convert the signature to a polynomial + let s2 = sig.sig_poly(); + // We also need in the VM the expanded key corresponding to the public key that was provided + // via the operand stack + let h = sig.pk_poly(); + // Lastly, for the probabilistic product routine that is part of the verification procedure, + // we need to compute the product of the expanded key and the signature polynomial in + // the ring of polynomials with coefficients in the Miden field. + let pi = Polynomial::mul_modulo_p(h, s2); + + // We now push the expanded key, the signature polynomial, and the product of the + // expanded key and the signature polynomial to the advice stack. We also push + // the challenge point at which the previous polynomials will be evaluated. + // Finally, we push the nonce needed for the hash-to-point algorithm. + + let mut polynomials: Vec = + h.coefficients.iter().map(|a| Felt::from(a.value() as u32)).collect(); + polynomials.extend(s2.coefficients.iter().map(|a| Felt::from(a.value() as u32))); + polynomials.extend(pi.iter().map(|a| Felt::new(*a))); + + let digest_polynomials = Hasher::hash_elements(&polynomials); + let challenge = (digest_polynomials[0], digest_polynomials[1]); + + let mut result: Vec = vec![challenge.0, challenge.1]; + result.extend_from_slice(&polynomials); + result.extend_from_slice(&nonce.to_elements()); + + result.reverse(); + result +} diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index dffd8e1491..4d51bd2ace 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -23,7 +23,7 @@ pub use account_id::{ pub mod auth; -pub use auth::AuthSecretKey; +pub use auth::{AuthSecretKey, Signature}; mod builder; pub use builder::AccountBuilder; diff --git a/crates/miden-tx/src/auth/mod.rs b/crates/miden-tx/src/auth/mod.rs index 160d2962c0..7dd2be0a4c 100644 --- a/crates/miden-tx/src/auth/mod.rs +++ b/crates/miden-tx/src/auth/mod.rs @@ -5,5 +5,3 @@ pub use tx_authenticator::{ TransactionAuthenticator, UnreachableAuth, }; - -pub mod signatures; diff --git a/crates/miden-tx/src/auth/signatures/mod.rs b/crates/miden-tx/src/auth/signatures/mod.rs deleted file mode 100644 index d934d4deb4..0000000000 --- a/crates/miden-tx/src/auth/signatures/mod.rs +++ /dev/null @@ -1,67 +0,0 @@ -use alloc::vec::Vec; - -use miden_objects::Hasher; -use miden_objects::crypto::dsa::rpo_falcon512::{self, Polynomial}; -use miden_processor::{Felt, Word}; -use rand::Rng; - -use crate::AuthenticationError; - -/// Retrieves a Falcon signature over a message. -/// -/// Gets as input a [Word] containing a secret key, and a [Word] representing a message and -/// outputs a vector of values to be pushed onto the advice stack. The values are the ones required -/// for a Falcon signature verification inside the VM and they are: -/// -/// 1. The challenge point at which we evaluate the polynomials in the subsequent three bullet -/// points, i.e. `h`, `s2` and `pi`, to check the product relationship. -/// 2. The expanded public key represented as the coefficients of a polynomial `h` of degree < 512. -/// 3. The signature represented as the coefficients of a polynomial `s2` of degree < 512. -/// 4. The product of the above two polynomials `pi` in the ring of polynomials with coefficients in -/// the Miden field. -/// 5. The nonce represented as 8 field elements. -/// -/// # Errors -/// Will return an error if either: -/// - The secret key is malformed due to either incorrect length or failed decoding. -/// - The signature generation failed. -pub fn get_falcon_signature( - key: &rpo_falcon512::SecretKey, - message: Word, - rng: &mut R, -) -> Result, AuthenticationError> { - // Generate the signature - let sig = key.sign_with_rng(message, rng); - // The signature is composed of a nonce and a polynomial s2 - // The nonce is represented as 8 field elements. - let nonce = sig.nonce(); - // We convert the signature to a polynomial - let s2 = sig.sig_poly(); - // We also need in the VM the expanded key corresponding to the public key that was provided - // via the operand stack - let h = key.compute_pub_key_poly().0; - // Lastly, for the probabilistic product routine that is part of the verification procedure, - // we need to compute the product of the expanded key and the signature polynomial in - // the ring of polynomials with coefficients in the Miden field. - let pi = Polynomial::mul_modulo_p(&h, s2); - - // We now push the expanded key, the signature polynomial, and the product of the - // expanded key and the signature polynomial to the advice stack. We also push - // the challenge point at which the previous polynomials will be evaluated. - // Finally, we push the nonce needed for the hash-to-point algorithm. - - let mut polynomials: Vec = - h.coefficients.iter().map(|a| Felt::from(a.value() as u32)).collect(); - polynomials.extend(s2.coefficients.iter().map(|a| Felt::from(a.value() as u32))); - polynomials.extend(pi.iter().map(|a| Felt::new(*a))); - - let digest_polynomials = Hasher::hash_elements(&polynomials); - let challenge = (digest_polynomials[0], digest_polynomials[1]); - - let mut result: Vec = vec![challenge.0, challenge.1]; - result.extend_from_slice(&polynomials); - result.extend_from_slice(&nonce.to_elements()); - - result.reverse(); - Ok(result) -} diff --git a/crates/miden-tx/src/auth/tx_authenticator.rs b/crates/miden-tx/src/auth/tx_authenticator.rs index 609c1fe09a..507613cc38 100644 --- a/crates/miden-tx/src/auth/tx_authenticator.rs +++ b/crates/miden-tx/src/auth/tx_authenticator.rs @@ -4,7 +4,7 @@ use alloc::string::ToString; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_objects::account::AuthSecretKey; +use miden_objects::account::{AuthSecretKey, Signature}; use miden_objects::crypto::SequentialCommit; use miden_objects::transaction::TransactionSummary; use miden_objects::{Felt, Hasher, Word}; @@ -12,7 +12,6 @@ use miden_processor::FutureMaybeSend; use rand::Rng; use tokio::sync::RwLock; -use super::signatures::get_falcon_signature; use crate::errors::AuthenticationError; use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; @@ -226,10 +225,13 @@ impl TransactionAuthenticator for BasicAuthenticator { async move { let mut rng = self.rng.write().await; match self.keys.get(&pub_key) { - Some(key) => match key { - AuthSecretKey::RpoFalcon512(falcon_key) => { - get_falcon_signature(falcon_key, message, &mut *rng) - }, + Some(key) => { + let signature: Signature = match key { + AuthSecretKey::RpoFalcon512(falcon_key) => { + falcon_key.sign_with_rng(message, &mut *rng).into() + }, + }; + Ok(signature.to_prepared_signature()) }, None => Err(AuthenticationError::UnknownPublicKey(format!( "public key {pub_key} is not contained in the authenticator's keys", From 62feca75399d54f0d840f3aa3d5d345fe2f87e85 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 26 Sep 2025 16:57:31 +0200 Subject: [PATCH 059/133] feat: implement `SlotName` (#1932) * feat: Implement `SlotName` * chore: add changelog * feat: Simplify validation * fix: doc test * fix: typo * chore: address review comments (+ recommended layout) * chore: use `is_ascii_alphanumeric` --- CHANGELOG.md | 1 + crates/miden-objects/src/account/mod.rs | 1 + .../miden-objects/src/account/storage/mod.rs | 2 +- .../src/account/storage/slot/mod.rs | 3 + .../src/account/storage/slot/slot_name.rs | 314 ++++++++++++++++++ crates/miden-objects/src/errors.rs | 19 ++ crates/miden-objects/src/lib.rs | 1 + 7 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 crates/miden-objects/src/account/storage/slot/slot_name.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e35f305cee..fdaa532aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [BREAKING] Enabled lazy loading of assets and storage map items for foreign accounts during transaction execution ([#1888](https://github.com/0xMiden/miden-base/pull/1888)). - Added `get_item_init` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). - Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) +- Implement `SlotName` for named storage slots ([#1932](https://github.com/0xMiden/miden-base/issues/1932)) - [BREAKING] Removed `get_falcon_signature` from `miden-tx` crate ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Created a `Signature` wrapper to simplify the preparation of "native" signatures for use in the VM ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933]https://github.com/0xMiden/miden-base/pull/1933). diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index 4d51bd2ace..5dc225c769 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -67,6 +67,7 @@ pub use storage::{ AccountStorageHeader, PartialStorage, PartialStorageMap, + SlotName, StorageMap, StorageMapWitness, StorageSlot, diff --git a/crates/miden-objects/src/account/storage/mod.rs b/crates/miden-objects/src/account/storage/mod.rs index 5561a65be1..edf703d681 100644 --- a/crates/miden-objects/src/account/storage/mod.rs +++ b/crates/miden-objects/src/account/storage/mod.rs @@ -16,7 +16,7 @@ use super::{ use crate::account::{AccountComponent, AccountType}; mod slot; -pub use slot::{StorageSlot, StorageSlotType}; +pub use slot::{SlotName, StorageSlot, StorageSlotType}; mod map; pub use map::{PartialStorageMap, StorageMap, StorageMapWitness}; diff --git a/crates/miden-objects/src/account/storage/slot/mod.rs b/crates/miden-objects/src/account/storage/slot/mod.rs index c1aef7453a..9f6389c38b 100644 --- a/crates/miden-objects/src/account/storage/slot/mod.rs +++ b/crates/miden-objects/src/account/storage/slot/mod.rs @@ -5,6 +5,9 @@ use miden_processor::DeserializationError; use super::map::EMPTY_STORAGE_MAP_ROOT; use super::{StorageMap, Word}; +mod slot_name; +pub use slot_name::SlotName; + mod r#type; pub use r#type::StorageSlotType; diff --git a/crates/miden-objects/src/account/storage/slot/slot_name.rs b/crates/miden-objects/src/account/storage/slot/slot_name.rs new file mode 100644 index 0000000000..0efbb96a97 --- /dev/null +++ b/crates/miden-objects/src/account/storage/slot/slot_name.rs @@ -0,0 +1,314 @@ +use alloc::borrow::Cow; +use alloc::string::String; + +use crate::errors::SlotNameError; + +/// The name of an account storage slot. +/// +/// A typical slot name looks like this: +/// +/// ```text +/// miden::basic_fungible_faucet::metadata +/// ``` +/// +/// The double-colon (`::`) serves as a separator and the strings in between the separators are +/// called components. +/// +/// It is generally recommended that slot names have at least three components and follow this +/// structure: +/// +/// ```text +/// organization::component::slot_name +/// ``` +/// +/// ## Requirements +/// +/// For a string to be a valid slot name it needs to satisfy the following criteria: +/// - It needs to have at least 2 components. +/// - Each component must consist of at least one character. +/// - Each component must only consist of the characters `a` to `z`, `A` to `Z`, `0` to `9` or `_` +/// (underscore). +/// - Each component must not start with an underscore. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SlotName { + name: Cow<'static, str>, +} + +impl SlotName { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + // The minimum number of components that a slot name must contain. + pub(crate) const MIN_NUM_COMPONENTS: usize = 2; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Constructs a new [`SlotName`] from a static string. + /// + /// This function is `const` and can be used to define slot names as constants, e.g.: + /// + /// ```rust + /// # use miden_objects::account::SlotName; + /// const SLOT_NAME: SlotName = SlotName::from_static_str("miden::basic_fungible_faucet::metadata"); + /// ``` + /// + /// This is convenient because using a string that is not a valid slot name fails to compile. + /// + /// # Panics + /// + /// Panics if: + /// - the slot name is invalid (see the type-level docs for the requirements). + pub const fn from_static_str(name: &'static str) -> Self { + match Self::validate(name) { + Ok(()) => Self { name: Cow::Borrowed(name) }, + // We cannot format the error in a const context. + Err(_) => panic!("invalid slot name"), + } + } + + /// Constructs a new [`SlotName`] from a string. + /// + /// # Errors + /// + /// Returns an error if: + /// - the slot name is invalid (see the type-level docs for the requirements). + pub fn new(name: impl Into) -> Result { + let name = name.into(); + Self::validate(&name)?; + Ok(Self { name: Cow::Owned(name) }) + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the slot name as a string slice. + pub fn as_str(&self) -> &str { + &self.name + } + + // HELPERS + // -------------------------------------------------------------------------------------------- + + /// Validates a slot name. + /// + /// This checks that components are separated by double colons, that each component contains + /// only valid characters and that the name is not empty or starts or ends with a colon. + /// + /// We must check the validity of a slot name against the raw bytes of the UTF-8 string because + /// typical character APIs are not available in a const version. We can do this because any byte + /// in a UTF-8 string that is an ASCII character never represents anything other than such a + /// character, even though UTF-8 can contain multibyte sequences: + /// + /// > UTF-8, the object of this memo, has a one-octet encoding unit. It uses all bits of an + /// > octet, but has the quality of preserving the full US-ASCII range: US-ASCII characters + /// > are encoded in one octet having the normal US-ASCII value, and any octet with such a value + /// > can only stand for a US-ASCII character, and nothing else. + /// > https://www.rfc-editor.org/rfc/rfc3629 + const fn validate(name: &str) -> Result<(), SlotNameError> { + let bytes = name.as_bytes(); + let mut idx = 0; + let mut num_components = 0; + + if bytes.is_empty() { + return Err(SlotNameError::TooShort); + } + + // Slot names must not start with a colon or underscore. + // SAFETY: We just checked that we're not dealing with an empty slice. + if bytes[0] == b':' { + return Err(SlotNameError::UnexpectedColon); + } else if bytes[0] == b'_' { + return Err(SlotNameError::UnexpectedUnderscore); + } + + while idx < bytes.len() { + let byte = bytes[idx]; + + let is_colon = byte == b':'; + + if is_colon { + // A colon must always be followed by another colon. In other words, we + // expect a double colon. + if (idx + 1) < bytes.len() { + if bytes[idx + 1] != b':' { + return Err(SlotNameError::UnexpectedColon); + } + } else { + return Err(SlotNameError::UnexpectedColon); + } + + // A component cannot end with a colon, so this allows us to validate the start of a + // component: It must not start with a colon or an underscore. + if (idx + 2) < bytes.len() { + if bytes[idx + 2] == b':' { + return Err(SlotNameError::UnexpectedColon); + } else if bytes[idx + 2] == b'_' { + return Err(SlotNameError::UnexpectedUnderscore); + } + } else { + return Err(SlotNameError::UnexpectedColon); + } + + // Advance past the double colon. + idx += 2; + + // A double colon completes a slot name component. + num_components += 1; + } else if Self::is_valid_char(byte) { + idx += 1; + } else { + return Err(SlotNameError::InvalidCharacter); + } + } + + // The last component is not counted as part of the loop because no double colon follows. + num_components += 1; + + if num_components < Self::MIN_NUM_COMPONENTS { + return Err(SlotNameError::TooShort); + } + + Ok(()) + } + + /// Returns `true` if the given byte is a valid slot name character, `false` otherwise. + const fn is_valid_char(byte: u8) -> bool { + byte.is_ascii_alphanumeric() || byte == b'_' + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + + use super::*; + + // A string containing all allowed characters of a slot name. + const FULL_ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"; + + // Const function tests + // -------------------------------------------------------------------------------------------- + + const _NAME0: SlotName = SlotName::from_static_str("name::component"); + const _NAME1: SlotName = SlotName::from_static_str("one::two::three::four::five"); + const _NAME2: SlotName = SlotName::from_static_str("one::two_three::four"); + + #[test] + #[should_panic(expected = "invalid slot name")] + fn slot_name_panics_on_invalid_character() { + SlotName::from_static_str("miden!::component"); + } + + #[test] + #[should_panic(expected = "invalid slot name")] + fn slot_name_panics_on_invalid_character2() { + SlotName::from_static_str("miden_ö::component"); + } + + #[test] + #[should_panic(expected = "invalid slot name")] + fn slot_name_panics_when_too_short() { + SlotName::from_static_str("one"); + } + + #[test] + #[should_panic(expected = "invalid slot name")] + fn slot_name_panics_on_component_starting_with_underscores() { + SlotName::from_static_str("one::_two"); + } + + // Invalid colon or underscore tests + // -------------------------------------------------------------------------------------------- + + #[test] + fn slot_name_fails_on_invalid_colon_placement() { + // Single colon. + assert_matches!(SlotName::new(":").unwrap_err(), SlotNameError::UnexpectedColon); + assert_matches!(SlotName::new("0::1:").unwrap_err(), SlotNameError::UnexpectedColon); + assert_matches!(SlotName::new(":0::1").unwrap_err(), SlotNameError::UnexpectedColon); + assert_matches!(SlotName::new("0::1:2").unwrap_err(), SlotNameError::UnexpectedColon); + + // Double colon (placed invalidly). + assert_matches!(SlotName::new("::").unwrap_err(), SlotNameError::UnexpectedColon); + assert_matches!(SlotName::new("1::2::").unwrap_err(), SlotNameError::UnexpectedColon); + assert_matches!(SlotName::new("::1::2").unwrap_err(), SlotNameError::UnexpectedColon); + + // Triple colon. + assert_matches!(SlotName::new(":::").unwrap_err(), SlotNameError::UnexpectedColon); + assert_matches!(SlotName::new("1::2:::").unwrap_err(), SlotNameError::UnexpectedColon); + assert_matches!(SlotName::new(":::1::2").unwrap_err(), SlotNameError::UnexpectedColon); + assert_matches!(SlotName::new("1::2:::3").unwrap_err(), SlotNameError::UnexpectedColon); + } + + #[test] + fn slot_name_fails_on_invalid_underscore_placement() { + assert_matches!( + SlotName::new("_one::two").unwrap_err(), + SlotNameError::UnexpectedUnderscore + ); + assert_matches!( + SlotName::new("one::_two").unwrap_err(), + SlotNameError::UnexpectedUnderscore + ); + } + + // Num components tests + // -------------------------------------------------------------------------------------------- + + #[test] + fn slot_name_fails_on_empty_string() { + assert_matches!(SlotName::new("").unwrap_err(), SlotNameError::TooShort); + } + + #[test] + fn slot_name_fails_on_single_component() { + assert_matches!(SlotName::new("single_component").unwrap_err(), SlotNameError::TooShort); + } + + // Alphabet validation tests + // -------------------------------------------------------------------------------------------- + + #[test] + fn slot_name_allows_ascii_alphanumeric_and_underscore() -> anyhow::Result<()> { + let name = format!("{FULL_ALPHABET}::second"); + let slot_name = SlotName::new(&name)?; + assert_eq!(slot_name.as_str(), name); + + Ok(()) + } + + #[test] + fn slot_name_fails_on_invalid_character() { + assert_matches!( + SlotName::new("na#me::second").unwrap_err(), + SlotNameError::InvalidCharacter + ); + assert_matches!( + SlotName::new("first_entry::secönd").unwrap_err(), + SlotNameError::InvalidCharacter + ); + assert_matches!( + SlotName::new("first::sec::th!rd").unwrap_err(), + SlotNameError::InvalidCharacter + ); + } + + // Valid slot name tests + // -------------------------------------------------------------------------------------------- + + #[test] + fn slot_name_with_min_components_is_valid() -> anyhow::Result<()> { + SlotName::new("miden::component")?; + Ok(()) + } + + #[test] + fn slot_name_with_many_components_is_valid() -> anyhow::Result<()> { + SlotName::new("miden::faucet0::fungible_1::b4sic::metadata")?; + Ok(()) + } +} diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index 4a7dcb4d95..6be24aff22 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -22,6 +22,7 @@ use crate::account::{ AccountIdPrefix, AccountStorage, AccountType, + SlotName, StorageValueName, StorageValueNameError, TemplateTypeError, @@ -215,6 +216,24 @@ pub enum AccountIdError { AccountIdSuffixLeastSignificantByteMustBeZero, } +// SLOT NAME ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum SlotNameError { + #[error("slot names must only contain characters a..z, A..Z, 0..9 or underscore")] + InvalidCharacter, + #[error("slot names must be separated by double colons")] + UnexpectedColon, + #[error("slot name components must not start with an underscore")] + UnexpectedUnderscore, + #[error( + "slot names must contain at least {} components separated by double colons", + SlotName::MIN_NUM_COMPONENTS + )] + TooShort, +} + // ACCOUNT TREE ERROR // ================================================================================================ diff --git a/crates/miden-objects/src/lib.rs b/crates/miden-objects/src/lib.rs index 042b4ddba0..b455fa6159 100644 --- a/crates/miden-objects/src/lib.rs +++ b/crates/miden-objects/src/lib.rs @@ -42,6 +42,7 @@ pub use errors::{ ProposedBlockError, ProvenBatchError, ProvenTransactionError, + SlotNameError, StorageMapError, TokenSymbolError, TransactionInputError, From b6f679b2bd21f4f977f950242a0cbbfed1c6ab59 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 26 Sep 2025 23:23:10 +0200 Subject: [PATCH 060/133] feat: add `AssetVault::{num_assets, num_leaves, inner_nodes}` (#1939) --- CHANGELOG.md | 3 ++- crates/miden-objects/src/asset/vault/mod.rs | 25 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdaa532aac..512ec48dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,8 @@ - Created a `Signature` wrapper to simplify the preparation of "native" signatures for use in the VM ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933]https://github.com/0xMiden/miden-base/pull/1933). - Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937)) -- Add `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935]https://github.com/0xMiden/miden-base/pull/1935). +- Added `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935]https://github.com/0xMiden/miden-base/pull/1935). +- Added `AssetVault::{num_assets, num_leaves, inner_nodes}` ([#1939]https://github.com/0xMiden/miden-base/pull/1939). ### Changes diff --git a/crates/miden-objects/src/asset/vault/mod.rs b/crates/miden-objects/src/asset/vault/mod.rs index b5ba9798b2..1718029838 100644 --- a/crates/miden-objects/src/asset/vault/mod.rs +++ b/crates/miden-objects/src/asset/vault/mod.rs @@ -1,5 +1,6 @@ use alloc::string::ToString; +use miden_crypto::merkle::InnerNodeInfo; use miden_processor::SMT_DEPTH; use super::{ @@ -98,7 +99,13 @@ impl AssetVault { /// Returns an iterator over the assets stored in the vault. pub fn assets(&self) -> impl Iterator + '_ { - self.asset_tree.entries().map(|x| Asset::new_unchecked(x.1)) + // SAFETY: The asset tree tracks only valid assets. + self.asset_tree.entries().map(|(_key, value)| Asset::new_unchecked(*value)) + } + + /// Returns an iterator over the inner nodes of the underlying [`Smt`]. + pub fn inner_nodes(&self) -> impl Iterator + '_ { + self.asset_tree.inner_nodes() } /// Returns an opening of the leaf associated with `vault_key`. @@ -115,6 +122,22 @@ impl AssetVault { self.asset_tree.is_empty() } + /// Returns the number of non-empty leaves in the underlying [`Smt`]. + /// + /// Note that this may return a different value from [Self::num_assets()] as a single leaf may + /// contain more than one asset. + pub fn num_leaves(&self) -> usize { + self.asset_tree.num_leaves() + } + + /// Returns the number of assets in this vault. + /// + /// Note that this may return a different value from [Self::num_leaves()] as a single leaf may + /// contain more than one asset. + pub fn num_assets(&self) -> usize { + self.asset_tree.num_entries() + } + // TODO: Replace with https://github.com/0xMiden/crypto/issues/515 once implemented. /// Returns the leaf index of a vault key. pub fn vault_key_to_leaf_index(vault_key: Word) -> Felt { From 23a7be3b62f1d65a4fc89e207f644470f4780d70 Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Mon, 29 Sep 2025 14:55:50 +0530 Subject: [PATCH 061/133] feat: Rename MockChainBuilder::add_note to add_output_note (#1946) * feat: Rename MockChainBuilder::add_note to add_output_note * fix: add changelog --- CHANGELOG.md | 1 + .../src/kernel_tests/block/proposed_block_success.rs | 4 ++-- .../src/kernel_tests/tx/test_account_interface.rs | 2 +- crates/miden-testing/src/kernel_tests/tx/test_fee.rs | 2 +- .../miden-testing/src/kernel_tests/tx/test_note.rs | 2 +- crates/miden-testing/src/kernel_tests/tx/test_tx.rs | 2 +- crates/miden-testing/src/mock_chain/chain_builder.rs | 12 ++++++------ crates/miden-testing/src/tx_context/builder.rs | 2 +- crates/miden-testing/tests/auth/rpo_falcon_acl.rs | 2 +- crates/miden-testing/tests/scripts/faucet.rs | 2 +- crates/miden-testing/tests/scripts/swap.rs | 2 +- 11 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 512ec48dc3..d6d27ab877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ - [BREAKING] Remove `MockChain::add_pending_note` to simplify mock chain internals ([#1903](https://github.com/0xMiden/miden-base/pull/1903)). - [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). - [BREAKING] Remove account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). +- [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). ## 0.11.4 (2025-09-17) diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs index 802de2e1b2..6f7b2f70ac 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs @@ -275,8 +275,8 @@ fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow: NoteBuilder::new(ACCOUNT_ID_SENDER.try_into().unwrap(), &mut rand::rng()).build()?; let noop_note1 = NoteBuilder::new(ACCOUNT_ID_SENDER.try_into().unwrap(), &mut rand::rng()).build()?; - builder.add_note(OutputNote::Full(noop_note0.clone())); - builder.add_note(OutputNote::Full(noop_note1.clone())); + builder.add_output_note(OutputNote::Full(noop_note0.clone())); + builder.add_output_note(OutputNote::Full(noop_note1.clone())); let mut chain = builder.build()?; let noop_tx = generate_conditional_tx(&mut chain, account0.id(), noop_note0, false); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index 913aff0fe9..eefa914729 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -327,7 +327,7 @@ async fn check_note_consumability_epilogue_failure_with_new_combination() -> any let fail_epilogue_note = NoteBuilder::new(account.id(), &mut rand::rng()) .add_assets([Asset::from(note_asset)]) .build()?; - builder.add_note(OutputNote::Full(fail_epilogue_note.clone())); + builder.add_output_note(OutputNote::Full(fail_epilogue_note.clone())); let mock_chain = builder.build()?; let notes = vec![ diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fee.rs b/crates/miden-testing/src/kernel_tests/tx/test_fee.rs index 25c66dde45..e8197fe566 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fee.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fee.rs @@ -168,7 +168,7 @@ fn create_output_notes() -> anyhow::Result { // This creates a note that adds the given assets to the account vault. let asset_note = create_public_p2any_note(account.id(), [Asset::from(note_asset0.add(note_asset1)?)]); - builder.add_note(OutputNote::Full(asset_note.clone())); + builder.add_output_note(OutputNote::Full(asset_note.clone())); let output_note0 = create_public_p2any_note(account.id(), [note_asset0.into()]); let output_note1 = create_public_p2any_note(account.id(), [note_asset1.into()]); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 968c34322a..654df49d84 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -481,7 +481,7 @@ pub fn test_timelock() -> anyhow::Result<()> { .dynamically_linked_libraries(TransactionKernel::mock_libraries()) .build()?; - builder.add_note(OutputNote::Full(timelock_note.clone())); + builder.add_output_note(OutputNote::Full(timelock_note.clone())); let mut mock_chain = builder.build()?; mock_chain diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 76560ed76e..406ebeaf3d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -506,7 +506,7 @@ fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { let input_note = create_spawn_note(vec![&output_note])?; let mut builder = MockChain::builder(); - builder.add_note(OutputNote::Full(input_note.clone())); + builder.add_output_note(OutputNote::Full(input_note.clone())); let mock_chain = builder.build()?; let tx_context = mock_chain.build_tx_context(account, &[input_note.id()], &[])?.build()?; diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index a320e4f546..fc5f8be3d3 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -400,7 +400,7 @@ impl MockChainBuilder { // ---------------------------------------------------------------------------------------- /// Adds the provided note to the initial chain state. - pub fn add_note(&mut self, note: impl Into) { + pub fn add_output_note(&mut self, note: impl Into) { self.notes.push(note.into()); } @@ -415,7 +415,7 @@ impl MockChainBuilder { assets: impl IntoIterator, ) -> anyhow::Result { let note = self.create_p2any_note(sender_account_id, note_type, assets)?; - self.add_note(OutputNote::Full(note.clone())); + self.add_output_note(OutputNote::Full(note.clone())); Ok(note) } @@ -438,7 +438,7 @@ impl MockChainBuilder { asset.iter().copied(), note_type, )?; - self.add_note(OutputNote::Full(note.clone())); + self.add_output_note(OutputNote::Full(note.clone())); Ok(note) } @@ -468,7 +468,7 @@ impl MockChainBuilder { &mut self.rng, )?; - self.add_note(OutputNote::Full(note.clone())); + self.add_output_note(OutputNote::Full(note.clone())); Ok(note) } @@ -492,7 +492,7 @@ impl MockChainBuilder { &mut self.rng, )?; - self.add_note(OutputNote::Full(swap_note.clone())); + self.add_output_note(OutputNote::Full(swap_note.clone())); Ok((swap_note, payback_note)) } @@ -515,7 +515,7 @@ impl MockChainBuilder { I: ExactSizeIterator, { let note = create_spawn_note(output_notes)?; - self.add_note(OutputNote::Full(note.clone())); + self.add_output_note(OutputNote::Full(note.clone())); Ok(note) } diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 9d6fa4d2c1..fc437c3143 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -271,7 +271,7 @@ impl TransactionContextBuilder { let mut builder = MockChain::builder(); for i in self.input_notes { - builder.add_note(OutputNote::Full(i)); + builder.add_output_note(OutputNote::Full(i)); } let mut mock_chain = builder.build()?; diff --git a/crates/miden-testing/tests/auth/rpo_falcon_acl.rs b/crates/miden-testing/tests/auth/rpo_falcon_acl.rs index a999228e69..906c83acc3 100644 --- a/crates/miden-testing/tests/auth/rpo_falcon_acl.rs +++ b/crates/miden-testing/tests/auth/rpo_falcon_acl.rs @@ -70,7 +70,7 @@ fn setup_rpo_falcon_acl_test( let note = NoteBuilder::new(account.id(), &mut rand::rng()) .build() .expect("failed to create mock note"); - builder.add_note(OutputNote::Full(note.clone())); + builder.add_output_note(OutputNote::Full(note.clone())); let mock_chain = builder.build()?; Ok((account, mock_chain, note)) diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 3bc3cc217b..c142482448 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -244,7 +244,7 @@ fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result< let note = get_note_with_fungible_asset_and_script(fungible_asset, burn_note_script_code); - builder.add_note(OutputNote::Full(note.clone())); + builder.add_output_note(OutputNote::Full(note.clone())); let mock_chain = builder.build()?; // The Fungible Faucet component is added as the second component after auth, so it's storage diff --git a/crates/miden-testing/tests/scripts/swap.rs b/crates/miden-testing/tests/scripts/swap.rs index b179cda620..a0fdf92d9d 100644 --- a/crates/miden-testing/tests/scripts/swap.rs +++ b/crates/miden-testing/tests/scripts/swap.rs @@ -327,7 +327,7 @@ fn setup_swap_test(payback_note_type: NoteType) -> anyhow::Result .add_swap_note(sender_account.id(), offered_asset, requested_asset, payback_note_type) .unwrap(); - builder.add_note(OutputNote::Full(swap_note.clone())); + builder.add_output_note(OutputNote::Full(swap_note.clone())); let mock_chain = builder.build()?; Ok(SwapTestSetup { From d8bb53936a4d02cd6418ce85fa83f64b3a4b079f Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 30 Sep 2025 16:09:44 +0200 Subject: [PATCH 062/133] feat: migrate to `miden-vm` 0.18.0 and `miden-crypto` 0.17.0 (#1832) Signed-off-by: Bernhard Schuster --- CHANGELOG.md | 7 +- Cargo.lock | 845 ++++++++++++++---- Cargo.toml | 18 +- crates/miden-lib/Cargo.toml | 3 + .../multisig_rpo_falcon_512.masm | 4 +- .../asm/kernels/transaction/lib/account.masm | 33 +- .../kernels/transaction/lib/asset_vault.masm | 13 +- .../asm/kernels/transaction/lib/epilogue.masm | 8 +- .../asm/kernels/transaction/lib/link_map.masm | 10 +- .../asm/kernels/transaction/lib/memory.masm | 7 +- .../kernels/transaction/lib/output_note.masm | 28 +- .../asm/kernels/transaction/lib/prologue.masm | 17 +- .../asm/kernels/transaction/main.masm | 26 +- crates/miden-lib/asm/miden/active_note.masm | 4 +- .../asm/miden/auth/rpo_falcon512.masm | 4 +- crates/miden-lib/asm/miden/output_note.masm | 4 +- crates/miden-lib/build.rs | 100 ++- crates/miden-lib/src/account/auth/mod.rs | 3 + .../src/account/auth/public_key_commitment.rs | 29 + .../src/account/auth/rpo_falcon_512.rs | 10 +- .../src/account/auth/rpo_falcon_512_acl.rs | 14 +- .../account/auth/rpo_falcon_512_multisig.rs | 19 +- .../miden-lib/src/account/components/mod.rs | 1 + crates/miden-lib/src/account/faucets/mod.rs | 14 +- .../src/account/interface/component.rs | 8 +- crates/miden-lib/src/account/interface/mod.rs | 1 + .../miden-lib/src/account/interface/test.rs | 25 +- crates/miden-lib/src/account/wallets/mod.rs | 7 +- crates/miden-lib/src/auth.rs | 6 +- .../src/errors/transaction_errors.rs | 11 +- crates/miden-lib/src/transaction/events.rs | 94 +- .../src/transaction/kernel_procedures.rs | 44 +- crates/miden-lib/src/transaction/mod.rs | 4 +- crates/miden-objects/Cargo.toml | 5 +- crates/miden-objects/src/account/auth.rs | 2 +- .../miden-objects/src/account/builder/mod.rs | 1 + .../src/account/code/procedure.rs | 2 +- .../src/account/component/mod.rs | 66 +- .../src/account/component/template/mod.rs | 2 + .../template/storage/entry_content.rs | 2 +- .../component/template/storage/placeholder.rs | 56 +- .../component/template/storage/toml.rs | 7 +- crates/miden-objects/src/account/mod.rs | 2 +- .../src/account/storage/map/mod.rs | 14 +- .../miden-objects/src/account/storage/mod.rs | 4 +- .../src/account/storage/partial.rs | 4 +- crates/miden-objects/src/asset/mod.rs | 13 +- crates/miden-objects/src/asset/nonfungible.rs | 12 +- crates/miden-objects/src/asset/vault/mod.rs | 24 +- .../miden-objects/src/batch/proposed_batch.rs | 3 +- .../miden-objects/src/block/account_tree.rs | 18 +- .../src/block/account_witness.rs | 13 +- .../miden-objects/src/block/nullifier_tree.rs | 14 +- .../src/block/partial_account_tree.rs | 14 +- .../src/block/partial_nullifier_tree.rs | 4 +- crates/miden-objects/src/errors.rs | 14 + crates/miden-objects/src/lib.rs | 3 +- crates/miden-objects/src/note/script.rs | 2 + .../miden-objects/src/testing/account_code.rs | 4 +- crates/miden-objects/src/testing/storage.rs | 2 +- .../src/transaction/inputs/account.rs | 4 + .../src/transaction/proven_tx.rs | 3 +- .../miden-objects/src/transaction/tx_args.rs | 3 +- .../kernel_tests/batch/proven_tx_builder.rs | 3 +- .../kernel_tests/block/proven_block_error.rs | 3 +- .../miden-testing/src/kernel_tests/tx/mod.rs | 12 +- .../src/kernel_tests/tx/test_account.rs | 29 +- .../src/kernel_tests/tx/test_account_delta.rs | 30 +- .../src/kernel_tests/tx/test_asset_vault.rs | 6 +- .../src/kernel_tests/tx/test_faucet.rs | 16 +- .../src/kernel_tests/tx/test_fpi.rs | 51 +- .../src/kernel_tests/tx/test_input_note.rs | 4 +- .../src/kernel_tests/tx/test_link_map.rs | 4 +- .../src/kernel_tests/tx/test_note.rs | 25 +- .../src/kernel_tests/tx/test_output_note.rs | 2 +- .../src/kernel_tests/tx/test_prologue.rs | 2 +- .../src/kernel_tests/tx/test_tx.rs | 12 +- crates/miden-testing/src/mock_chain/auth.rs | 11 +- crates/miden-testing/src/mock_host.rs | 39 +- crates/miden-testing/tests/auth/multisig.rs | 43 +- crates/miden-testing/tests/scripts/swap.rs | 2 +- crates/miden-testing/tests/wallet/mod.rs | 5 +- crates/miden-tx/src/executor/exec_host.rs | 18 +- crates/miden-tx/src/executor/mod.rs | 7 +- .../miden-tx/src/host/account_procedures.rs | 2 +- crates/miden-tx/src/host/link_map.rs | 18 +- crates/miden-tx/src/host/mod.rs | 156 ++-- crates/miden-tx/src/prover/mod.rs | 25 +- crates/miden-tx/src/prover/prover_host.rs | 21 +- 89 files changed, 1496 insertions(+), 788 deletions(-) create mode 100644 crates/miden-lib/src/account/auth/public_key_commitment.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d6d27ab877..f4b3d3fc81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,9 +29,10 @@ ### Changes - [BREAKING] Incremented MSRV to 1.89. -- [BREAKING] Remove `MockChain::add_pending_p2id_note` in favor of using `MockChainBuilder` ([#1842](https://github.com/0xMiden/miden-base/pull/#1842)). -- [BREAKING] Remove versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). -- [BREAKING] Move `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). +- [BREAKING] Migrated to `miden-vm` v0.18 and `miden-crypto` v0.17 ([#1832](https://github.com/0xMiden/miden-base/pull/1832)). +- [BREAKING] Removed `MockChain::add_pending_p2id_note` in favor of using `MockChainBuilder` ([#1842](https://github.com/0xMiden/miden-base/pull/#1842)). +- [BREAKING] Removed versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). +- [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Removed versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). - Added `AccountComponent::from_package()` method to create components from `miden-mast-package::Package` ([#1802](https://github.com/0xMiden/miden-base/pull/1802)). - [BREAKING] Removed some of the `note` kernel procedures and use `input_note` procedures instead ([#1834](https://github.com/0xMiden/miden-base/pull/1834)). diff --git a/Cargo.lock b/Cargo.lock index 2993319e6a..f0f3a11825 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,28 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] [[package]] -name = "adler2" -version = "2.0.1" +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] [[package]] name = "ahash" @@ -24,7 +34,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -71,9 +81,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -106,9 +116,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" dependencies = [ "backtrace", ] @@ -170,17 +180,17 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", + "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", ] [[package]] @@ -192,6 +202,18 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + [[package]] name = "bech32" version = "0.11.0" @@ -304,9 +326,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.36" +version = "1.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ "find-msvc-tools", "jobserver", @@ -320,6 +342,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -347,20 +393,31 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstyle", "clap_lex", @@ -372,12 +429,45 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "color-eyre" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors 1.3.0", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" +dependencies = [ + "once_cell", + "owo-colors 1.3.0", + "tracing-core", + "tracing-error", +] + [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -484,6 +574,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -491,6 +593,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -503,6 +606,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derive_more" version = "2.0.1" @@ -530,7 +643,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -539,12 +654,46 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "ena" version = "0.14.3" @@ -554,6 +703,18 @@ dependencies = [ "log", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -605,12 +766,22 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.1", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", ] [[package]] @@ -619,11 +790,21 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" [[package]] name = "findshlibs" @@ -643,6 +824,27 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + +[[package]] +name = "fs-err" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f150ffc8782f35521cec2b23727707cb4045706ba3c854e86bef66b3a8cdbd" +dependencies = [ + "autocfg", +] + [[package]] name = "futures" version = "0.3.31" @@ -745,6 +947,20 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -757,15 +973,15 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.4+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -773,6 +989,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "half" version = "2.6.0" @@ -785,9 +1012,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hermit-abi" @@ -795,6 +1022,24 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "indenter" version = "0.3.4" @@ -803,9 +1048,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.11.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", @@ -829,6 +1074,15 @@ dependencies = [ "str_stack", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "io-uring" version = "0.7.10" @@ -926,20 +1180,34 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom", + "getrandom 0.3.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.78" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + [[package]] name = "keccak" version = "0.1.5" @@ -987,9 +1255,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libm" @@ -1005,9 +1273,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" @@ -1035,7 +1303,7 @@ dependencies = [ "generator", "scoped-tls", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1049,9 +1317,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" @@ -1064,9 +1332,9 @@ dependencies = [ [[package]] name = "miden-air" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +checksum = "09f7f7261ba4f1ff0175138936c45e306ef773289cd697657e3120004f7a1a9f" dependencies = [ "miden-core", "thiserror", @@ -1076,9 +1344,9 @@ dependencies = [ [[package]] name = "miden-assembly" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345ae47710bbb4c6956dcc669a537c5cf2081879b6238c0df6da50e84a77ea3f" +checksum = "c7719656b8064503cb84405ed9d9124025b4e90731f4577d0dc747242d483ea4" dependencies = [ "log", "miden-assembly-syntax", @@ -1090,9 +1358,9 @@ dependencies = [ [[package]] name = "miden-assembly-syntax" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +checksum = "9b343d32937785e508c2ea094e11f5d9aca6f0dda37ee1cf4aa7c3309ae9c089" dependencies = [ "aho-corasick", "lalrpop", @@ -1102,9 +1370,10 @@ dependencies = [ "miden-debug-types", "miden-utils-diagnostics", "midenc-hir-type", + "proptest", "regex", "rustc_version 0.4.1", - "semver 1.0.26", + "semver 1.0.27", "smallvec", "thiserror", ] @@ -1120,10 +1389,11 @@ dependencies = [ [[package]] name = "miden-core" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ad0b07592486e02de3ff7f3bff1d7fa81b1a7120f7f0b1027d650d810bbab" +checksum = "fbcdfc12e9ef7ac5731f1bfd5985486292696a3bcf974667e3680d9c4d7f9c87" dependencies = [ + "enum_dispatch", "miden-crypto", "miden-debug-types", "miden-formatting", @@ -1136,29 +1406,37 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.15.9" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +checksum = "4b8c283ed3017d58d9b648d9b1099f26abff52b185ecbb7b91ff4fb406c286b7" dependencies = [ "blake3", "cc", + "chacha20poly1305", + "flume", + "getrandom 0.2.16", "glob", + "hkdf", + "k256", "num", "num-complex", "rand", - "rand_core", + "rand_chacha", + "rand_core 0.9.3", + "rand_hc", "sha3", "thiserror", "winter-crypto", "winter-math", "winter-utils", + "zeroize", ] [[package]] name = "miden-debug-types" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +checksum = "cb09918a959cad19c181568dfd3580324f72429d1ba818d90d0b04fb36a4f715" dependencies = [ "memchr", "miden-crypto", @@ -1186,7 +1464,9 @@ version = "0.12.0" dependencies = [ "anyhow", "assert_matches", + "fs-err", "miden-assembly", + "miden-core", "miden-objects", "miden-processor", "miden-stdlib", @@ -1198,13 +1478,14 @@ dependencies = [ [[package]] name = "miden-mast-package" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +checksum = "125bfb772948874e78d154f0b9a4caf22ea839c45aa6e72d2daeb23fda0775c1" dependencies = [ "derive_more", "miden-assembly-syntax", "miden-core", + "thiserror", ] [[package]] @@ -1220,7 +1501,7 @@ dependencies = [ "indenter", "lazy_static", "miden-miette-derive", - "owo-colors", + "owo-colors 4.2.3", "regex", "rustc_version 0.2.3", "rustversion", @@ -1256,9 +1537,11 @@ dependencies = [ "anyhow", "assert_matches", "bech32", + "color-eyre", "criterion 0.5.1", - "getrandom", + "getrandom 0.3.3", "log", + "miden-air", "miden-assembly", "miden-core", "miden-crypto", @@ -1271,7 +1554,7 @@ dependencies = [ "rand", "rand_xoshiro", "rstest", - "semver 1.0.26", + "semver 1.0.27", "serde", "tempfile", "thiserror", @@ -1282,14 +1565,15 @@ dependencies = [ [[package]] name = "miden-processor" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64304339292cc4e50a7b534197326824eeb21f3ffaadd955be63cc57093854ed" +checksum = "8074e1e08aa5ac1b570f8e23275127f75aedf3b28eda615b9699018776c7169e" dependencies = [ "miden-air", "miden-core", "miden-debug-types", "miden-utils-diagnostics", + "paste", "thiserror", "tokio", "tracing", @@ -1298,9 +1582,9 @@ dependencies = [ [[package]] name = "miden-prover" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21637f9dcca045f7bf65da20d42f37f86373b43092ae9df9a230cffb86f4d6d" +checksum = "fbe2d652ad71c2ab5c63621c4c706dded05d89b24ce5d68b811068ceae927746" dependencies = [ "miden-air", "miden-debug-types", @@ -1312,13 +1596,14 @@ dependencies = [ [[package]] name = "miden-stdlib" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7308c32ec08dd75c29653315a0f395786d391c2fe55a5318ec39662a31de6a26" +checksum = "94e78035c073f88709af30eb7823390b05eaeb6692a8eac869f309ba836968d9" dependencies = [ "env_logger", "miden-assembly", "miden-core", + "miden-crypto", "miden-processor", "miden-utils-sync", "thiserror", @@ -1375,9 +1660,9 @@ dependencies = [ [[package]] name = "miden-utils-diagnostics" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +checksum = "e6619048a6027feb08af62adb6a26a959a7e817987f4c4baa208a775eda550c4" dependencies = [ "miden-crypto", "miden-debug-types", @@ -1388,9 +1673,9 @@ dependencies = [ [[package]] name = "miden-utils-sync" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb026e69ae937b2a83bf69ea86577e0ec2450cb22cce163190b9fd42f2972b63" +checksum = "cc49b00e9c15ef7e67debed489e9d4dbe3b12dbf91d98e815bf6a6768ff60a9d" dependencies = [ "lock_api", "loom", @@ -1399,9 +1684,9 @@ dependencies = [ [[package]] name = "miden-verifier" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a305e5e2c68d10bcb8e2ed3dc38e54bc3a538acc9eadc154b713d5bb47af22" +checksum = "c009bd93c82f447509503953d3669ef5d24b749b97af04b63acab60041d0b476" dependencies = [ "miden-air", "miden-core", @@ -1412,9 +1697,9 @@ dependencies = [ [[package]] name = "midenc-hir-type" -version = "0.1.5" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +checksum = "7798671ffbf6596de00619a9abaec67dc26965b891328c9d65c4cb6007597d50" dependencies = [ "miden-formatting", "smallvec", @@ -1423,11 +1708,11 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ - "adler2", + "adler", ] [[package]] @@ -1441,6 +1726,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -1564,9 +1858,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -1589,11 +1883,23 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "owo-colors" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" + [[package]] name = "owo-colors" -version = "4.2.2" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" [[package]] name = "parking_lot" @@ -1655,6 +1961,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "plotters" version = "0.3.7" @@ -1683,6 +1999,17 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.11.1" @@ -1738,9 +2065,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] @@ -1754,6 +2081,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" +dependencies = [ + "bitflags 2.9.4", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "unarray", +] + [[package]] name = "quick-xml" version = "0.26.0" @@ -1765,9 +2108,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -1785,7 +2128,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.3", ] [[package]] @@ -1795,7 +2138,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", ] [[package]] @@ -1804,7 +2156,25 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", + "getrandom 0.3.3", +] + +[[package]] +name = "rand_hc" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b363d4f6370f88d62bf586c80405657bde0f0e1b8945d47d2ad59b906cb4f54" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", ] [[package]] @@ -1813,7 +2183,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" dependencies = [ - "rand_core", + "rand_core 0.9.3", ] [[package]] @@ -1847,9 +2217,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -1859,9 +2229,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -1880,6 +2250,16 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rgb" version = "0.8.52" @@ -1939,7 +2319,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.26", + "semver 1.0.27", ] [[package]] @@ -1957,15 +2337,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.1", ] [[package]] @@ -2001,6 +2381,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "0.9.0" @@ -2012,11 +2406,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -2027,18 +2422,28 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2047,24 +2452,36 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "indexmap", "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "serde_spanned" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" dependencies = [ - "serde", + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", ] [[package]] @@ -2092,6 +2509,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "siphasher" version = "1.0.1" @@ -2121,6 +2548,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spin" @@ -2131,6 +2561,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2164,6 +2604,12 @@ dependencies = [ "vte", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "supports-color" version = "3.0.2" @@ -2187,9 +2633,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "symbolic-common" -version = "12.16.2" +version = "12.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9da12f8fecbbeaa1ee62c1d50dc656407e007c3ee7b2a41afce4b5089eaef15e" +checksum = "d03f433c9befeea460a01d750e698aa86caf86dcfbd77d552885cd6c89d52f50" dependencies = [ "debugid", "memmap2", @@ -2199,9 +2645,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.16.2" +version = "12.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd35afe0ef9d35d3dcd41c67ddf882fc832a387221338153b7cd685a105495c" +checksum = "13d359ef6192db1760a34321ec4f089245ede4342c27e59be99642f12a859de8" dependencies = [ "rustc-demangle", "symbolic-common", @@ -2226,24 +2672,24 @@ checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" [[package]] name = "tempfile" -version = "3.21.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.3.3", "once_cell", - "rustix 1.0.8", - "windows-sys 0.60.2", + "rustix 1.1.2", + "windows-sys 0.61.1", ] [[package]] name = "term" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a43bddab41f8626c7bdaab872bbba75f8df5847b516d77c569c746e2ae5eb746" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -2278,18 +2724,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -2367,14 +2813,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" dependencies = [ "indexmap", - "serde", + "serde_core", "serde_spanned", - "toml_datetime 0.7.0", + "toml_datetime", "toml_parser", "toml_writer", "winnow", @@ -2382,44 +2828,39 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - -[[package]] -name = "toml_datetime" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ "indexmap", - "toml_datetime 0.6.11", + "toml_datetime", + "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" [[package]] name = "tracing" @@ -2453,6 +2894,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" +dependencies = [ + "tracing", + "tracing-subscriber 0.2.25", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -2464,6 +2915,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.20" @@ -2484,9 +2946,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.110" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e257d7246e7a9fd015fb0b28b330a8d4142151a33f03e6a497754f4b1f6a8e" +checksum = "0ded9fdb81f30a5708920310bfcd9ea7482ff9cba5f54601f7a19a877d5c2392" dependencies = [ "dissimilar", "glob", @@ -2504,11 +2966,17 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-linebreak" @@ -2534,6 +3002,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "utf8parse" version = "0.2.2" @@ -2589,18 +3067,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.4+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -2611,9 +3098,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -2625,9 +3112,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2635,9 +3122,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -2648,18 +3135,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.78" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -2687,7 +3174,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] [[package]] @@ -2744,9 +3231,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" dependencies = [ "proc-macro2", "quote", @@ -2755,9 +3242,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" dependencies = [ "proc-macro2", "quote", @@ -2837,14 +3324,14 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.4", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" dependencies = [ "windows-link 0.2.0", ] @@ -2882,11 +3369,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" dependencies = [ - "windows-link 0.1.3", + "windows-link 0.2.0", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -3168,9 +3655,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.45.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "zerocopy" @@ -3191,3 +3678,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" diff --git a/Cargo.toml b/Cargo.toml index d839a19062..0731ad906d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,15 +48,15 @@ miden-tx = { default-features = false, path = "crates/miden-tx", ve miden-tx-batch-prover = { default-features = false, path = "crates/miden-tx-batch-prover", version = "0.12" } # Miden dependencies -miden-assembly = { default-features = false, version = "0.17" } -miden-core = { default-features = false, version = "0.17" } -miden-crypto = { default-features = false, version = "0.15.6" } -miden-mast-package = { default-features = false, version = "0.17" } -miden-processor = { default-features = false, version = "0.17" } -miden-prover = { default-features = false, version = "0.17" } -miden-stdlib = { default-features = false, version = "0.17" } -miden-utils-sync = { default-features = false, version = "0.17" } -miden-verifier = { default-features = false, version = "0.17" } +miden-assembly = { default-features = false, version = "0.18" } +miden-core = { default-features = false, version = "0.18" } +miden-crypto = { default-features = false, version = "0.17" } +miden-mast-package = { default-features = false, version = "0.18" } +miden-processor = { default-features = false, version = "0.18" } +miden-prover = { default-features = false, version = "0.18" } +miden-stdlib = { default-features = false, version = "0.18" } +miden-utils-sync = { default-features = false, version = "0.18" } +miden-verifier = { default-features = false, version = "0.18" } # External dependencies anyhow = { default-features = false, features = ["backtrace", "std"], version = "1.0" } diff --git a/crates/miden-lib/Cargo.toml b/crates/miden-lib/Cargo.toml index a7aefcfcdf..fe845f123a 100644 --- a/crates/miden-lib/Cargo.toml +++ b/crates/miden-lib/Cargo.toml @@ -22,6 +22,7 @@ with-debug-info = ["miden-stdlib/with-debug-info"] [dependencies] # Miden dependencies +miden-core = { workspace = true } miden-objects = { workspace = true } miden-processor = { workspace = true } miden-stdlib = { workspace = true } @@ -31,7 +32,9 @@ rand = { optional = true, workspace = true } thiserror = { workspace = true } [build-dependencies] +fs-err = { version = "3" } miden-assembly = { workspace = true } +miden-core = { workspace = true } miden-stdlib = { workspace = true } regex = { version = "1.11" } walkdir = { version = "2.5" } diff --git a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm index b9c120a3a8..7fcd47c845 100644 --- a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm @@ -9,10 +9,10 @@ use.miden::auth # Auth Request Constants # The event to request an authentication signature. -const.AUTH_REQUEST=131087 +const.AUTH_REQUEST=event("miden::auth::request") # The event emitted when a signature is not found for a required signer. -const.UNAUTHORIZED_EVENT=131102 +const.UNAUTHORIZED_EVENT=event("miden::auth::unauthorized") # Storage Layout Constants # diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 7866a2405d..81b0a6e81e 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -121,45 +121,45 @@ const.ACCOUNT_PROCEDURE_DATA_LENGTH=8 # ================================================================================================= # Event emitted before a foreign account is loaded from the advice inputs. -const.ACCOUNT_BEFORE_FOREIGN_LOAD_EVENT=131104 +const.ACCOUNT_BEFORE_FOREIGN_LOAD_EVENT=event("miden::account::before_foreign_load") # Event emitted before an asset is added to the account vault. -const.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT=131072 +const.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT=event("miden::account::vault_before_add_asset") # Event emitted after an asset is added to the account vault. -const.ACCOUNT_VAULT_AFTER_ADD_ASSET_EVENT=131073 +const.ACCOUNT_VAULT_AFTER_ADD_ASSET_EVENT=event("miden::account::vault_after_add_asset") # Event emitted before an asset is removed from the account vault. -const.ACCOUNT_VAULT_BEFORE_REMOVE_ASSET_EVENT=131074 +const.ACCOUNT_VAULT_BEFORE_REMOVE_ASSET_EVENT=event("miden::account::vault_before_remove_asset") # Event emitted after an asset is removed from the account vault. -const.ACCOUNT_VAULT_AFTER_REMOVE_ASSET_EVENT=131075 +const.ACCOUNT_VAULT_AFTER_REMOVE_ASSET_EVENT=event("miden::account::vault_after_remove_asset") # Event emitted before a fungible asset's balance is fetched from the account vault. -const.ACCOUNT_VAULT_BEFORE_GET_BALANCE_EVENT=131105 +const.ACCOUNT_VAULT_BEFORE_GET_BALANCE_EVENT=event("miden::account::vault_before_get_balance") # Event emitted before it is checked whether a non-fungible asset exists in the account vault. -const.ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET_EVENT=131106 +const.ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET_EVENT=event("miden::account::vault_before_has_non_fungible_asset") # Event emitted before an account storage item is updated. -const.ACCOUNT_STORAGE_BEFORE_SET_ITEM_EVENT=131076 +const.ACCOUNT_STORAGE_BEFORE_SET_ITEM_EVENT=event("miden::account::storage_before_set_item") # Event emitted after an account storage item is updated. -const.ACCOUNT_STORAGE_AFTER_SET_ITEM_EVENT=131077 +const.ACCOUNT_STORAGE_AFTER_SET_ITEM_EVENT=event("miden::account::storage_after_set_item") # Event emitted before an account storage map item is accessed. -const.ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT=131103 +const.ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM_EVENT=event("miden::account::storage_before_get_map_item") # Event emitted before an account storage map item is updated. -const.ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM_EVENT=131078 +const.ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM_EVENT=event("miden::account::storage_before_set_map_item") # Event emitted after an account storage map item is updated. -const.ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM_EVENT=131079 +const.ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM_EVENT=event("miden::account::storage_after_set_map_item") # Event emitted before an account nonce is incremented. -const.ACCOUNT_BEFORE_INCREMENT_NONCE_EVENT=131080 +const.ACCOUNT_BEFORE_INCREMENT_NONCE_EVENT=event("miden::account::before_increment_nonce") # Event emitted after an account nonce is incremented. -const.ACCOUNT_AFTER_INCREMENT_NONCE_EVENT=131081 +const.ACCOUNT_AFTER_INCREMENT_NONCE_EVENT=event("miden::account::after_increment_nonce") # Event emitted to push the index of the account procedure at the top of the operand stack onto # the advice stack. -const.ACCOUNT_PUSH_PROCEDURE_INDEX_EVENT=131082 +const.ACCOUNT_PUSH_PROCEDURE_INDEX_EVENT=event("miden::account::push_procedure_index") # CONSTANT ACCESSORS # ================================================================================================= @@ -917,7 +917,8 @@ export.add_asset_to_vault # => [ASSET, ASSET'] # emit event to signal that an asset is being added to the account vault - emit.ACCOUNT_VAULT_AFTER_ADD_ASSET_EVENT dropw + emit.ACCOUNT_VAULT_AFTER_ADD_ASSET_EVENT + dropw # => [ASSET'] end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm index 4cae60233e..b1c8cb45e4 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm @@ -26,6 +26,11 @@ const.ERR_VAULT_REMOVE_FUNGIBLE_ASSET_FAILED_INITIAL_VALUE_INVALID="failed to re const.ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND="failed to remove non-existent non-fungible asset from the vault" +# EVENTS +# ================================================================================================= + +const.SMT_PEEK=event("stdlib::collections::smt::smt_peek") + # CONSTANTS # ================================================================================================= @@ -106,7 +111,7 @@ export.peek_balance # => [ASSET_KEY, ASSET_VAULT_ROOT] # lookup asset - adv.push_smtpeek + emit.SMT_PEEK # OS => [ASSET_KEY, ASSET_VAULT_ROOT] # AS => [ASSET] @@ -198,8 +203,8 @@ export.add_fungible_asset # we therefore overwrite the faucet id with the faucet id from ASSET to account for this edge case mem_loadw swapw # => [ASSET_KEY, VAULT_ROOT, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] - adv.push_smtpeek - adv_loadw + + emit.SMT_PEEK adv_loadw # => [CUR_VAULT_VALUE, VAULT_ROOT, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] swapw # => [VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] @@ -531,7 +536,7 @@ proc.peek_asset # => [ASSET_KEY, ASSET_VAULT_ROOT] # lookup asset - adv.push_smtpeek + emit.SMT_PEEK # OS => [ASSET_KEY, ASSET_VAULT_ROOT] # AS => [ASSET] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm index 1f2f39e8f7..39d22e7900 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -22,16 +22,16 @@ const.ERR_AUTH_PROCEDURE_CALLED_FROM_WRONG_CONTEXT="auth procedure had been call # ================================================================================================= # Event emitted to signal that the compute_fee procedure has obtained the current number of cycles. -const.EPILOGUE_AFTER_TX_CYCLES_OBTAINED=131097 +const.EPILOGUE_AFTER_TX_CYCLES_OBTAINED=event("miden::epilogue::after_tx_cycles_obtained") # Event emitted to signal that the fee was computed. -const.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT=131098 +const.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT=event("miden::epilogue::before_tx_fee_removed_from_account") # Event emitted to signal that an execution of the authentication procedure has started. -const.EPILOGUE_AUTH_PROC_START=131107 +const.EPILOGUE_AUTH_PROC_START=event("miden::epilogue::auth_proc_start") # Event emitted to signal that an execution of the authentication procedure has ended. -const.EPILOGUE_AUTH_PROC_END=131108 +const.EPILOGUE_AUTH_PROC_END=event("miden::epilogue::auth_proc_end") # An additional number of cyclces to account for the number of cycles that smt::set will take when # removing the computed fee from the asset vault. diff --git a/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm b/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm index bb03a73d5a..f11f04dbff 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm @@ -167,10 +167,10 @@ const.GET_OPERATION_ABSENT_AT_HEAD=1 # ================================================================================================= # Event emitted when an entry is set. -const.LINK_MAP_SET_EVENT=131100 +const.LINK_MAP_SET_EVENT=event("miden::link_map::set") # Event emitted when an entry is fetched. -const.LINK_MAP_GET_EVENT=131101 +const.LINK_MAP_GET_EVENT=event("miden::link_map::get") # LINK MAP PROCEDURES # ================================================================================================= @@ -199,7 +199,8 @@ const.LINK_MAP_GET_EVENT=131101 #! - the host provides faulty advice. See panic sections of assert_entry_ptr_is_valid, #! update_entry, insert_at_head, insert_after_entry. export.set - emit.LINK_MAP_SET_EVENT adv_push.2 + emit.LINK_MAP_SET_EVENT + adv_push.2 # => [operation, entry_ptr, map_ptr, KEY, VALUE0, VALUE1] dup.2 dup.2 @@ -265,7 +266,8 @@ end #! - the host provides faulty advice. See panic sections of assert_entry_ptr_is_valid, #! get_existing_value, assert_absent_at_head, assert_absent_after_entry. export.get - emit.LINK_MAP_GET_EVENT adv_push.2 + emit.LINK_MAP_GET_EVENT + adv_push.2 # => [get_operation, entry_ptr, map_ptr, KEY] dup.2 dup.2 diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index f9709ecb97..5af6ece99a 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -1052,7 +1052,7 @@ end #! Where: #! - nonce is the nonce of the native account of the transaction. export.get_native_account_nonce - push.NATIVE_ACCOUNT_DATA_PTR add.ACCT_NONCE_OFFSET + push.NATIVE_ACCOUNT_DATA_PTR add.ACCT_NONCE_OFFSET mem_load end @@ -1368,7 +1368,7 @@ end export.get_account_initial_storage_slots_ptr exec.is_native_account # => [is_native_account] - + if.true # For native account, return the initial storage slots pointer exec.get_native_account_initial_storage_slots_ptr @@ -1830,8 +1830,11 @@ end #! - note_ptr is the memory address at which the output note data begins. export.get_output_note_metadata padw + # => [0, 0, 0, 0, note_ptr] movup.4 add.OUTPUT_NOTE_METADATA_OFFSET + # => [(note_ptr + offset), 0, 0, 0, 0] mem_loadw + # => [METADATA] end #! Sets the output note's metadata. diff --git a/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm b/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm index 1589a82584..706d0b7fc2 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm @@ -59,14 +59,14 @@ const.ERR_NOTE_TAG_MUST_BE_U32="the note's tag must fit into a u32 so the 32 mos # ================================================================================================= # Event emitted before a new note is created. -const.NOTE_BEFORE_CREATED_EVENT=131083 +const.NOTE_BEFORE_CREATED_EVENT=event("miden::note::before_created") # Event emitted after a new note is created. -const.NOTE_AFTER_CREATED_EVENT=131084 +const.NOTE_AFTER_CREATED_EVENT=event("miden::note::after_created") # Event emitted before an ASSET is added to a note -const.NOTE_BEFORE_ADD_ASSET_EVENT=131085 +const.NOTE_BEFORE_ADD_ASSET_EVENT=event("miden::note::before_add_asset") # Event emitted after an ASSET is added to a note -const.NOTE_AFTER_ADD_ASSET_EVENT=131086 +const.NOTE_AFTER_ADD_ASSET_EVENT=event("miden::note::after_add_asset") # OUTPUT NOTE PROCEDURES # ================================================================================================= @@ -111,7 +111,9 @@ export.create emit.NOTE_AFTER_CREATED_EVENT # set the metadata for the output note - dup.4 exec.memory::set_output_note_metadata dropw + dup.4 + # => [note_ptr, NOTE_METADATA, note_ptr, RECIPIENT, note_idx] + exec.memory::set_output_note_metadata dropw # => [note_ptr, RECIPIENT, note_idx] # set the RECIPIENT for the output note @@ -147,23 +149,23 @@ export.get_assets_info # able to get the assets later (in the `miden::output_note::get_assets` procedure) # get the start and the end pointers of the asset data - # - # notice that if the number of assets is odd, the asset data end pointer will be shifted one - # word further to make the assets number even (the same way it is done in the + # + # notice that if the number of assets is odd, the asset data end pointer will be shifted one + # word further to make the assets number even (the same way it is done in the # `note::compute_output_note_assets_commitment` procedure) movup.4 exec.memory::get_output_note_asset_data_ptr # => [assets_data_ptr, ASSETS_COMMITMENT, num_assets] dup dup.6 dup is_odd add # => [padded_num_assets, assets_data_ptr, assets_data_ptr, ASSETS_COMMITMENT, num_assets] - + mul.4 add # => [assets_end_ptr, assets_start_ptr, ASSETS_COMMITMENT, num_assets] movdn.5 movdn.4 # => [ASSETS_COMMITMENT, assets_start_ptr, assets_end_ptr, num_assets] - # store the assets data to the advice map using ASSETS_COMMITMENT as a key + # store the assets data to the advice map using ASSETS_COMMITMENT as a key adv.insert_mem # => [ASSETS_COMMITMENT, assets_start_ptr, assets_end_ptr, num_assets] @@ -222,7 +224,7 @@ export.add_asset end # => [note_ptr, note_idx] - # update the assets commitment dirty flag to signal that the current assets commitment is not + # update the assets commitment dirty flag to signal that the current assets commitment is not # valid anymore push.1 swap exec.memory::set_output_note_dirty_flag # => [note_idx] @@ -238,9 +240,9 @@ end #! Outputs: [note_index] export.assert_note_index_in_bounds # assert that the provided note index is less than the total number of notes - dup exec.memory::get_num_output_notes + dup exec.memory::get_num_output_notes # => [output_notes_num, note_index, note_index] - + u32assert2.err=ERR_OUTPUT_NOTE_INDEX_OUT_OF_BOUNDS u32lt assert.err=ERR_OUTPUT_NOTE_INDEX_OUT_OF_BOUNDS # => [note_index] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm index 12b8c5dc2c..b0258de5f8 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -16,6 +16,12 @@ use.$kernel::memory # Max U32 value, used for initializing the expiration block number const.MAX_BLOCK_NUM=0xFFFFFFFF +# EVENTS +#================================================================================================= + +# Emission of an equivalent to `ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT`, use in `add_input_note_assets_to_vault` +const.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT=event("miden::account::vault_before_add_asset") + # ERRORS # ================================================================================================= @@ -93,7 +99,7 @@ end # KERNEL DATA # ================================================================================================= -#! Saves the kernel procedure roots to the memory. Verifies that the kernel commitment match the +#! Saves the kernel procedure roots to the memory. Verifies that the kernel commitment match the #! sequential hash of kernel procedures. #! #! Inputs: @@ -442,7 +448,7 @@ proc.process_account_data # copy the initial account vault root to the input vault root to support transaction asset # invariant checking # this account vault root is also stored as an initial one in the global inputs - exec.memory::get_account_vault_root + exec.memory::get_account_vault_root exec.memory::set_init_account_vault_root exec.memory::set_input_vault_root dropw # => [ACCOUNT_COMMITMENT] @@ -576,7 +582,7 @@ end #! - SCRIPT_ROOT is the note's script root. #! - INPUTS_COMMITMENT is the sequential hash of the padded note's inputs. #! - ASSETS_COMMITMENT is the sequential hash of the padded note's assets. -#! - NULLIFIER is the result of +#! - NULLIFIER is the result of #! `hash(SERIAL_NUMBER || SCRIPT_ROOT || INPUTS_COMMITMENT || ASSETS_COMMITMENT)`. proc.process_input_note_details exec.memory::get_input_note_core_ptr @@ -773,7 +779,8 @@ proc.add_input_note_assets_to_vault # pre-load all relevant merkle paths for the note assets before tx execution. # # This emitted event is equivalent to ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT. - emit.131072 + + emit.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT # => [ASSET, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] exec.asset_vault::add_asset dropw @@ -1061,7 +1068,7 @@ end #! #! Where: #! - TX_SCRIPT_ROOT is the transaction's script root. -#! - TX_SCRIPT_ARGS is the word of values which could be used directly or could be used to obtain +#! - TX_SCRIPT_ARGS is the word of values which could be used directly or could be used to obtain #! some values associated with it from the advice map. proc.process_tx_script_data # read the transaction script root from the advice stack diff --git a/crates/miden-lib/asm/kernels/transaction/main.masm b/crates/miden-lib/asm/kernels/transaction/main.masm index c41603c661..dc399d0c91 100644 --- a/crates/miden-lib/asm/kernels/transaction/main.masm +++ b/crates/miden-lib/asm/kernels/transaction/main.masm @@ -9,29 +9,29 @@ use.$kernel::prologue # ================================================================================================= # Event emitted to signal that an execution of the transaction prologue has started. -const.PROLOGUE_START=131088 +const.PROLOGUE_START=event("miden::tx::prologue_start") # Event emitted to signal that an execution of the transaction prologue has ended. -const.PROLOGUE_END=131089 +const.PROLOGUE_END=event("miden::tx::prologue_end") # Event emitted to signal that the notes processing has started. -const.NOTES_PROCESSING_START=131090 +const.NOTES_PROCESSING_START=event("miden::tx::notes_processing_start") # Event emitted to signal that the notes processing has ended. -const.NOTES_PROCESSING_END=131091 +const.NOTES_PROCESSING_END=event("miden::tx::notes_processing_end") # Event emitted to signal that the note consuming has started. -const.NOTE_EXECUTION_START=131092 +const.NOTE_EXECUTION_START=event("miden::tx::note_execution_start") # Event emitted to signal that the note consuming has ended. -const.NOTE_EXECUTION_END=131093 +const.NOTE_EXECUTION_END=event("miden::tx::note_execution_end") # Event emitted to signal that the transaction script processing has started. -const.TX_SCRIPT_PROCESSING_START=131094 +const.TX_SCRIPT_PROCESSING_START=event("miden::tx::tx_script_processing_start") # Event emitted to signal that the transaction script processing has ended. -const.TX_SCRIPT_PROCESSING_END=131095 +const.TX_SCRIPT_PROCESSING_END=event("miden::tx::tx_script_processing_end") # Event emitted to signal that an execution of the transaction epilogue has started. -const.EPILOGUE_START=131096 +const.EPILOGUE_START=event("miden::tx::epilogue_start") # Event emitted to signal that an execution of the transaction epilogue has ended. -const.EPILOGUE_END=131099 +const.EPILOGUE_END=event("miden::tx::epilogue_end") # MAIN # ================================================================================================= @@ -69,7 +69,7 @@ const.EPILOGUE_END=131099 proc.main.1 # Prologue # --------------------------------------------------------------------------------------------- - + emit.PROLOGUE_START exec.prologue::prepare_transaction @@ -79,7 +79,7 @@ proc.main.1 # Note Processing # --------------------------------------------------------------------------------------------- - + emit.NOTES_PROCESSING_START exec.memory::get_num_input_notes @@ -124,7 +124,7 @@ proc.main.1 # Transaction Script Processing # --------------------------------------------------------------------------------------------- - + emit.TX_SCRIPT_PROCESSING_START # get the memory address of the transaction script root and load it to the stack diff --git a/crates/miden-lib/asm/miden/active_note.masm b/crates/miden-lib/asm/miden/active_note.masm index 80596b001c..71ab6dd4fc 100644 --- a/crates/miden-lib/asm/miden/active_note.masm +++ b/crates/miden-lib/asm/miden/active_note.masm @@ -15,7 +15,7 @@ const.ERR_NOTE_INVALID_NUMBER_OF_INPUTS="the specified number of note inputs doe # ACTIVE NOTE PROCEDURES # ================================================================================================= # -# By the "active note" notion here we assume the note which is currently being processed by the +# By the "active note" notion here we assume the note which is currently being processed by the # transaction kernel. #! Writes the assets of the active note into memory starting at the specified address. @@ -321,7 +321,7 @@ end # HELPER PROCEDURES # ================================================================================================= -#! Writes the note inputs stored in the advice map to the memory specified by the provided +#! Writes the note inputs stored in the advice map to the memory specified by the provided #! destination pointer. #! #! Inputs: diff --git a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm index f8a4a3d27a..87b89c6cc8 100644 --- a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm +++ b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm @@ -7,10 +7,10 @@ use.std::crypto::dsa::rpo_falcon512 # ================================================================================================= # The event to request an authentication signature. -const.AUTH_REQUEST=131087 +const.AUTH_REQUEST=event("miden::auth::request") # The event emitted when a signature is not found for a required signer. -const.UNAUTHORIZED_EVENT=131102 +const.UNAUTHORIZED_EVENT=event("miden::auth::unauthorized") # The slot in this component's storage layout where the public key is stored. const.PUBLIC_KEY_SLOT=0 diff --git a/crates/miden-lib/asm/miden/output_note.masm b/crates/miden-lib/asm/miden/output_note.masm index cb3877d5ed..63fd14df81 100644 --- a/crates/miden-lib/asm/miden/output_note.masm +++ b/crates/miden-lib/asm/miden/output_note.masm @@ -68,14 +68,14 @@ export.get_assets_info # => [ASSETS_COMMITMENT, num_assets, pad(11)] # clean the stack - swapdw dropw dropw + swapdw dropw dropw repeat.3 movup.5 drop end # => [ASSETS_COMMITMENT, num_assets] end -#! Writes the assets of the output note with the specified index into memory starting at the +#! Writes the assets of the output note with the specified index into memory starting at the #! specified address. #! #! Attention: memory starting from the `dest_ptr` should have enough space to store all the assets diff --git a/crates/miden-lib/build.rs b/crates/miden-lib/build.rs index 137889393c..37b3b918f3 100644 --- a/crates/miden-lib/build.rs +++ b/crates/miden-lib/build.rs @@ -1,11 +1,11 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::env; use std::fmt::Write; -use std::fs::{self}; use std::io::{self}; use std::path::{Path, PathBuf}; use std::sync::Arc; +use fs_err as fs; use miden_assembly::diagnostics::{IntoDiagnostic, Result, WrapErr}; use miden_assembly::utils::Serializable; use miden_assembly::{ @@ -91,6 +91,9 @@ fn main() -> Result<()> { // set target directory to {OUT_DIR}/assets let target_dir = Path::new(&build_dir).join(ASSETS_DIR); + // re-build if any of the generated files were modified + println!("cargo::rerun-if-changed={}", target_dir.display()); + // compile transaction kernel let mut assembler = compile_tx_kernel(&source_dir.join(ASM_TX_KERNEL_DIR), &target_dir.join("kernels"))?; @@ -115,6 +118,7 @@ fn main() -> Result<()> { generate_error_constants(&source_dir)?; + generate_event_constants(&target_dir).into_diagnostic()?; Ok(()) } @@ -257,7 +261,7 @@ fn generate_kernel_proc_hash_file(kernel: KernelLibrary) -> Result<()> { fs::write( KERNEL_PROCEDURES_RS_FILE, format!( - r#"//! This file is generated by build.rs, do not modify + r#"// This file is generated by build.rs, do not modify use miden_objects::{{Word, word}}; @@ -486,7 +490,7 @@ fn get_masm_files>(dir_path: P) -> Result> { } } } else { - println!("cargo:rerun-The specified path is not a directory."); + println!("cargo:warn=The specified path is not a directory."); } Ok(files) @@ -774,3 +778,91 @@ impl TxKernelErrorCategory { } } } + +fn generate_event_constants(dst: &Path) -> std::io::Result<()> { + let values = [ + ("ACCOUNT_BEFORE_FOREIGN_LOAD", "account::before_foreign_load"), + ("ACCOUNT_VAULT_BEFORE_ADD_ASSET", "account::vault_before_add_asset"), + ("ACCOUNT_VAULT_AFTER_ADD_ASSET", "account::vault_after_add_asset"), + ("ACCOUNT_VAULT_BEFORE_REMOVE_ASSET", "account::vault_before_remove_asset"), + ("ACCOUNT_VAULT_AFTER_REMOVE_ASSET", "account::vault_after_remove_asset"), + ("ACCOUNT_VAULT_BEFORE_GET_BALANCE", "account::vault_before_get_balance"), + ( + "ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET", + "account::vault_before_has_non_fungible_asset", + ), + ("ACCOUNT_STORAGE_BEFORE_SET_ITEM", "account::storage_before_set_item"), + ("ACCOUNT_STORAGE_AFTER_SET_ITEM", "account::storage_after_set_item"), + ("ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM", "account::storage_before_get_map_item"), + ("ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM", "account::storage_before_set_map_item"), + ("ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM", "account::storage_after_set_map_item"), + ("ACCOUNT_BEFORE_INCREMENT_NONCE", "account::before_increment_nonce"), + ("ACCOUNT_AFTER_INCREMENT_NONCE", "account::after_increment_nonce"), + ("ACCOUNT_PUSH_PROCEDURE_INDEX", "account::push_procedure_index"), + ("NOTE_BEFORE_CREATED", "note::before_created"), + ("NOTE_AFTER_CREATED", "note::after_created"), + ("NOTE_BEFORE_ADD_ASSET", "note::before_add_asset"), + ("NOTE_AFTER_ADD_ASSET", "note::after_add_asset"), + ("AUTH_REQUEST", "auth::request"), + ("PROLOGUE_START", "tx::prologue_start"), + ("PROLOGUE_END", "tx::prologue_end"), + ("NOTES_PROCESSING_START", "tx::notes_processing_start"), + ("NOTES_PROCESSING_END", "tx::notes_processing_end"), + ("NOTE_EXECUTION_START", "tx::note_execution_start"), + ("NOTE_EXECUTION_END", "tx::note_execution_end"), + ("TX_SCRIPT_PROCESSING_START", "tx::tx_script_processing_start"), + ("TX_SCRIPT_PROCESSING_END", "tx::tx_script_processing_end"), + ("EPILOGUE_START", "tx::epilogue_start"), + ("EPILOGUE_END", "tx::epilogue_end"), + ("EPILOGUE_AFTER_TX_CYCLES_OBTAINED", "epilogue::after_tx_cycles_obtained"), + ( + "EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT", + "epilogue::before_tx_fee_removed_from_account", + ), + ("LINK_MAP_SET_EVENT", "link_map::set"), + ("LINK_MAP_GET_EVENT", "link_map::get"), + ("UNAUTHORIZED_EVENT", "auth::unauthorized"), + ("EPILOGUE_AUTH_PROC_START", "epilogue::auth_proc_start"), + ("EPILOGUE_AUTH_PROC_END", "epilogue::auth_proc_end"), + ]; + + use std::io::Write as _; + + let map = HashMap::::from_iter(values.iter().map(|&(konst, name)| { + let name = format!("miden::{name}"); + let value = miden_core::EventId::from_name(&name).as_felt().as_int(); + (name, (konst, value)) + })); + + let path = dst.join("transaction_events.rs"); + let mut f = fs::OpenOptions::new().create(true).truncate(true).write(true).open(path)?; + for (_name, (konst, value)) in map.iter() { + writeln!(&mut f, "const {konst}: u64 = {value};")?; + } + + #[cfg(feature = "std")] + { + writeln!(&mut f, "// This file is generated by build.rs, do not modify")?; + + // we require a form of lut, but we don't want to bother supporting non-std cases for now + let mut inner = String::new(); + for (name, (_, value)) in map.iter() { + inner.push_str(format!(" ({value}, \"{name}\"),\n").as_str()); + } + + writeln!(&mut f)?; + writeln!(&mut f, "// Reverse lookup table for better error messages")?; + writeln!(&mut f)?; + writeln!( + &mut f, + r###" +pub(crate) static EVENT_NAME_LUT: ::miden_objects::utils::sync::LazyLock<::std::collections::HashMap> = ::miden_objects::utils::sync::LazyLock::new(|| {{ + ::std::collections::HashMap::from_iter([ + {inner} + ]) +}}); + "### + )?; + } + Ok(()) +} diff --git a/crates/miden-lib/src/account/auth/mod.rs b/crates/miden-lib/src/account/auth/mod.rs index 2861b784b0..b16071780f 100644 --- a/crates/miden-lib/src/account/auth/mod.rs +++ b/crates/miden-lib/src/account/auth/mod.rs @@ -1,6 +1,9 @@ mod no_auth; pub use no_auth::NoAuth; +mod public_key_commitment; +pub use public_key_commitment::PublicKeyCommitment; + mod rpo_falcon_512; pub use rpo_falcon_512::AuthRpoFalcon512; diff --git a/crates/miden-lib/src/account/auth/public_key_commitment.rs b/crates/miden-lib/src/account/auth/public_key_commitment.rs new file mode 100644 index 0000000000..7aa63275b3 --- /dev/null +++ b/crates/miden-lib/src/account/auth/public_key_commitment.rs @@ -0,0 +1,29 @@ +use miden_core::Word; +use miden_core::crypto::dsa::rpo_falcon512::PublicKey; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PublicKeyCommitment(pub Word); + +impl PublicKeyCommitment { + pub fn empty() -> Self { + Self(Word::empty()) + } +} + +impl From for PublicKeyCommitment { + fn from(value: PublicKey) -> Self { + Self(value.to_commitment()) + } +} + +impl From for Word { + fn from(value: PublicKeyCommitment) -> Self { + value.0 + } +} + +impl From for PublicKeyCommitment { + fn from(value: Word) -> Self { + Self(value) + } +} diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512.rs index 50636000b6..582adeb779 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512.rs @@ -1,6 +1,6 @@ use miden_objects::account::{AccountComponent, StorageSlot}; -use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; +use crate::account::auth::PublicKeyCommitment; use crate::account::components::rpo_falcon_512_library; /// An [`AccountComponent`] implementing the RpoFalcon512 signature scheme for authentication of @@ -17,13 +17,13 @@ use crate::account::components::rpo_falcon_512_library; /// /// [kasm]: crate::transaction::TransactionKernel::assembler pub struct AuthRpoFalcon512 { - public_key: PublicKey, + pub_key: PublicKeyCommitment, } impl AuthRpoFalcon512 { /// Creates a new [`AuthRpoFalcon512`] component with the given `public_key`. - pub fn new(public_key: PublicKey) -> Self { - Self { public_key } + pub fn new(pub_key: PublicKeyCommitment) -> Self { + Self { pub_key } } } @@ -31,7 +31,7 @@ impl From for AccountComponent { fn from(falcon: AuthRpoFalcon512) -> Self { AccountComponent::new( rpo_falcon_512_library(), - vec![StorageSlot::Value(falcon.public_key.into())], + vec![StorageSlot::Value(falcon.pub_key.into())], ) .expect("falcon component should satisfy the requirements of a valid account component") .with_supports_all_types() diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs index 14a9482a13..b8815da658 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs @@ -1,9 +1,9 @@ use alloc::vec::Vec; use miden_objects::account::{AccountCode, AccountComponent, StorageMap, StorageSlot}; -use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; use miden_objects::{AccountError, Word}; +use crate::account::auth::PublicKeyCommitment; use crate::account::components::rpo_falcon_512_acl_library; /// Configuration for [`AuthRpoFalcon512Acl`] component. @@ -110,7 +110,7 @@ impl Default for AuthRpoFalcon512AclConfig { /// /// This component supports all account types. pub struct AuthRpoFalcon512Acl { - public_key: PublicKey, + pub_key: PublicKeyCommitment, config: AuthRpoFalcon512AclConfig, } @@ -121,7 +121,7 @@ impl AuthRpoFalcon512Acl { /// # Panics /// Panics if more than [AccountCode::MAX_NUM_PROCEDURES] procedures are specified. pub fn new( - public_key: PublicKey, + pub_key: PublicKeyCommitment, config: AuthRpoFalcon512AclConfig, ) -> Result { let max_procedures = AccountCode::MAX_NUM_PROCEDURES; @@ -131,7 +131,7 @@ impl AuthRpoFalcon512Acl { ))); } - Ok(Self { public_key, config }) + Ok(Self { pub_key, config }) } } @@ -140,7 +140,7 @@ impl From for AccountComponent { let mut storage_slots = Vec::with_capacity(3); // Slot 0: Public key - storage_slots.push(StorageSlot::Value(falcon.public_key.into())); + storage_slots.push(StorageSlot::Value(falcon.pub_key.into())); // Slot 1: [num_tracked_procs, allow_unauthorized_output_notes, // allow_unauthorized_input_notes, 0] @@ -205,7 +205,7 @@ mod tests { /// Parametrized test helper for ACL component testing fn test_acl_component(config: AclTestConfig) { - let public_key = PublicKey::new(Word::empty()); + let public_key = PublicKeyCommitment::from(Word::empty()); // Build the configuration let mut acl_config = AuthRpoFalcon512AclConfig::new() @@ -232,7 +232,7 @@ mod tests { // Assert public key in slot 0 let public_key_slot = account.storage().get_item(0).expect("storage slot 0 access failed"); - assert_eq!(public_key_slot, Word::from(public_key)); + assert_eq!(public_key_slot, public_key.into()); // Assert configuration in slot 1 let slot_1 = account.storage().get_item(1).expect("storage slot 1 access failed"); diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs index 36976359be..81ac08b9a5 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs @@ -1,11 +1,14 @@ use alloc::vec::Vec; use miden_objects::account::{AccountComponent, StorageMap, StorageSlot}; -use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; use miden_objects::{AccountError, Word}; +use crate::account::auth::PublicKeyCommitment; use crate::account::components::multisig_library; +// MULTISIG AUTHENTICATION COMPONENT +// ================================================================================================ + /// An [`AccountComponent`] implementing a multisig based on RpoFalcon512 signatures. /// /// This component requires a threshold number of signatures from a set of approvers. @@ -19,7 +22,7 @@ use crate::account::components::multisig_library; #[derive(Debug)] pub struct AuthRpoFalcon512Multisig { threshold: u32, - approvers: Vec, + approvers: Vec, } impl AuthRpoFalcon512Multisig { @@ -28,7 +31,7 @@ impl AuthRpoFalcon512Multisig { /// /// # Errors /// Returns an error if threshold is 0 or greater than the number of approvers. - pub fn new(threshold: u32, approvers: Vec) -> Result { + pub fn new(threshold: u32, approvers: Vec) -> Result { if threshold == 0 { return Err(AccountError::other("threshold must be at least 1")); } @@ -90,9 +93,9 @@ mod tests { #[test] fn test_multisig_component_setup() { // Create test public keys - let pub_key_1 = PublicKey::new(Word::from([1u32, 0, 0, 0])); - let pub_key_2 = PublicKey::new(Word::from([2u32, 0, 0, 0])); - let pub_key_3 = PublicKey::new(Word::from([3u32, 0, 0, 0])); + let pub_key_1 = PublicKeyCommitment::from(Word::from([1u32, 0, 0, 0])); + let pub_key_2 = PublicKeyCommitment::from(Word::from([2u32, 0, 0, 0])); + let pub_key_3 = PublicKeyCommitment::from(Word::from([3u32, 0, 0, 0])); let approvers = vec![pub_key_1, pub_key_2, pub_key_3]; let threshold = 2u32; @@ -124,7 +127,7 @@ mod tests { /// Test multisig component with minimum threshold (1 of 1) #[test] fn test_multisig_component_minimum_threshold() { - let pub_key = PublicKey::new(Word::from([42u32, 0, 0, 0])); + let pub_key = PublicKeyCommitment::from(Word::from([42u32, 0, 0, 0])); let approvers = vec![pub_key]; let threshold = 1u32; @@ -151,7 +154,7 @@ mod tests { /// Test multisig component error cases #[test] fn test_multisig_component_error_cases() { - let pub_key = PublicKey::new(Word::from([1u32, 0, 0, 0])); + let pub_key = PublicKeyCommitment::from(Word::from([1u32, 0, 0, 0])); let approvers = vec![pub_key]; // Test threshold = 0 (should fail) diff --git a/crates/miden-lib/src/account/components/mod.rs b/crates/miden-lib/src/account/components/mod.rs index de2b7700ec..749b8b12cf 100644 --- a/crates/miden-lib/src/account/components/mod.rs +++ b/crates/miden-lib/src/account/components/mod.rs @@ -6,6 +6,7 @@ use miden_objects::account::AccountProcedureInfo; use miden_objects::assembly::Library; use miden_objects::utils::Deserializable; use miden_objects::utils::sync::LazyLock; +use miden_processor::MastNodeExt; use crate::account::interface::AccountComponentInterface; diff --git a/crates/miden-lib/src/account/faucets/mod.rs b/crates/miden-lib/src/account/faucets/mod.rs index d5ae247446..657a22d090 100644 --- a/crates/miden-lib/src/account/faucets/mod.rs +++ b/crates/miden-lib/src/account/faucets/mod.rs @@ -354,7 +354,6 @@ pub enum FungibleFaucetError { #[cfg(test)] mod tests { use assert_matches::assert_matches; - use miden_objects::crypto::dsa::rpo_falcon512::{self, PublicKey}; use miden_objects::{FieldElement, ONE, Word}; use super::{ @@ -368,13 +367,13 @@ mod tests { TokenSymbol, create_basic_fungible_faucet, }; - use crate::account::auth::AuthRpoFalcon512; + use crate::account::auth::{AuthRpoFalcon512, PublicKeyCommitment}; use crate::account::wallets::BasicWallet; #[test] fn faucet_contract_creation() { - let pub_key = rpo_falcon512::PublicKey::new(Word::new([ONE; 4])); - let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key }; + let pub_key_word = Word::new([ONE; 4]); + let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key: pub_key_word.into() }; // we need to use an initial seed to create the wallet account let init_seed: [u8; 32] = [ @@ -403,7 +402,7 @@ mod tests { // The falcon auth component is added first so its assigned storage slot for the public key // will be 1. - assert_eq!(faucet_account.storage().get_item(1).unwrap(), Word::from(pub_key)); + assert_eq!(faucet_account.storage().get_item(1).unwrap(), pub_key_word); // Slot 2 stores [num_tracked_procs, allow_unauthorized_output_notes, // allow_unauthorized_input_notes, 0]. With 1 tracked procedure (distribute), @@ -445,8 +444,9 @@ mod tests { #[test] fn faucet_create_from_account() { // prepare the test data - let mock_public_key = PublicKey::new(Word::from([0, 1, 2, 3u32])); - let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes(); + let mock_word = Word::from([0, 1, 2, 3u32]); + let mock_public_key = PublicKeyCommitment::from(mock_word); + let mock_seed = mock_word.as_bytes(); // valid account let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol"); diff --git a/crates/miden-lib/src/account/interface/component.rs b/crates/miden-lib/src/account/interface/component.rs index d52356c1ad..1cf9aa929b 100644 --- a/crates/miden-lib/src/account/interface/component.rs +++ b/crates/miden-lib/src/account/interface/component.rs @@ -3,11 +3,11 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use miden_objects::account::{AccountId, AccountProcedureInfo, AccountStorage}; -use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; use miden_objects::note::PartialNote; use miden_objects::{Felt, FieldElement, Word}; use crate::AuthScheme; +use crate::account::auth::PublicKeyCommitment; use crate::account::components::WellKnownComponent; use crate::account::interface::AccountInterfaceError; @@ -101,7 +101,7 @@ impl AccountComponentInterface { AccountComponentInterface::AuthRpoFalcon512(storage_index) | AccountComponentInterface::AuthRpoFalcon512Acl(storage_index) => { vec![AuthScheme::RpoFalcon512 { - pub_key: PublicKey::new( + pub_key: PublicKeyCommitment::from( storage .get_item(*storage_index) .expect("invalid storage index of the public key"), @@ -299,8 +299,8 @@ fn extract_multisig_auth_scheme(storage: &AccountStorage, storage_index: u8) -> let map_key = [Felt::new(key_index as u64), Felt::ZERO, Felt::ZERO, Felt::ZERO]; match storage.get_map_item(pub_keys_map_slot, map_key.into()) { - Ok(pub_key_word) => { - pub_keys.push(PublicKey::new(pub_key_word)); + Ok(pub_key) => { + pub_keys.push(PublicKeyCommitment::from(pub_key)); }, Err(_) => { // If we can't read a public key, panic with a clear error message diff --git a/crates/miden-lib/src/account/interface/mod.rs b/crates/miden-lib/src/account/interface/mod.rs index 92e6acfa6c..bca596435a 100644 --- a/crates/miden-lib/src/account/interface/mod.rs +++ b/crates/miden-lib/src/account/interface/mod.rs @@ -8,6 +8,7 @@ use miden_objects::account::{Account, AccountCode, AccountId, AccountIdPrefix, A use miden_objects::assembly::mast::{MastForest, MastNode, MastNodeId}; use miden_objects::note::{Note, NoteScript, PartialNote}; use miden_objects::transaction::TransactionScript; +use miden_processor::MastNodeExt; use thiserror::Error; use crate::AuthScheme; diff --git a/crates/miden-lib/src/account/interface/test.rs b/crates/miden-lib/src/account/interface/test.rs index 969ad4a6bf..2cffe34064 100644 --- a/crates/miden-lib/src/account/interface/test.rs +++ b/crates/miden-lib/src/account/interface/test.rs @@ -7,7 +7,6 @@ use miden_objects::account::{Account, AccountBuilder, AccountComponent, AccountT use miden_objects::assembly::diagnostics::NamedSource; use miden_objects::assembly::{Assembler, DefaultSourceManager}; use miden_objects::asset::{FungibleAsset, NonFungibleAsset, TokenSymbol}; -use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; use miden_objects::crypto::rand::{FeltRng, RpoRandomCoin}; use miden_objects::note::{ Note, @@ -26,7 +25,12 @@ use miden_objects::testing::account_id::{ use miden_objects::{AccountError, Felt, NoteError, Word, ZERO}; use crate::AuthScheme; -use crate::account::auth::{AuthRpoFalcon512, AuthRpoFalcon512Multisig, NoAuth}; +use crate::account::auth::{ + AuthRpoFalcon512, + AuthRpoFalcon512Multisig, + NoAuth, + PublicKeyCommitment, +}; use crate::account::faucets::BasicFungibleFaucet; use crate::account::interface::{ AccountComponentInterface, @@ -702,8 +706,10 @@ impl AccountComponentExt for AccountComponent { } } +/// Helper function to create a mock auth component for testing fn get_mock_auth_component() -> AuthRpoFalcon512 { - let mock_public_key = PublicKey::new(Word::from([0, 1, 2, 3u32])); + let mock_word = Word::from([0, 1, 2, 3u32]); + let mock_public_key = PublicKeyCommitment::from(mock_word); AuthRpoFalcon512::new(mock_public_key) } @@ -734,7 +740,7 @@ fn test_get_auth_scheme_rpo_falcon512() { let auth_scheme = &auth_schemes[0]; match auth_scheme { AuthScheme::RpoFalcon512 { pub_key } => { - assert_eq!(*pub_key, PublicKey::new(Word::from([0, 1, 2, 3u32]))); + assert_eq!(*pub_key, PublicKeyCommitment::from(Word::from([0, 1, 2, 3u32]))); }, _ => panic!("Expected RpoFalcon512 auth scheme"), } @@ -800,7 +806,8 @@ fn test_account_interface_from_account_uses_get_auth_scheme() { match &wallet_account_interface.auth()[0] { AuthScheme::RpoFalcon512 { pub_key } => { - assert_eq!(*pub_key, PublicKey::new(Word::from([0, 1, 2, 3u32]))); + let expected_pub_key = PublicKeyCommitment::from(Word::from([0, 1, 2, 3u32])); + assert_eq!(*pub_key, expected_pub_key); }, _ => panic!("Expected RpoFalcon512 auth scheme"), } @@ -839,7 +846,7 @@ fn test_account_interface_get_auth_scheme() { assert_eq!(wallet_account_interface.auth().len(), 1); match &wallet_account_interface.auth()[0] { AuthScheme::RpoFalcon512 { pub_key } => { - assert_eq!(*pub_key, PublicKey::new(Word::from([0, 1, 2, 3u32]))); + assert_eq!(*pub_key, PublicKeyCommitment::from(Word::from([0, 1, 2, 3u32]))); }, _ => panic!("Expected RpoFalcon512 auth scheme"), } @@ -903,9 +910,9 @@ fn test_public_key_extraction_regular_account() { #[test] fn test_public_key_extraction_multisig_account() { // Create test public keys - let pub_key_1 = PublicKey::new(Word::from([1u32, 0, 0, 0])); - let pub_key_2 = PublicKey::new(Word::from([2u32, 0, 0, 0])); - let pub_key_3 = PublicKey::new(Word::from([3u32, 0, 0, 0])); + let pub_key_1 = PublicKeyCommitment::from(Word::from([1u32, 0, 0, 0])); + let pub_key_2 = PublicKeyCommitment::from(Word::from([2u32, 0, 0, 0])); + let pub_key_3 = PublicKeyCommitment::from(Word::from([3u32, 0, 0, 0])); let approvers = vec![pub_key_1, pub_key_2, pub_key_3]; let threshold = 2u32; diff --git a/crates/miden-lib/src/account/wallets/mod.rs b/crates/miden-lib/src/account/wallets/mod.rs index 087b235eb4..ed0b4276d3 100644 --- a/crates/miden-lib/src/account/wallets/mod.rs +++ b/crates/miden-lib/src/account/wallets/mod.rs @@ -158,17 +158,16 @@ pub fn create_basic_wallet( #[cfg(test)] mod tests { - - use miden_objects::crypto::dsa::rpo_falcon512; use miden_objects::{ONE, Word}; use miden_processor::utils::{Deserializable, Serializable}; use super::{Account, AccountStorageMode, AccountType, AuthScheme, create_basic_wallet}; + use crate::account::auth::PublicKeyCommitment; use crate::account::wallets::BasicWallet; #[test] fn test_create_basic_wallet() { - let pub_key = rpo_falcon512::PublicKey::new(Word::from([ONE; 4])); + let pub_key = PublicKeyCommitment::from(Word::from([ONE; 4])); let wallet = create_basic_wallet( [1; 32], AuthScheme::RpoFalcon512 { pub_key }, @@ -183,7 +182,7 @@ mod tests { #[test] fn test_serialize_basic_wallet() { - let pub_key = rpo_falcon512::PublicKey::new(Word::from([ONE; 4])); + let pub_key = PublicKeyCommitment::from(Word::from([ONE; 4])); let wallet = create_basic_wallet( [1; 32], AuthScheme::RpoFalcon512 { pub_key }, diff --git a/crates/miden-lib/src/auth.rs b/crates/miden-lib/src/auth.rs index 33de58f69c..116f00617d 100644 --- a/crates/miden-lib/src/auth.rs +++ b/crates/miden-lib/src/auth.rs @@ -1,6 +1,6 @@ use alloc::vec::Vec; -use miden_objects::crypto::dsa::rpo_falcon512; +use crate::account::auth::PublicKeyCommitment; /// Defines authentication schemes available to standard and faucet accounts. pub enum AuthScheme { @@ -8,12 +8,12 @@ pub enum AuthScheme { /// a variant of the [Falcon](https://falcon-sign.info/) signature scheme. This variant differs from /// the standard in that instead of using SHAKE256 hash function in the hash-to-point algorithm /// we use RPO256. This makes the signature more efficient to verify in Miden VM. - RpoFalcon512 { pub_key: rpo_falcon512::PublicKey }, + RpoFalcon512 { pub_key: PublicKeyCommitment }, /// A multi-signature authentication scheme using RPO Falcon512 signatures. /// Requires a threshold number of signatures from the provided public keys. RpoFalcon512Multisig { threshold: u32, - pub_keys: Vec, + pub_keys: Vec, }, /// A minimal authentication scheme that provides no cryptographic authentication. /// It only increments the nonce if the account state has actually changed during diff --git a/crates/miden-lib/src/errors/transaction_errors.rs b/crates/miden-lib/src/errors/transaction_errors.rs index ba2155f346..a94d719d21 100644 --- a/crates/miden-lib/src/errors/transaction_errors.rs +++ b/crates/miden-lib/src/errors/transaction_errors.rs @@ -1,16 +1,21 @@ +use miden_core::EventId; use thiserror::Error; +use crate::transaction::TransactionEvent; + // TRANSACTION EVENT PARSING ERROR // ================================================================================================ #[derive(Debug, Error)] pub enum TransactionEventError { + #[error("event id {0} is reserved for system events")] + ReservedSystemEvent(EventId), #[error("event id {0} is not a valid transaction event")] - InvalidTransactionEvent(u32), + InvalidTransactionEvent(EventId, Option<&'static str>), #[error("event id {0} is not a transaction kernel event")] - NotTransactionEvent(u32), + NotTransactionEvent(EventId, Option<&'static str>), #[error("event id {0} can only be emitted from the root context")] - NotRootContext(u32), + NotRootContext(TransactionEvent), } // TRANSACTION TRACE PARSING ERROR diff --git a/crates/miden-lib/src/transaction/events.rs b/crates/miden-lib/src/transaction/events.rs index 5217145fc5..add77d7302 100644 --- a/crates/miden-lib/src/transaction/events.rs +++ b/crates/miden-lib/src/transaction/events.rs @@ -1,79 +1,23 @@ use core::fmt; +pub use miden_core::EventId; + use super::TransactionEventError; // CONSTANTS // ================================================================================================ +// Include the generated event constants +include!(concat!(env!("OUT_DIR"), "/assets/transaction_events.rs")); // TRANSACTION EVENT // ================================================================================================ -const ACCOUNT_BEFORE_FOREIGN_LOAD: u32 = 0x2_0020; // 131104 - -const ACCOUNT_VAULT_BEFORE_ADD_ASSET: u32 = 0x2_0000; // 131072 -const ACCOUNT_VAULT_AFTER_ADD_ASSET: u32 = 0x2_0001; // 131073 - -const ACCOUNT_VAULT_BEFORE_REMOVE_ASSET: u32 = 0x2_0002; // 131074 -const ACCOUNT_VAULT_AFTER_REMOVE_ASSET: u32 = 0x2_0003; // 131075 - -const ACCOUNT_VAULT_BEFORE_GET_BALANCE: u32 = 0x2_0021; // 131105 - -const ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET: u32 = 0x2_0022; // 131106 - -const ACCOUNT_STORAGE_BEFORE_SET_ITEM: u32 = 0x2_0004; // 131076 -const ACCOUNT_STORAGE_AFTER_SET_ITEM: u32 = 0x2_0005; // 131077 - -const ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM: u32 = 0x2_001f; // 131103 - -const ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM: u32 = 0x2_0006; // 131078 -const ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM: u32 = 0x2_0007; // 131079 - -const ACCOUNT_BEFORE_INCREMENT_NONCE: u32 = 0x2_0008; // 131080 -const ACCOUNT_AFTER_INCREMENT_NONCE: u32 = 0x2_0009; // 131081 - -const ACCOUNT_PUSH_PROCEDURE_INDEX: u32 = 0x2_000a; // 131082 - -const NOTE_BEFORE_CREATED: u32 = 0x2_000b; // 131083 -const NOTE_AFTER_CREATED: u32 = 0x2_000c; // 131084 - -const NOTE_BEFORE_ADD_ASSET: u32 = 0x2_000d; // 131085 -const NOTE_AFTER_ADD_ASSET: u32 = 0x2_000e; // 131086 - -const AUTH_REQUEST: u32 = 0x2_000f; // 131087 - -const PROLOGUE_START: u32 = 0x2_0010; // 131088 -const PROLOGUE_END: u32 = 0x2_0011; // 131089 - -const NOTES_PROCESSING_START: u32 = 0x2_0012; // 131090 -const NOTES_PROCESSING_END: u32 = 0x2_0013; // 131091 - -const NOTE_EXECUTION_START: u32 = 0x2_0014; // 131092 -const NOTE_EXECUTION_END: u32 = 0x2_0015; // 131093 - -const TX_SCRIPT_PROCESSING_START: u32 = 0x2_0016; // 131094 -const TX_SCRIPT_PROCESSING_END: u32 = 0x2_0017; // 131095 - -const EPILOGUE_START: u32 = 0x2_0018; // 131096 -const EPILOGUE_AFTER_TX_CYCLES_OBTAINED: u32 = 0x2_0019; // 131097 -const EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT: u32 = 0x2_001a; // 131098 -const EPILOGUE_END: u32 = 0x2_001b; // 131099 - -const LINK_MAP_SET_EVENT: u32 = 0x2_001c; // 131100 -const LINK_MAP_GET_EVENT: u32 = 0x2_001d; // 131101 - -const UNAUTHORIZED_EVENT: u32 = 0x2_001e; // 131102 - -const EPILOGUE_AUTH_PROC_START: u32 = 0x2_0023; // 131107 -const EPILOGUE_AUTH_PROC_END: u32 = 0x2_0024; // 131108 - /// Events which may be emitted by a transaction kernel. /// -/// The events are emitted via the `emit.` instruction. The event ID is a 32-bit -/// unsigned integer which is used to identify the event type. For events emitted by the -/// transaction kernel, the event_id is structured as follows: -/// - The upper 16 bits of the event ID are set to 2. -/// - The lower 16 bits represent a unique event ID within the transaction kernel. -#[repr(u32)] +/// The events are emitted via the `emit.` instruction. The event ID is a Felt +/// derived from the `EventId` string which is used to identify the event type. Events emitted +/// by the transaction kernel are in the `miden` namespace. +#[repr(u64)] #[derive(Debug, Clone, Eq, PartialEq)] pub enum TransactionEvent { AccountBeforeForeignLoad = ACCOUNT_BEFORE_FOREIGN_LOAD, @@ -137,9 +81,6 @@ pub enum TransactionEvent { } impl TransactionEvent { - /// Value of the top 16 bits of a transaction kernel event ID. - pub const ID_PREFIX: u32 = 2; - /// Returns `true` if the event is privileged, i.e. it is only allowed to be emitted from the /// root context of the VM, which is where the transaction kernel executes. pub fn is_privileged(&self) -> bool { @@ -154,15 +95,22 @@ impl fmt::Display for TransactionEvent { } } -impl TryFrom for TransactionEvent { +impl TryFrom for TransactionEvent { type Error = TransactionEventError; - fn try_from(value: u32) -> Result { - if value >> 16 != TransactionEvent::ID_PREFIX { - return Err(TransactionEventError::NotTransactionEvent(value)); + fn try_from(value: EventId) -> Result { + let raw = value.as_felt().as_int(); + + #[cfg(feature = "std")] + let name = EVENT_NAME_LUT.get(&raw).copied(); + #[cfg(not(feature = "std"))] + let name = Some(""); + + if value.is_reserved() { + return Err(TransactionEventError::ReservedSystemEvent(value)); } - match value { + match raw { ACCOUNT_BEFORE_FOREIGN_LOAD => Ok(TransactionEvent::AccountBeforeForeignLoad), ACCOUNT_VAULT_BEFORE_ADD_ASSET => Ok(TransactionEvent::AccountVaultBeforeAddAsset), @@ -236,7 +184,7 @@ impl TryFrom for TransactionEvent { UNAUTHORIZED_EVENT => Ok(TransactionEvent::Unauthorized), - _ => Err(TransactionEventError::InvalidTransactionEvent(value)), + _ => Err(TransactionEventError::InvalidTransactionEvent(value, name)), } } } diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index 4030ec3489..2c10d82af5 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -1,4 +1,4 @@ -//! This file is generated by build.rs, do not modify +// This file is generated by build.rs, do not modify use miden_objects::{Word, word}; @@ -18,51 +18,51 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // account_get_nonce word!("0x4a1f11db21ddb1f0ebf7c9fd244f896a95e99bb136008185da3e7d6aa85827a3"), // account_incr_nonce - word!("0x72f4595fd7030542ab303c77be42962671948ef18ffeda49b0e88a374f0969f6"), + word!("0x99c6b16e86eb9eae02657256b8859e2809acd05bf25810933cf50e72d876d8bf"), // account_get_native_nonce word!("0xeae4dcae877e64a1951aa1ca35ac2adda724e359ee9c7689e55c42dde55d70c4"), // account_get_code_commitment - word!("0x02e55aa37f40207bc2a3882383d4c0f1f6633b5f3ea5b7ef814d827632aa7ae8"), + word!("0xb2ebd0acc4ef40d37c403190dc07d03d7df9169fb8752e8025e3fe469b5ee192"), // account_get_initial_storage_commitment word!("0x5932cb0394cc85330e65e4c0325bb869ec1d08b295c310f7375076a98b143d57"), // account_compute_storage_commitment - word!("0x2e508f8505188ce9b6c46be727e2c1a237806a19402486e38105665b87191526"), + word!("0xa87008550383e1a88dde5d0adefc68ee3bf477aec07e4700f9101241aa1e868f"), // account_get_item - word!("0x011e508cf9b261c33e5f3da1afbf23caefa1f6bc7eac9de2cd123c77fa74f02a"), + word!("0xe1e6843fb47f24476a12ef8cd19dd5de2dd74b90433051b26720dce5ab223bf0"), // account_get_item_init - word!("0x46948d2c64c5b8979cbf1d628a90459b54b41491db5e0f1cff8747c9901da165"), + word!("0x256b8513f29310cc8aa8ead06f6de800b7cf342dbfa435a2e11cc32a32d62345"), // account_set_item - word!("0xd2232daa3895669f2bb34af764504d72432fb119eb0be0ce07481290c8701af8"), + word!("0x84b5206c5a0dccf56568bc0157b8322e8a506332bc212f1ad35bab4fe9f6bfed"), // account_get_map_item - word!("0x95449dd3a32ca3e069acc132e8f44fa87679e2f373f4ca7ae1807246802c5d0d"), + word!("0xc310b9a4a08531061839abd3f575a681a6af61c36ca48491d0896d3badee87f1"), // account_get_map_item_init - word!("0x2e84c009a58b5fda1547865090ac446294d4db20d0aefef3d9e4c6a1a93df8fd"), + word!("0x19a84e1abf9a99c1af352c39d0562bad29003b572cd4eb7a5fdceeac388eaa1b"), // account_set_map_item - word!("0x33309593aa405279a27907cee07b0cde54dbf4c088d0995a05f61e60236cfb0f"), + word!("0x6ee1674cb94eaf4e23383abbfe918bff742ec13f69150eaf66cc9ea0243f4a7e"), // account_get_initial_vault_root word!("0x4d4d91079aaacad1bc86b29a0d61d25508ccb705c29d1b1357016f7373bf299e"), // account_get_vault_root word!("0x42a2bfb8eac4fce9bbf75ea15215b00729faeeaf7fff784692948d3f618a9bb7"), // account_add_asset - word!("0x8e7c972f1eaa807de54df810f851db14557bb88c846c7bcb3d21340f34834c57"), + word!("0xcc3f09332303fc856a05fe2ec176a19e8e9e69213e380f7ac019fbd67ca42552"), // account_remove_asset - word!("0xc5ac5f912281dca3f8f778e713f564f71695af68d094937072a303ea0d8385c0"), + word!("0x7b965e458a962667a9cdc54a6677fd0c5a573bd7b265ccc33775a8a985aeead5"), // account_get_balance - word!("0x010bf86e092ec9d35ab4778d7bb8a5c8659594b64409f682cf860cd48114d59c"), + word!("0x52233827fc91ef50a5dc09f74d3176011d244fd80e291ae751b64bdaa2c6cf75"), // account_has_non_fungible_asset - word!("0xc976f1583f11533cd4887d03bd4d82d8051c0a930f926f67ce8b6e9cb0af34a1"), + word!("0xf975c799cffebf8565a8479475fe04c1832ef2a7484c4a2a42bbf7d8a340d649"), // account_compute_delta_commitment word!("0xb4589587f804af8205f9179ec6b58814d78171a3dc6d78cbf470db512ec25129"), // account_was_procedure_called - word!("0x84c8c518a005605619909976ce54c41d6a88505e815421ff4b5516d0285b28bf"), + word!("0x34f27a609f2f2b4fec454b17182552b0acc52524e507e134257a1f1ed30a57cd"), // faucet_mint_asset - word!("0xbdd92de4f1308992f95c45de90a79799ff96439494088d715bd7838880625433"), + word!("0x83cca30914ec2265bb79bf0eda0c4fc1dfa2a300b47511528a1c85f49967faa9"), // faucet_burn_asset - word!("0x17b95ae638852d24116254223385d5c627820000880b7f60f39c5b3cb5e5231e"), + word!("0x5a2b12bbd942e74187c1a5c1b8efe546e98f15b9915c3224d48f3147eb8dcc3e"), // faucet_get_total_fungible_asset_issuance word!("0x7d32952d4dc0edd0311e3424b8128df2d48cf949f800c28218fbc851a8db42b5"), // faucet_is_non_fungible_asset_issued - word!("0xfe8db6e0903ad0d6a368cedd197f756ade6c17d275d131fc8e1a07f9cb96875e"), + word!("0xcf6969fde43c797d0bbf76685cb707ff46774fe606b075db0cd7a8c369e29e07"), // input_note_get_metadata word!("0x7ad3e94585e7a397ee27443c98b376ed8d4ba762122af6413fde9314c00a6219"), // input_note_get_assets_info @@ -76,15 +76,15 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // input_note_get_recipient word!("0xd3c255177f9243bb1a523a87615bbe76dd5a3605fcae87eb9d3a626d4ecce33c"), // output_note_create - word!("0x52b37f8b25e26517f22f1f600acae7fbfffa84094595ba961af2af807a484736"), + word!("0x6562a9d1d8605d158415a4dcd4c8fd48fc2b6c21ec64c188db9def16b991a560"), // output_note_get_metadata word!("0xde4a5b57f9d53692459383e6cf6302ef3602a348896ed6ab6fdf67e07fa483ff"), // output_note_get_assets_info - word!("0x71d0e246d52c4d896e6508564207e049d4d68da187a143fe95bf5e7f5602f967"), + word!("0xc1af8e03d4672761fab05e742cc267a03d090d43f1348721eeb00881ebcfead8"), // output_note_get_recipient word!("0xc824115ed79a2e1670daed8c18fba1bc15f54c5ec0ec6699de69a00b21d9df92"), // output_note_add_asset - word!("0xb40136c331981a3aff1cc2879e394517abef86a75e4ee5180161976ac360f43d"), + word!("0x70bed5d5bdd012db2dc3f47a72b4d1f4c58d2be9b7b86e63c95076ebb7a80a94"), // tx_get_num_input_notes word!("0xfcc186d4b65c584f3126dda1460b01eef977efd76f9e36f972554af28e33c685"), // tx_get_input_notes_commitment @@ -100,7 +100,7 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ // tx_get_block_timestamp word!("0x7903185b847517debb6c2072364e3e757b99ee623e97c2bd0a4661316c5c5418"), // tx_start_foreign_context - word!("0x7fac8c3ab1af62226616bd157d01238ce3252f006bf4004e1b2ac6bc38f6c6f1"), + word!("0xabd1952a963dac6441e309628fd375424c6d997a91e667fd7dceb7b20eaca40e"), // tx_end_foreign_context word!("0xaa0018aa8da890b73511879487f65553753fb7df22de380dd84c11e6f77eec6f"), // tx_get_expiration_delta diff --git a/crates/miden-lib/src/transaction/mod.rs b/crates/miden-lib/src/transaction/mod.rs index e69c2a261c..efe6bb19c4 100644 --- a/crates/miden-lib/src/transaction/mod.rs +++ b/crates/miden-lib/src/transaction/mod.rs @@ -28,7 +28,7 @@ use super::MidenLib; pub mod memory; mod events; -pub use events::TransactionEvent; +pub use events::{EventId, TransactionEvent}; mod inputs; pub use inputs::{TransactionAdviceInputs, TransactionAdviceMapMismatch}; @@ -284,7 +284,7 @@ impl TransactionKernel { /// - Indices 13..16 on the stack are not zeroes. /// - Overflow addresses are not empty. pub fn parse_output_stack( - stack: &StackOutputs, + stack: &StackOutputs, // FIXME TODO add an extension trait for this one ) -> Result<(Word, Word, FungibleAsset, BlockNumber), TransactionOutputError> { let output_notes_commitment = stack .get_stack_word(OUTPUT_NOTES_COMMITMENT_WORD_IDX * 4) diff --git a/crates/miden-objects/Cargo.toml b/crates/miden-objects/Cargo.toml index 1a35b97bc5..d35e510402 100644 --- a/crates/miden-objects/Cargo.toml +++ b/crates/miden-objects/Cargo.toml @@ -30,7 +30,7 @@ std = [ "miden-processor/std", "miden-verifier/std", ] -testing = ["dep:rand", "dep:rand_xoshiro", "dep:winter-rand-utils"] +testing = ["dep:rand", "dep:rand_xoshiro", "dep:winter-rand-utils", "miden-air/testing"] [dependencies] # Miden dependencies @@ -65,3 +65,6 @@ pprof = { default-features = false, features = ["criterion", "flamegrap rstest = { workspace = true } tempfile = { version = "3.19" } winter-air = { version = "0.13" } +# for HashFunction/ExecutionProof::new_dummy +color-eyre = { version = "0.5" } +miden-air = { features = ["std", "testing"], version = "0.18" } diff --git a/crates/miden-objects/src/account/auth.rs b/crates/miden-objects/src/account/auth.rs index 93432e8bde..4a67408f89 100644 --- a/crates/miden-objects/src/account/auth.rs +++ b/crates/miden-objects/src/account/auth.rs @@ -150,7 +150,7 @@ fn prepare_rpo_falcon512_signature(sig: &rpo_falcon512::Signature) -> Vec let s2 = sig.sig_poly(); // We also need in the VM the expanded key corresponding to the public key that was provided // via the operand stack - let h = sig.pk_poly(); + let h = sig.public_key(); // Lastly, for the probabilistic product routine that is part of the verification procedure, // we need to compute the product of the expanded key and the signature polynomial in // the ring of polynomials with coefficients in the Miden field. diff --git a/crates/miden-objects/src/account/builder/mod.rs b/crates/miden-objects/src/account/builder/mod.rs index eb285576c7..e5d19206be 100644 --- a/crates/miden-objects/src/account/builder/mod.rs +++ b/crates/miden-objects/src/account/builder/mod.rs @@ -288,6 +288,7 @@ mod tests { use assert_matches::assert_matches; use miden_assembly::{Assembler, Library}; use miden_core::FieldElement; + use miden_processor::MastNodeExt; use super::*; use crate::account::StorageSlot; diff --git a/crates/miden-objects/src/account/code/procedure.rs b/crates/miden-objects/src/account/code/procedure.rs index 7804501184..8281bc532e 100644 --- a/crates/miden-objects/src/account/code/procedure.rs +++ b/crates/miden-objects/src/account/code/procedure.rs @@ -3,7 +3,7 @@ use alloc::sync::Arc; use miden_core::mast::MastForest; use miden_core::prettier::PrettyPrint; -use miden_processor::{MastNode, MastNodeId}; +use miden_processor::{MastNode, MastNodeExt, MastNodeId}; use super::Felt; use crate::utils::serde::{ diff --git a/crates/miden-objects/src/account/component/mod.rs b/crates/miden-objects/src/account/component/mod.rs index ea3287e503..a2bae654b8 100644 --- a/crates/miden-objects/src/account/component/mod.rs +++ b/crates/miden-objects/src/account/component/mod.rs @@ -4,7 +4,7 @@ use alloc::vec::Vec; use miden_assembly::ast::QualifiedProcedureName; use miden_assembly::{Assembler, Library, Parse}; use miden_core::utils::Deserializable; -use miden_mast_package::Package; +use miden_mast_package::{Package, SectionId}; use miden_processor::MastForest; mod template; @@ -22,21 +22,27 @@ impl TryFrom for AccountComponentTemplate { fn try_from(package: Package) -> Result { let library = package.unwrap_library().as_ref().clone(); - // Extract metadata - require explicit account component metadata - let metadata = match package.account_component_metadata_bytes.as_deref() { - Some(metadata_bytes) => AccountComponentMetadata::read_from_bytes(metadata_bytes) - .map_err(|err| { - AccountError::other_with_source( - "failed to deserialize account component metadata", - err, - ) - })?, - None => { - return Err(AccountError::other( - "package does not contain account component metadata - packages without explicit metadata may be intended for other purposes (e.g., note scripts, transaction scripts)", - )); - }, - }; + // Look for account component metadata in sections + let metadata = package + .sections + .iter() + .find_map(|section| { + (section.id == SectionId::ACCOUNT_COMPONENT_METADATA).then(|| { + AccountComponentMetadata::read_from_bytes(§ion.data) + .map_err(|err| { + AccountError::other_with_source( + "failed to deserialize account component metadata", + err, + ) + }) + }) + }) + .transpose()? + .ok_or_else(|| { + AccountError::other( + "package does not contain account component metadata section - packages without explicit metadata may be intended for other purposes (e.g., note scripts, transaction scripts)", + ) + })?; Ok(AccountComponentTemplate::new(metadata, library)) } @@ -270,7 +276,7 @@ mod tests { use miden_assembly::Assembler; use miden_core::utils::Serializable; - use miden_mast_package::{MastArtifact, Package, PackageManifest}; + use miden_mast_package::{MastArtifact, Package, PackageManifest, Section}; use semver::Version; use super::*; @@ -296,7 +302,13 @@ mod tests { name: "test_package".to_string(), mast: MastArtifact::Library(Arc::new(library.clone())), manifest: PackageManifest::new(None), - account_component_metadata_bytes: Some(metadata_bytes), + + sections: vec![Section::new( + SectionId::ACCOUNT_COMPONENT_METADATA, + metadata_bytes.clone(), + )], + version: Default::default(), + description: None, }; let template = AccountComponentTemplate::try_from(package_with_metadata).unwrap(); @@ -313,7 +325,9 @@ mod tests { name: "test_package_no_metadata".to_string(), mast: MastArtifact::Library(Arc::new(library)), manifest: PackageManifest::new(None), - account_component_metadata_bytes: None, + sections: vec![], // No metadata section + version: Default::default(), + description: None, }; let result = AccountComponentTemplate::try_from(package_without_metadata); @@ -340,15 +354,17 @@ mod tests { ) .unwrap(); - // Serialize the metadata - let metadata_bytes = metadata.to_bytes(); - // Create a package with metadata let package = Package { name: "test_package_init_data".to_string(), mast: MastArtifact::Library(Arc::new(library.clone())), manifest: PackageManifest::new(None), - account_component_metadata_bytes: Some(metadata_bytes), + sections: vec![Section::new( + SectionId::ACCOUNT_COMPONENT_METADATA, + metadata.to_bytes(), + )], + version: Default::default(), + description: None, }; // Test with empty init data - this tests the complete workflow: @@ -368,7 +384,9 @@ mod tests { name: "test_package_no_metadata".to_string(), mast: MastArtifact::Library(Arc::new(library)), manifest: PackageManifest::new(None), - account_component_metadata_bytes: None, + sections: vec![], // No metadata section + version: Default::default(), + description: None, }; let result = diff --git a/crates/miden-objects/src/account/component/template/mod.rs b/crates/miden-objects/src/account/component/template/mod.rs index 842b83acd2..01c32dde7a 100644 --- a/crates/miden-objects/src/account/component/template/mod.rs +++ b/crates/miden-objects/src/account/component/template/mod.rs @@ -476,6 +476,8 @@ mod tests { #[test] pub fn fail_duplicate_key_instance() { + let _ = color_eyre::install(); + let toml_text = r#" name = "Test Component" description = "This is a test component" diff --git a/crates/miden-objects/src/account/component/template/storage/entry_content.rs b/crates/miden-objects/src/account/component/template/storage/entry_content.rs index 6575ad803e..221c7d59ee 100644 --- a/crates/miden-objects/src/account/component/template/storage/entry_content.rs +++ b/crates/miden-objects/src/account/component/template/storage/entry_content.rs @@ -558,7 +558,7 @@ impl MapRepresentation { /// Validates map keys by checking for duplicates. /// /// Because keys can be represented in a variety of ways, the `to_string()` implementation is - /// used to check for duplicates. + /// used to check for duplicates. pub(crate) fn validate(&self) -> Result<(), AccountComponentTemplateError> { let mut seen_keys = BTreeSet::new(); for entry in self.entries() { diff --git a/crates/miden-objects/src/account/component/template/storage/placeholder.rs b/crates/miden-objects/src/account/component/template/storage/placeholder.rs index 548a2d9395..f6927421f2 100644 --- a/crates/miden-objects/src/account/component/template/storage/placeholder.rs +++ b/crates/miden-objects/src/account/component/template/storage/placeholder.rs @@ -7,7 +7,6 @@ use core::fmt::{self, Display}; use miden_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; use miden_core::{Felt, Word}; use miden_crypto::dsa::rpo_falcon512::{self}; -use miden_crypto::word::parse_hex_string_as_word; use miden_processor::DeserializationError; use thiserror::Error; @@ -42,7 +41,7 @@ pub static TEMPLATE_REGISTRY: LazyLock = LazyLock::new(|| { /// templated elements. /// /// At component instantiation, a map of names to values must be provided to dynamically -/// replace these placeholders with the instance’s actual values. +/// replace these placeholders with the instance's actual values. #[derive(Clone, Debug, Ord, PartialOrd, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(::serde::Deserialize, ::serde::Serialize))] #[cfg_attr(feature = "std", serde(transparent))] @@ -56,7 +55,7 @@ impl StorageValueName { /// A [`StorageValueName`] serves as an identifier for storage values that are determined at /// instantiation time of an [AccountComponentTemplate](super::super::AccountComponentTemplate). /// - /// The key can consist of one or more segments separated by dots (`.`). + /// The key can consist of one or more segments separated by dots (`.`). /// Each segment must be non-empty and may contain only alphanumeric characters, underscores /// (`_`), or hyphens (`-`). /// @@ -374,20 +373,35 @@ impl TemplateFelt for TokenSymbol { #[error("error parsing word: {0}")] struct WordParseError(String); +/// Pads a hex string to 64 characters (excluding the 0x prefix). +/// +/// If the input starts with "0x" and has fewer than 64 hex characters after the prefix, +/// it will be left-padded with zeros. Otherwise, returns the input unchanged. +fn pad_hex_string(input: &str) -> String { + if input.starts_with("0x") && input.len() < 66 { + // 66 = "0x" + 64 hex chars + let hex_part = &input[2..]; + let padding = "0".repeat(64 - hex_part.len()); + format!("0x{}{}", padding, hex_part) + } else { + input.to_string() + } +} + impl TemplateWord for Word { fn type_name() -> TemplateType { TemplateType::native_word() } fn parse_word(input: &str) -> Result { - parse_hex_string_as_word(input) - .map_err(|err| { - TemplateTypeError::parse( - Self::type_name().as_str(), - Self::type_name(), - WordParseError(err.into()), - ) - }) - .map(Word::from) + let padded_input = pad_hex_string(input); + + Word::try_from(padded_input.as_str()).map_err(|err| { + TemplateTypeError::parse( + input.to_string(), // Use original input in error + Self::type_name(), + WordParseError(err.to_string()), + ) + }) } } @@ -396,15 +410,15 @@ impl TemplateWord for rpo_falcon512::PublicKey { TemplateType::new("auth::rpo_falcon512::pub_key").expect("type is well formed") } fn parse_word(input: &str) -> Result { - parse_hex_string_as_word(input) - .map_err(|err| { - TemplateTypeError::parse( - input.to_string(), - Self::type_name(), - WordParseError(err.into()), - ) - }) - .map(Word::from) + let padded_input = pad_hex_string(input); + + Word::try_from(padded_input.as_str()).map_err(|err| { + TemplateTypeError::parse( + input.to_string(), // Use original input in error + Self::type_name(), + WordParseError(err.to_string()), + ) + }) } } diff --git a/crates/miden-objects/src/account/component/template/storage/toml.rs b/crates/miden-objects/src/account/component/template/storage/toml.rs index 8153ab4fdd..28cbd6cfe7 100644 --- a/crates/miden-objects/src/account/component/template/storage/toml.rs +++ b/crates/miden-objects/src/account/component/template/storage/toml.rs @@ -3,7 +3,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::fmt; -use miden_core::Felt; +use miden_core::{Felt, Word}; use serde::de::value::MapAccessDeserializer; use serde::de::{self, Error, MapAccess, SeqAccess, Visitor}; use serde::ser::{SerializeMap, SerializeStruct}; @@ -25,7 +25,6 @@ use crate::account::component::FieldIdentifier; use crate::account::component::template::storage::placeholder::{TEMPLATE_REGISTRY, TemplateFelt}; use crate::account::{AccountComponentMetadata, StorageValueName}; use crate::errors::AccountComponentTemplateError; -use crate::utils::parse_hex_string_as_word; // ACCOUNT COMPONENT METADATA TOML FROM/TO // ================================================================================================ @@ -105,13 +104,13 @@ impl<'de> Deserialize<'de> for WordRepresentation { where E: Error, { - let parsed_value = parse_hex_string_as_word(value).map_err(|_err| { + let parsed_value = Word::parse(value).map_err(|_err| { E::invalid_value( serde::de::Unexpected::Str(value), &"a valid hexadecimal string", ) })?; - Ok(parsed_value.into()) + Ok(<[Felt; _]>::from(&parsed_value).into()) } fn visit_string(self, value: String) -> Result diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index 5dc225c769..6a2aba0891 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -647,7 +647,7 @@ mod tests { let updated_map = StorageMapDelta::from_iters([], [(new_map_entry.0, new_map_entry.1.into())]); - storage_map.insert(new_map_entry.0, new_map_entry.1.into()); + storage_map.insert(new_map_entry.0, new_map_entry.1.into()).unwrap(); // build account delta let final_nonce = Felt::new(2); diff --git a/crates/miden-objects/src/account/storage/map/mod.rs b/crates/miden-objects/src/account/storage/map/mod.rs index b757c94792..a9b79cacc1 100644 --- a/crates/miden-objects/src/account/storage/map/mod.rs +++ b/crates/miden-objects/src/account/storage/map/mod.rs @@ -7,7 +7,7 @@ use super::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serial use crate::account::StorageMapDelta; use crate::crypto::merkle::{InnerNodeInfo, LeafIndex, SMT_DEPTH, Smt, SmtLeaf}; use crate::errors::StorageMapError; -use crate::{Felt, Hasher}; +use crate::{AccountError, Felt, Hasher}; mod partial; pub use partial::PartialStorageMap; @@ -175,7 +175,7 @@ impl StorageMap { /// [`Self::EMPTY_VALUE`] if no entry was previously present. /// /// If the provided `value` is [`Self::EMPTY_VALUE`] the entry will be removed. - pub fn insert(&mut self, raw_key: Word, value: Word) -> Word { + pub fn insert(&mut self, raw_key: Word, value: Word) -> Result { if value == EMPTY_WORD { self.entries.remove(&raw_key); } else { @@ -183,17 +183,19 @@ impl StorageMap { } let hashed_key = Self::hash_key(raw_key); - self.smt.insert(hashed_key, value) + self.smt + .insert(hashed_key, value) + .map_err(AccountError::MaxNumStorageMapLeavesExceeded) } /// Applies the provided delta to this account storage. - pub fn apply_delta(&mut self, delta: &StorageMapDelta) -> Word { + pub fn apply_delta(&mut self, delta: &StorageMapDelta) -> Result { // apply the updated and cleared leaves to the storage map for (&key, &value) in delta.entries().iter() { - self.insert(key.into_inner(), value); + self.insert(key.into_inner(), value)?; } - self.root() + Ok(self.root()) } /// Consumes the map and returns the underlying map of entries. diff --git a/crates/miden-objects/src/account/storage/mod.rs b/crates/miden-objects/src/account/storage/mod.rs index edf703d681..c6e2584f80 100644 --- a/crates/miden-objects/src/account/storage/mod.rs +++ b/crates/miden-objects/src/account/storage/mod.rs @@ -185,7 +185,7 @@ impl AccountStorage { _ => return Err(AccountError::StorageSlotNotMap(idx)), }; - storage_map.apply_delta(map); + storage_map.apply_delta(map)?; } // update storage values @@ -260,7 +260,7 @@ impl AccountStorage { let old_root = storage_map.root(); // update the key-value pair in the map - let old_value = storage_map.insert(key, value); + let old_value = storage_map.insert(key, value)?; Ok((old_root, old_value)) } diff --git a/crates/miden-objects/src/account/storage/partial.rs b/crates/miden-objects/src/account/storage/partial.rs index 87cdee0884..cab4281fa4 100644 --- a/crates/miden-objects/src/account/storage/partial.rs +++ b/crates/miden-objects/src/account/storage/partial.rs @@ -147,8 +147,8 @@ mod tests { let map_key_absent: Word = [9u64, 12, 18, 3].try_into()?; let mut map_1 = StorageMap::new(); - map_1.insert(map_key_absent, Word::try_from([1u64, 2, 3, 2])?); - map_1.insert(map_key_present, Word::try_from([5u64, 4, 3, 2])?); + map_1.insert(map_key_absent, Word::try_from([1u64, 2, 3, 2])?).unwrap(); + map_1.insert(map_key_present, Word::try_from([5u64, 4, 3, 2])?).unwrap(); assert_eq!(map_1.get(&map_key_present), [5u64, 4, 3, 2].try_into()?); let storage = AccountStorage::new(vec![StorageSlot::Map(map_1.clone())]).unwrap(); diff --git a/crates/miden-objects/src/asset/mod.rs b/crates/miden-objects/src/asset/mod.rs index 482e2164ab..9fdd803870 100644 --- a/crates/miden-objects/src/asset/mod.rs +++ b/crates/miden-objects/src/asset/mod.rs @@ -198,13 +198,14 @@ impl TryFrom for Asset { fn try_from(value: Word) -> Result { // Return an error if element 3 is not a valid account ID prefix, which cannot be checked by // is_not_a_non_fungible_asset. - AccountIdPrefix::try_from(value[3]) + // Keep in mind serialized assets do _not_ carry the suffix required to reconstruct the full + // account identifier. + let prefix = AccountIdPrefix::try_from(value[3]) .map_err(|err| AssetError::InvalidFaucetAccountId(Box::from(err)))?; - - if is_not_a_non_fungible_asset(value) { - FungibleAsset::try_from(value).map(Asset::from) - } else { - NonFungibleAsset::try_from(value).map(Asset::from) + match prefix.account_type() { + AccountType::FungibleFaucet => FungibleAsset::try_from(value).map(Asset::from), + AccountType::NonFungibleFaucet => NonFungibleAsset::try_from(value).map(Asset::from), + _ => Err(AssetError::InvalidFaucetAccountIdPrefix(prefix)), } } } diff --git a/crates/miden-objects/src/asset/nonfungible.rs b/crates/miden-objects/src/asset/nonfungible.rs index aaeb8a3f7f..65d3b6f7e4 100644 --- a/crates/miden-objects/src/asset/nonfungible.rs +++ b/crates/miden-objects/src/asset/nonfungible.rs @@ -7,8 +7,8 @@ use super::{AccountIdPrefix, AccountType, Asset, AssetError, Felt, Hasher, Word} use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use crate::{FieldElement, WORD_SIZE}; -/// Position of the faucet_id inside the [`NonFungibleAsset`] word. -const FAUCET_ID_POS: usize = 3; +/// Position of the faucet_id inside the [`NonFungibleAsset`] word having fields in BigEndian. +const FAUCET_ID_POS_BE: usize = 3; // NON-FUNGIBLE ASSET // ================================================================================================ @@ -74,7 +74,7 @@ impl NonFungibleAsset { return Err(AssetError::NonFungibleFaucetIdTypeMismatch(faucet_id)); } - data_hash[FAUCET_ID_POS] = Felt::from(faucet_id); + data_hash[FAUCET_ID_POS_BE] = Felt::from(faucet_id); Ok(Self(data_hash)) } @@ -110,7 +110,7 @@ impl NonFungibleAsset { let mut vault_key = self.0; // Swap prefix of faucet ID with hash0. - vault_key.swap(0, FAUCET_ID_POS); + vault_key.swap(0, FAUCET_ID_POS_BE); // Set the fungible bit to zero. vault_key[3] = @@ -121,7 +121,7 @@ impl NonFungibleAsset { /// Return ID prefix of the faucet which issued this asset. pub fn faucet_id_prefix(&self) -> AccountIdPrefix { - AccountIdPrefix::new_unchecked(self.0[FAUCET_ID_POS]) + AccountIdPrefix::new_unchecked(self.0[FAUCET_ID_POS_BE]) } // HELPER FUNCTIONS @@ -133,7 +133,7 @@ impl NonFungibleAsset { /// - The faucet_id is not a valid non-fungible faucet ID. /// - The most significant bit of the asset is not ZERO. fn validate(&self) -> Result<(), AssetError> { - let faucet_id = AccountIdPrefix::try_from(self.0[FAUCET_ID_POS]) + let faucet_id = AccountIdPrefix::try_from(self.0[FAUCET_ID_POS_BE]) .map_err(|err| AssetError::InvalidFaucetAccountId(Box::new(err)))?; let account_type = faucet_id.account_type(); diff --git a/crates/miden-objects/src/asset/vault/mod.rs b/crates/miden-objects/src/asset/vault/mod.rs index 1718029838..5f421704e2 100644 --- a/crates/miden-objects/src/asset/vault/mod.rs +++ b/crates/miden-objects/src/asset/vault/mod.rs @@ -157,6 +157,7 @@ impl AssetVault { /// the vault. /// - If the delta contains a non-fungible asset removal that is not stored in the vault. /// - If the delta contains a non-fungible asset addition that is already stored in the vault. + /// - The maximum number of leaves per asset is exceeded. pub fn apply_delta(&mut self, delta: &AccountVaultDelta) -> Result<(), AssetVaultError> { for (&faucet_id, &delta) in delta.fungible().iter() { let asset = FungibleAsset::new(faucet_id, delta.unsigned_abs()) @@ -184,6 +185,7 @@ impl AssetVault { /// # Errors /// - If the total value of two fungible assets is greater than or equal to 2^63. /// - If the vault already contains the same non-fungible asset. + /// - The maximum number of leaves per asset is exceeded. pub fn add_asset(&mut self, asset: Asset) -> Result { Ok(match asset { Asset::Fungible(asset) => Asset::Fungible(self.add_fungible_asset(asset)?), @@ -196,6 +198,7 @@ impl AssetVault { /// /// # Errors /// - If the total value of assets is greater than or equal to 2^63. + /// - The maximum number of leaves per asset is exceeded. fn add_fungible_asset( &mut self, asset: FungibleAsset, @@ -208,7 +211,9 @@ impl AssetVault { current.add(asset).map_err(AssetVaultError::AddFungibleAssetBalanceError)? }, }; - self.asset_tree.insert(new.vault_key(), new.into()); + self.asset_tree + .insert(new.vault_key(), new.into()) + .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return the new asset Ok(new) @@ -218,12 +223,16 @@ impl AssetVault { /// /// # Errors /// - If the vault already contains the same non-fungible asset. + /// - The maximum number of leaves per asset is exceeded. fn add_non_fungible_asset( &mut self, asset: NonFungibleAsset, ) -> Result { // add non-fungible asset to the vault - let old = self.asset_tree.insert(asset.vault_key(), asset.into()); + let old = self + .asset_tree + .insert(asset.vault_key(), asset.into()) + .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // if the asset already exists, return an error if old != Smt::EMPTY_VALUE { @@ -260,6 +269,7 @@ impl AssetVault { /// # Errors /// - The asset is not found in the vault. /// - The amount of the asset in the vault is less than the amount to be removed. + /// - The maximum number of leaves per asset is exceeded. fn remove_fungible_asset( &mut self, asset: FungibleAsset, @@ -280,7 +290,9 @@ impl AssetVault { 0 => Smt::EMPTY_VALUE, _ => new.into(), }; - self.asset_tree.insert(new.vault_key(), value); + self.asset_tree + .insert(new.vault_key(), value) + .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return the asset that was removed. Ok(asset) @@ -291,12 +303,16 @@ impl AssetVault { /// /// # Errors /// - The non-fungible asset is not found in the vault. + /// - The maximum number of leaves per asset is exceeded. fn remove_non_fungible_asset( &mut self, asset: NonFungibleAsset, ) -> Result { // remove the asset from the vault. - let old = self.asset_tree.insert(asset.vault_key(), Smt::EMPTY_VALUE); + let old = self + .asset_tree + .insert(asset.vault_key(), Smt::EMPTY_VALUE) + .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return an error if the asset did not exist in the vault. if old == Smt::EMPTY_VALUE { diff --git a/crates/miden-objects/src/batch/proposed_batch.rs b/crates/miden-objects/src/batch/proposed_batch.rs index 52c816892a..d24364ef43 100644 --- a/crates/miden-objects/src/batch/proposed_batch.rs +++ b/crates/miden-objects/src/batch/proposed_batch.rs @@ -427,7 +427,6 @@ mod tests { use anyhow::Context; use miden_crypto::merkle::{Mmr, PartialMmr}; use miden_verifier::ExecutionProof; - use winter_air::proof::Proof; use winter_rand_utils::rand_value; use super::*; @@ -473,7 +472,7 @@ mod tests { let block_num = reference_block_header.block_num(); let block_ref = reference_block_header.commitment(); let expiration_block_num = reference_block_header.block_num() + 1; - let proof = ExecutionProof::new(Proof::new_dummy(), Default::default()); + let proof = ExecutionProof::new_dummy(); let tx = ProvenTransactionBuilder::new( account_id, diff --git a/crates/miden-objects/src/block/account_tree.rs b/crates/miden-objects/src/block/account_tree.rs index b6ab7481d9..1427d13f5f 100644 --- a/crates/miden-objects/src/block/account_tree.rs +++ b/crates/miden-objects/src/block/account_tree.rs @@ -184,11 +184,14 @@ impl AccountTree { &self, account_commitments: impl IntoIterator, ) -> Result { - let mutation_set = self.smt.compute_mutations( - account_commitments - .into_iter() - .map(|(id, commitment)| (Self::id_to_smt_key(id), commitment)), - ); + let mutation_set = self + .smt + .compute_mutations( + account_commitments + .into_iter() + .map(|(id, commitment)| (Self::id_to_smt_key(id), commitment)), + ) + .map_err(AccountTreeError::ComputeMutations)?; for id_key in mutation_set.new_pairs().keys() { // Check if the insertion would be valid. @@ -234,7 +237,10 @@ impl AccountTree { state_commitment: Word, ) -> Result { let key = Self::id_to_smt_key(account_id); - let prev_value = self.smt.insert(key, state_commitment); + // SAFETY: account tree should not contain multi-entry leaves and so the maximum number + // of entries per leaf should never be exceeded. + let prev_value = self.smt.insert(key, state_commitment) + .expect("account tree should always have a single value per key, and hence cannot exceed the maximum leaf number"); // If the leaf of the account ID now has two or more entries, we've inserted a duplicate // prefix. diff --git a/crates/miden-objects/src/block/account_witness.rs b/crates/miden-objects/src/block/account_witness.rs index 5f7d8e3c16..5be295e653 100644 --- a/crates/miden-objects/src/block/account_witness.rs +++ b/crates/miden-objects/src/block/account_witness.rs @@ -8,6 +8,7 @@ use miden_crypto::merkle::{ SmtLeaf, SmtProof, SmtProofError, + SparseMerklePath, }; use crate::account::AccountId; @@ -104,7 +105,7 @@ impl AccountWitness { // the account trees. debug_assert_eq!(proof.path().depth(), AccountTree::DEPTH); - AccountWitness::new_unchecked(witness_id, commitment, proof.into_parts().0) + AccountWitness::new_unchecked(witness_id, commitment, proof.into_parts().0.into()) } /// Constructs a new [`AccountWitness`] from the provided parts. @@ -145,8 +146,14 @@ impl AccountWitness { /// Consumes self and returns the inner proof. pub fn into_proof(self) -> SmtProof { let leaf = self.leaf(); - SmtProof::new(self.path, leaf) - .expect("merkle path depth should be the SMT depth by construction") + debug_assert_eq!(self.path.depth(), AccountTree::DEPTH); + SmtProof::new( + SparseMerklePath::try_from(self.path).expect( + "only ever exists for merkle paths that match the SMT depth by construction", + ), + leaf, + ) + .expect("merkle path depth should be the SMT depth by construction") } /// Returns an iterator over every inner node of this witness' merkle path. diff --git a/crates/miden-objects/src/block/nullifier_tree.rs b/crates/miden-objects/src/block/nullifier_tree.rs index d678f8f288..ce99372208 100644 --- a/crates/miden-objects/src/block/nullifier_tree.rs +++ b/crates/miden-objects/src/block/nullifier_tree.rs @@ -123,10 +123,12 @@ impl NullifierTree { } } - let mutation_set = - self.smt.compute_mutations(nullifiers.into_iter().map(|(nullifier, block_num)| { + let mutation_set = self + .smt + .compute_mutations(nullifiers.into_iter().map(|(nullifier, block_num)| { (nullifier.as_word(), Self::block_num_to_leaf_value(block_num)) - })); + })) + .map_err(NullifierTreeError::ComputeMutations)?; Ok(NullifierMutationSet::new(mutation_set)) } @@ -145,8 +147,10 @@ impl NullifierTree { nullifier: Nullifier, block_num: BlockNumber, ) -> Result<(), NullifierTreeError> { - let prev_nullifier_value = - self.smt.insert(nullifier.as_word(), Self::block_num_to_leaf_value(block_num)); + let prev_nullifier_value = self + .smt + .insert(nullifier.as_word(), Self::block_num_to_leaf_value(block_num)) + .map_err(NullifierTreeError::MaxLeafEntriesExceeded)?; if prev_nullifier_value != Self::UNSPENT_NULLIFIER { Err(NullifierTreeError::NullifierAlreadySpent(nullifier)) diff --git a/crates/miden-objects/src/block/partial_account_tree.rs b/crates/miden-objects/src/block/partial_account_tree.rs index 44e09c43b5..b11db52277 100644 --- a/crates/miden-objects/src/block/partial_account_tree.rs +++ b/crates/miden-objects/src/block/partial_account_tree.rs @@ -270,10 +270,16 @@ mod tests { let proof1 = full_tree.open(&key1); assert_eq!(proof0.leaf(), proof1.leaf()); - let witness0 = - AccountWitness::new_unchecked(id0, proof0.get(&key0).unwrap(), proof0.into_parts().0); - let witness1 = - AccountWitness::new_unchecked(id1, proof1.get(&key1).unwrap(), proof1.into_parts().0); + let witness0 = AccountWitness::new_unchecked( + id0, + proof0.get(&key0).unwrap(), + proof0.into_parts().0.into(), + ); + let witness1 = AccountWitness::new_unchecked( + id1, + proof1.get(&key1).unwrap(), + proof1.into_parts().0.into(), + ); let mut partial_tree = PartialAccountTree::new(); partial_tree.track_account(witness0).unwrap(); diff --git a/crates/miden-objects/src/block/partial_nullifier_tree.rs b/crates/miden-objects/src/block/partial_nullifier_tree.rs index 37f1717299..9a9c2ab04a 100644 --- a/crates/miden-objects/src/block/partial_nullifier_tree.rs +++ b/crates/miden-objects/src/block/partial_nullifier_tree.rs @@ -132,8 +132,8 @@ mod tests { let mut full = Smt::with_entries(kv_pairs).unwrap(); let stale_proof0 = full.open(&key0); // Insert a non-empty value so the nullifier tree's root changes. - full.insert(key1, value1); - full.insert(key2, value2); + full.insert(key1, value1).unwrap(); + full.insert(key2, value2).unwrap(); let proof2 = full.open(&key2); assert_ne!(stale_proof0.compute_root(), proof2.compute_root()); diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index 6be24aff22..bfb5cbc5f2 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -162,6 +162,8 @@ pub enum AccountError { account_type: AccountType, component_index: usize, }, + #[error("maximum number of storage map leaves exceeded")] + MaxNumStorageMapLeavesExceeded(#[source] MerkleError), /// This variant can be used by methods that are not inherent to the account but want to return /// this error type. #[error("{error_msg}")] @@ -253,6 +255,8 @@ pub enum AccountTreeError { TreeRootConflict(#[source] MerkleError), #[error("failed to apply mutations to account tree")] ApplyMutations(#[source] MerkleError), + #[error("failed to compute account tree mutations")] + ComputeMutations(#[source] MerkleError), #[error("smt leaf's index is not a valid account ID prefix")] InvalidAccountIdPrefix(#[source] AccountIdError), #[error("account witness merkle path depth {0} does not match AccountTree::DEPTH")] @@ -402,6 +406,8 @@ pub enum AssetError { }, #[error("faucet account ID in asset is invalid")] InvalidFaucetAccountId(#[source] Box), + #[error("faucet account ID in asset has a non-faucet prefix: {}", .0)] + InvalidFaucetAccountIdPrefix(AccountIdPrefix), #[error( "faucet id {0} of type {id_type} must be of type {expected_ty} for fungible assets", id_type = .0.account_type(), @@ -452,6 +458,8 @@ pub enum AssetVaultError { NonFungibleAssetNotFound(NonFungibleAsset), #[error("subtracting fungible asset amounts would underflow")] SubtractFungibleAssetBalanceError(#[source] AssetError), + #[error("maximum number of asset vault leaves exceeded")] + MaxLeafEntriesExceeded(#[source] MerkleError), } // PARTIAL ASSET VAULT ERROR @@ -986,6 +994,9 @@ pub enum NullifierTreeError { #[error("attempt to mark nullifier {0} as spent but it is already spent")] NullifierAlreadySpent(Nullifier), + #[error("maximum number of nullifier tree leaves exceeded")] + MaxLeafEntriesExceeded(#[source] MerkleError), + #[error("nullifier {nullifier} is not tracked by the partial nullifier tree")] UntrackedNullifier { nullifier: Nullifier, @@ -994,4 +1005,7 @@ pub enum NullifierTreeError { #[error("new tree root after nullifier witness insertion does not match previous tree root")] TreeRootConflict(#[source] MerkleError), + + #[error("failed to compute nulifier tree mutations")] + ComputeMutations(#[source] MerkleError), } diff --git a/crates/miden-objects/src/lib.rs b/crates/miden-objects/src/lib.rs index b455fa6159..1eaa30338c 100644 --- a/crates/miden-objects/src/lib.rs +++ b/crates/miden-objects/src/lib.rs @@ -84,8 +84,7 @@ pub mod crypto { pub mod utils { pub use miden_core::utils::*; - pub use miden_crypto::utils::{HexParseError, bytes_to_hex_string, collections, hex_to_bytes}; - pub use miden_crypto::word::parse_hex_string_as_word; + pub use miden_crypto::utils::{HexParseError, bytes_to_hex_string, hex_to_bytes}; pub use miden_utils_sync as sync; pub mod serde { diff --git a/crates/miden-objects/src/note/script.rs b/crates/miden-objects/src/note/script.rs index 67ef2cd177..39c5b12537 100644 --- a/crates/miden-objects/src/note/script.rs +++ b/crates/miden-objects/src/note/script.rs @@ -2,6 +2,8 @@ use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::Display; +use miden_processor::MastNodeExt; + use super::Felt; use crate::assembly::mast::{MastForest, MastNodeId}; use crate::utils::serde::{ diff --git a/crates/miden-objects/src/testing/account_code.rs b/crates/miden-objects/src/testing/account_code.rs index 119d378c86..e8ddbbdbb9 100644 --- a/crates/miden-objects/src/testing/account_code.rs +++ b/crates/miden-objects/src/testing/account_code.rs @@ -8,11 +8,11 @@ use crate::testing::noop_auth_component::NoopAuthComponent; pub const CODE: &str = " export.foo - push.1 push.2 mul + push.1.2 mul end export.bar - push.1 push.2 add + push.1.2 add end "; diff --git a/crates/miden-objects/src/testing/storage.rs b/crates/miden-objects/src/testing/storage.rs index 20e821fd08..85f8f673f5 100644 --- a/crates/miden-objects/src/testing/storage.rs +++ b/crates/miden-objects/src/testing/storage.rs @@ -139,7 +139,7 @@ impl AccountStorage { pub fn prepare_assets(note_assets: &NoteAssets) -> Vec { let mut assets = Vec::new(); for &asset in note_assets.iter() { - let asset_word: Word = asset.into(); + let asset_word = Word::from(asset); assets.push(asset_word.to_string()); } assets diff --git a/crates/miden-objects/src/transaction/inputs/account.rs b/crates/miden-objects/src/transaction/inputs/account.rs index f06cdd3ab4..d1c09e831b 100644 --- a/crates/miden-objects/src/transaction/inputs/account.rs +++ b/crates/miden-objects/src/transaction/inputs/account.rs @@ -1,3 +1,5 @@ +use miden_crypto::merkle::SparseMerklePath; + use crate::Word; use crate::account::{AccountCode, AccountId, PartialAccount, PartialStorage}; use crate::asset::PartialVault; @@ -66,6 +68,8 @@ impl AccountInputs { /// This root should be equal to the account root in the reference block header. pub fn compute_account_root(&self) -> Result { let smt_merkle_path = self.witness.path().clone(); + let smt_merkle_path = SparseMerklePath::try_from(smt_merkle_path) + .expect("Only ever exists for merkle paths that match the SMT depth by construction"); let smt_leaf = self.witness.leaf(); let root = SmtProof::new(smt_merkle_path, smt_leaf)?.compute_root(); diff --git a/crates/miden-objects/src/transaction/proven_tx.rs b/crates/miden-objects/src/transaction/proven_tx.rs index c903255bca..5bfef2d792 100644 --- a/crates/miden-objects/src/transaction/proven_tx.rs +++ b/crates/miden-objects/src/transaction/proven_tx.rs @@ -656,7 +656,6 @@ mod tests { use anyhow::Context; use miden_core::utils::Deserializable; use miden_verifier::ExecutionProof; - use winter_air::proof::Proof; use winter_rand_utils::rand_value; use super::ProvenTransaction; @@ -780,7 +779,7 @@ mod tests { let ref_block_num = BlockNumber::from(1); let ref_block_commitment = Word::empty(); let expiration_block_num = BlockNumber::from(2); - let proof = ExecutionProof::new(Proof::new_dummy(), Default::default()); + let proof = ExecutionProof::new_dummy(); let tx = ProvenTransactionBuilder::new( account_id, diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index 952dbbbb9c..08bd4c220a 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -4,6 +4,7 @@ use alloc::vec::Vec; use miden_crypto::dsa::rpo_falcon512::PublicKey; use miden_crypto::merkle::InnerNodeInfo; +use miden_processor::MastNodeExt; use super::{AccountInputs, Felt, Hasher, Word}; use crate::note::{NoteId, NoteRecipient}; @@ -196,7 +197,7 @@ impl TransactionArgs { pub fn add_signature(&mut self, public_key: PublicKey, message: Word, signature: Vec) { self.advice_inputs .map - .insert(Hasher::merge(&[public_key.into(), message]), signature); + .insert(Hasher::merge(&[public_key.to_commitment(), message]), signature); } /// Populates the advice inputs with the specified note recipient details. diff --git a/crates/miden-testing/src/kernel_tests/batch/proven_tx_builder.rs b/crates/miden-testing/src/kernel_tests/batch/proven_tx_builder.rs index 52b8c7f4ca..3c583dd06a 100644 --- a/crates/miden-testing/src/kernel_tests/batch/proven_tx_builder.rs +++ b/crates/miden-testing/src/kernel_tests/batch/proven_tx_builder.rs @@ -14,7 +14,6 @@ use miden_objects::transaction::{ ProvenTransactionBuilder, }; use miden_objects::vm::ExecutionProof; -use winterfell::Proof; /// A builder to build mocked [`ProvenTransaction`]s. pub struct MockProvenTxBuilder { @@ -112,7 +111,7 @@ impl MockProvenTxBuilder { self.ref_block_commitment.unwrap_or_default(), self.fee, self.expiration_block_num, - ExecutionProof::new(Proof::new_dummy(), Default::default()), + ExecutionProof::new_dummy(), ) .add_input_notes(self.input_notes.unwrap_or_default()) .add_input_notes(self.nullifiers.unwrap_or_default()) diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs index 93e518a889..89833ae77b 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs @@ -15,7 +15,6 @@ use miden_objects::transaction::ProvenTransactionBuilder; use miden_objects::vm::ExecutionProof; use miden_objects::{AccountTreeError, NullifierTreeError, Word}; use miden_tx::LocalTransactionProver; -use winterfell::Proof; use crate::kernel_tests::block::utils::MockChainBlockExt; use crate::{Auth, MockChain, TransactionContextBuilder}; @@ -382,7 +381,7 @@ fn proven_block_fails_on_creating_account_with_duplicate_account_id_prefix() -> genesis_block.commitment(), FungibleAsset::mock(500).unwrap_fungible(), BlockNumber::from(u32::MAX), - ExecutionProof::new(Proof::new_dummy(), Default::default()), + ExecutionProof::new_dummy(), ) .account_update_details(AccountUpdateDetails::Private) .build() diff --git a/crates/miden-testing/src/kernel_tests/tx/mod.rs b/crates/miden-testing/src/kernel_tests/tx/mod.rs index 0a37abbe96..7a98b01242 100644 --- a/crates/miden-testing/src/kernel_tests/tx/mod.rs +++ b/crates/miden-testing/src/kernel_tests/tx/mod.rs @@ -108,19 +108,19 @@ pub fn create_mock_notes_procedure(notes: &[Note]) -> String { " # populate note {idx} push.{metadata} - push.{OUTPUT_NOTE_SECTION_OFFSET}.{note_offset}.{OUTPUT_NOTE_METADATA_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_METADATA_OFFSET} add add mem_storew dropw push.{recipient} - push.{OUTPUT_NOTE_SECTION_OFFSET}.{note_offset}.{OUTPUT_NOTE_RECIPIENT_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_RECIPIENT_OFFSET} add add mem_storew dropw push.{num_assets} - push.{OUTPUT_NOTE_SECTION_OFFSET}.{note_offset}.{OUTPUT_NOTE_NUM_ASSETS_OFFSET} add add mem_store + push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_NUM_ASSETS_OFFSET} add add mem_store push.1 # dirty flag should be `1` by default - push.{OUTPUT_NOTE_SECTION_OFFSET}.{note_offset}.{OUTPUT_NOTE_DIRTY_FLAG_OFFSET} add add mem_store + push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_DIRTY_FLAG_OFFSET} add add mem_store push.{first_asset} - push.{OUTPUT_NOTE_SECTION_OFFSET}.{note_offset}.{OUTPUT_NOTE_ASSETS_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_ASSETS_OFFSET} add add mem_storew dropw ", idx = idx, metadata = metadata, @@ -132,7 +132,7 @@ pub fn create_mock_notes_procedure(notes: &[Note]) -> String { } script.push_str(&format!( "# set num output notes - push.{count}.{NUM_OUTPUT_NOTES_PTR} mem_store + push.{count} push.{NUM_OUTPUT_NOTES_PTR} mem_store end ", count = notes.len(), diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 0023723f44..3fa1cca576 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -42,7 +42,7 @@ use miden_objects::testing::account_id::{ use miden_objects::testing::storage::STORAGE_LEAVES_2; use miden_objects::transaction::{ExecutedTransaction, TransactionScript}; use miden_objects::{LexicographicWord, StarkField}; -use miden_processor::{EMPTY_WORD, ExecutionError, Word}; +use miden_processor::{EMPTY_WORD, ExecutionError, MastNodeExt, Word}; use miden_tx::{LocalTransactionProver, TransactionExecutorError}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; @@ -119,7 +119,7 @@ pub fn compute_current_commitment() -> miette::Result<()> { # => [STORAGE_COMMITMENT1, STORAGE_COMMITMENT0] # assert that the commitment has changed - exec.word::eq + exec.word::eq assertz.err="storage commitment should have been updated by compute_current_commitment" # => [] end @@ -422,8 +422,7 @@ fn test_get_map_item() -> miette::Result<()> { map_key = &key, ); - let process = &tx_context.execute_code(&code)?; - + let process = &mut tx_context.execute_code(&code)?; assert_eq!( value, process.stack.get_word(0), @@ -431,17 +430,17 @@ fn test_get_map_item() -> miette::Result<()> { ); assert_eq!( Word::empty(), - process.stack.get_word(1), + process.stack.get_word(4), "The rest of the stack must be cleared", ); assert_eq!( Word::empty(), - process.stack.get_word(2), + process.stack.get_word(8), "The rest of the stack must be cleared", ); assert_eq!( Word::empty(), - process.stack.get_word(3), + process.stack.get_word(12), "The rest of the stack must be cleared", ); } @@ -580,7 +579,7 @@ fn test_set_map_item() -> miette::Result<()> { let process = &tx_context.execute_code(&code).unwrap(); let mut new_storage_map = AccountStorage::mock_map(); - new_storage_map.insert(new_key, new_value); + new_storage_map.insert(new_key, new_value).unwrap(); assert_eq!( new_storage_map.root(), @@ -589,7 +588,7 @@ fn test_set_map_item() -> miette::Result<()> { ); assert_eq!( storage_item.slot.value(), - process.stack.get_word(1), + process.stack.get_word(4), "The original value stored in the map doesn't match the expected value", ); @@ -625,8 +624,8 @@ fn test_account_component_storage_offset() -> miette::Result<()> { export.foo_read push.0 exec.account::get_item - push.1.2.3.4 - + push.1.2.3.4 + exec.word::eq assert end "; @@ -645,8 +644,8 @@ fn test_account_component_storage_offset() -> miette::Result<()> { export.bar_read push.0 exec.account::get_item - push.5.6.7.8 - + push.5.6.7.8 + exec.word::eq assert end "; @@ -943,7 +942,7 @@ fn test_compute_storage_commitment() -> anyhow::Result<()> { push.{storage_commitment_0} assert_eqw.err="storage commitment after the 0th slot was updated is not equal to the expected one" - # get the storage commitment once more to get the cached data and assert that this data + # get the storage commitment once more to get the cached data and assert that this data # didn't change call.mock_account::compute_storage_commitment push.{storage_commitment_0} @@ -1441,7 +1440,7 @@ fn test_get_map_item_init() -> miette::Result<()> { push.{new_key} push.0 call.mock_account::get_map_item_init - push.0.0.0.0 + padw assert_eqw.err=\"new key should have empty initial value\" dropw dropw diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index 98cc4c43dd..db058ddf1c 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -227,16 +227,16 @@ fn storage_delta_for_map_slots() -> anyhow::Result<()> { let key5_final_value = Word::from([1, 2, 3, 4u32]); let mut map0 = StorageMap::new(); - map0.insert(key0, key0_init_value); - map0.insert(key1, key1_init_value); + map0.insert(key0, key0_init_value).unwrap(); + map0.insert(key1, key1_init_value).unwrap(); let mut map1 = StorageMap::new(); - map1.insert(key2, key2_init_value); - map1.insert(key3, key3_init_value); - map1.insert(key4, key4_init_value); + map1.insert(key2, key2_init_value).unwrap(); + map1.insert(key3, key3_init_value).unwrap(); + map1.insert(key4, key4_init_value).unwrap(); let mut map2 = StorageMap::new(); - map2.insert(key5, key5_init_value); + map2.insert(key5, key5_init_value).unwrap(); let TestSetup { mock_chain, account_id, .. } = setup_test( vec![ @@ -255,47 +255,47 @@ fn storage_delta_for_map_slots() -> anyhow::Result<()> { let tx_script = compile_tx_script(format!( " begin - push.{key0_final_value}.{key0}.0 + push.{key0_final_value} push.{key0} push.0 # => [index, KEY, VALUE] exec.set_map_item # => [] - push.{key1_tmp_value}.{key1}.0 + push.{key1_tmp_value} push.{key1} push.0 # => [index, KEY, VALUE] exec.set_map_item # => [] - push.{key1_final_value}.{key1}.0 + push.{key1_final_value} push.{key1} push.0 # => [index, KEY, VALUE] exec.set_map_item # => [] - push.{key2_final_value}.{key2}.1 + push.{key2_final_value} push.{key2} push.1 # => [index, KEY, VALUE] exec.set_map_item # => [] - push.{key3_final_value}.{key3}.1 + push.{key3_final_value} push.{key3} push.1 # => [index, KEY, VALUE] exec.set_map_item # => [] - push.{key4_tmp_value}.{key4}.1 + push.{key4_tmp_value} push.{key4} push.1 # => [index, KEY, VALUE] exec.set_map_item # => [] - push.{key4_final_value}.{key4}.1 + push.{key4_final_value} push.{key4} push.1 # => [index, KEY, VALUE] exec.set_map_item # => [] - push.{key5_tmp_value}.{key5}.2 + push.{key5_tmp_value} push.{key5} push.2 # => [index, KEY, VALUE] exec.set_map_item # => [] - push.{key5_final_value}.{key5}.2 + push.{key5_final_value} push.{key5} push.2 # => [index, KEY, VALUE] exec.set_map_item # => [] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 8a58eec648..37e97f1284 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -35,7 +35,7 @@ fn get_balance_returns_correct_amount() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - push.{suffix}.{prefix} + push.{suffix} push.{prefix} exec.account::get_balance # => [balance] @@ -74,7 +74,7 @@ fn peek_balance_returns_correct_amount() -> anyhow::Result<()> { exec.prologue::prepare_transaction exec.memory::get_account_vault_root_ptr - push.{suffix}.{prefix} + push.{suffix} push.{prefix} # => [prefix, suffix, account_vault_root_ptr, balance] exec.asset_vault::peek_balance @@ -110,7 +110,7 @@ fn test_get_balance_non_fungible_fails() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - push.{suffix}.{prefix} + push.{suffix} push.{prefix} exec.account::get_balance end ", diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index 05943d2a20..da0f1bc1a2 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -64,16 +64,16 @@ fn test_mint_fungible_asset_succeeds() -> anyhow::Result<()> { begin # mint asset exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} + push.{FUNGIBLE_ASSET_AMOUNT} push.0 push.{suffix} push.{prefix} call.faucet::mint # assert the correct asset is returned - push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} + push.{FUNGIBLE_ASSET_AMOUNT} push.0 push.{suffix} push.{prefix} assert_eqw.err="minted asset does not match expected asset" # assert the input vault has been updated exec.memory::get_input_vault_root_ptr - push.{suffix}.{prefix} + push.{suffix} push.{prefix} exec.asset_vault::get_balance push.{FUNGIBLE_ASSET_AMOUNT} assert_eq.err="input vault should contain minted asset" end @@ -355,17 +355,17 @@ fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { begin # burn asset exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} + push.{FUNGIBLE_ASSET_AMOUNT} push.0 push.{suffix} push.{prefix} call.faucet::burn # assert the correct asset is returned - push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} + push.{FUNGIBLE_ASSET_AMOUNT} push.0 push.{suffix} push.{prefix} assert_eqw.err="burnt asset does not match expected asset" # assert the input vault has been updated exec.memory::get_input_vault_root_ptr - push.{suffix}.{prefix} + push.{suffix} push.{prefix} exec.asset_vault::get_balance push.{final_input_vault_asset_amount} assert_eq.err="vault balance does not match expected balance" @@ -438,7 +438,7 @@ fn test_burn_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} + push.{FUNGIBLE_ASSET_AMOUNT} push.0 push.{suffix} push.{prefix} call.faucet::burn end ", @@ -469,7 +469,7 @@ fn test_burn_fungible_asset_insufficient_input_amount() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - push.{saturating_amount}.0.{suffix}.{prefix} + push.{saturating_amount} push.0 push.{suffix} push.{prefix} call.faucet::burn end ", diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index 559dcaed41..5d3be684e5 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -145,7 +145,7 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { push.{get_item_foreign_hash} # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(11)] exec.tx::execute_foreign_procedure @@ -198,7 +198,7 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { push.{get_map_item_foreign_hash} # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, MAP_ITEM_KEY, pad(10)] exec.tx::execute_foreign_procedure @@ -252,7 +252,7 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { push.{get_item_foreign_hash} # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure dropw @@ -270,7 +270,7 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { push.{get_item_foreign_hash} # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure @@ -412,7 +412,7 @@ fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { push.{get_item_foreign_1_hash} # push the foreign account ID - push.{foreign_1_suffix}.{foreign_1_prefix} + push.{foreign_1_suffix} push.{foreign_1_prefix} # => [foreign_account_1_id_prefix, foreign_account_1_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure dropw @@ -430,7 +430,7 @@ fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { push.{get_item_foreign_2_hash} # push the foreign account ID - push.{foreign_2_suffix}.{foreign_2_prefix} + push.{foreign_2_suffix} push.{foreign_2_prefix} # => [foreign_account_2_id_prefix, foreign_account_2_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure dropw @@ -448,7 +448,7 @@ fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { push.{get_item_foreign_1_hash} # push the foreign account ID - push.{foreign_1_suffix}.{foreign_1_prefix} + push.{foreign_1_suffix} push.{foreign_1_prefix} # => [foreign_account_1_id_prefix, foreign_account_1_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure @@ -584,7 +584,7 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { procref.::foreign_account::get_item_foreign # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure @@ -609,7 +609,7 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { procref.::foreign_account::get_map_item_foreign # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, MAP_ITEM_KEY, pad(10)] exec.tx::execute_foreign_procedure @@ -668,7 +668,7 @@ fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Result<()> export.get_asset_balance # get balance of first asset - push.{fungible_faucet_id_suffix}.{fungible_faucet_id_prefix} + push.{fungible_faucet_id_suffix} push.{fungible_faucet_id_prefix} exec.account::get_balance # => [balance] @@ -733,7 +733,7 @@ fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Result<()> procref.::foreign_account_code::get_asset_balance # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, pad(15)] exec.tx::execute_foreign_procedure @@ -952,7 +952,7 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { procref.::first_foreign_account::first_account_foreign_proc # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure @@ -1060,7 +1060,7 @@ fn test_nested_fpi_stack_overflow() { push.{next_account_proc_hash} # push the foreign account ID - push.{next_foreign_suffix}.{next_foreign_prefix} + push.{next_foreign_suffix} push.{next_foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure @@ -1128,7 +1128,7 @@ fn test_nested_fpi_stack_overflow() { push.{foreign_account_proc_hash} # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure @@ -1229,7 +1229,7 @@ fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { push.{first_account_foreign_proc_hash} # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure @@ -1366,11 +1366,11 @@ fn test_fpi_stale_account() -> anyhow::Result<()> { # => [pad(16)] # push some hash onto the stack - for this test it does not matter - push.0.0.0.0 + padw # => [FOREIGN_PROC_ROOT, pad(16)] # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, pad(16)] exec.tx::execute_foreign_procedure @@ -1448,7 +1448,7 @@ fn test_fpi_get_account_id() -> anyhow::Result<()> { procref.::foreign_account::get_current_and_native_ids # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, pad(15)] exec.tx::execute_foreign_procedure @@ -1456,14 +1456,14 @@ fn test_fpi_get_account_id() -> anyhow::Result<()> { # push the expected native account ID and check that it is equal to the one returned # from the FPI - push.{expected_native_suffix}.{expected_native_prefix} + push.{expected_native_suffix} push.{expected_native_prefix} exec.account_id::is_equal assert.err="native account ID returned from the FPI is not equal to the expected one" # => [acct_id_prefix, acct_id_suffix] # push the expected foreign account ID and check that it is equal to the one returned # from the FPI - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} exec.account_id::is_equal assert.err="foreign account ID returned from the FPI is not equal to the expected one" # => [] @@ -1562,7 +1562,7 @@ fn test_fpi_get_account_nonce() -> anyhow::Result<()> { push.{get_current_and_native_nonce_values} # push the foreign account ID - push.{foreign_suffix}.{foreign_prefix} + push.{foreign_suffix} push.{foreign_prefix} # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, pad(15)] exec.tx::execute_foreign_procedure @@ -1696,7 +1696,6 @@ fn test_get_item_init_and_get_map_item_init_with_foreign_account() -> anyhow::Re use.miden::account use.std::sys - export.test_get_item_init push.0 exec.account::get_item_init @@ -1737,10 +1736,10 @@ fn test_get_item_init_and_get_map_item_init_with_foreign_account() -> anyhow::Re begin # Test get_item_init on foreign account - padw padw padw push.0.0 - push.0 + padw padw padw push.0.0.0 + # => [ pad(4), pad(4), pad(4), 0, 0, 0 ] procref.::foreign_account::test_get_item_init - push.{foreign_account_id_suffix}.{foreign_account_id_prefix} + push.{foreign_account_id_suffix} push.{foreign_account_id_prefix} exec.tx::execute_foreign_procedure push.{expected_value_slot_0} assert_eqw.err=\"foreign account get_item_init should work\" @@ -1750,7 +1749,7 @@ fn test_get_item_init_and_get_map_item_init_with_foreign_account() -> anyhow::Re push.{map_key} push.1 procref.::foreign_account::test_get_map_item_init - push.{foreign_account_id_suffix}.{foreign_account_id_prefix} + push.{foreign_account_id_suffix} push.{foreign_account_id_prefix} exec.tx::execute_foreign_procedure push.{map_value} assert_eqw.err=\"foreign account get_map_item_init should work\" diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index fab581a52e..eece48380f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -209,7 +209,7 @@ fn test_get_assets() -> anyhow::Result<()> { let mut check_assets_code = format!( r#" # push the note index and memory destination pointer - push.{note_idx}.{dest_ptr} + push.{note_idx} push.{dest_ptr} # => [dest_ptr, note_index] # write the assets to the memory @@ -305,7 +305,7 @@ fn test_get_inputs_info() -> anyhow::Result<()> { use.miden::input_note begin - # get the inputs commitment and length from the input note with index 0 (the only one + # get the inputs commitment and length from the input note with index 0 (the only one # we have) push.0 exec.input_note::get_inputs_info diff --git a/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs b/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs index e5229e0a85..bdd31fa066 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs @@ -412,7 +412,7 @@ fn execute_link_map_test(operations: Vec) -> anyhow::Result<()> { let set_code = format!( r#" - push.{value1}.{value0}.{key}.{map_ptr} + push.{value1} push.{value0} push.{key} push.{map_ptr} # => [map_ptr, KEY, VALUE] exec.link_map::set # => [is_new_key] @@ -439,7 +439,7 @@ fn execute_link_map_test(operations: Vec) -> anyhow::Result<()> { let get_code = format!( r#" - push.{key}.{map_ptr} + push.{key} push.{map_ptr} # => [map_ptr, KEY] exec.link_map::get # => [contains_key, VALUE0, VALUE1] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 654df49d84..5dbcfd6409 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -3,6 +3,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use anyhow::Context; +use miden_lib::account::auth::PublicKeyCommitment; use miden_lib::account::wallets::BasicWallet; use miden_lib::errors::MasmError; use miden_lib::testing::note::NoteBuilder; @@ -166,7 +167,7 @@ fn test_note_script_and_note_args() -> miette::Result<()> { let process = tx_context.execute_code(code).unwrap(); assert_eq!(process.stack.get_word(0), note_args[0]); - assert_eq!(process.stack.get_word(1), note_args[1]); + assert_eq!(process.stack.get_word(4), note_args[1]); Ok(()) } @@ -214,10 +215,10 @@ fn test_build_recipient() -> anyhow::Result<()> { begin # put the values that will be hashed into the memory - push.{word_1}.{base_addr} mem_storew dropw - push.{word_2}.{addr_1} mem_storew dropw - push.{word_3}.{addr_2} mem_storew dropw - push.{word_4}.{addr_3} mem_storew dropw + push.{word_1} push.{base_addr} mem_storew dropw + push.{word_2} push.{addr_1} mem_storew dropw + push.{word_3} push.{addr_2} mem_storew dropw + push.{word_4} push.{addr_3} mem_storew dropw # Test with 4 values push.{script_root} # SCRIPT_ROOT @@ -303,10 +304,10 @@ fn test_compute_inputs_commitment() -> anyhow::Result<()> { begin # put the values that will be hashed into the memory - push.{word_1}.{base_addr} mem_storew dropw - push.{word_2}.{addr_1} mem_storew dropw - push.{word_3}.{addr_2} mem_storew dropw - push.{word_4}.{addr_3} mem_storew dropw + push.{word_1} push.{base_addr} mem_storew dropw + push.{word_2} push.{addr_1} mem_storew dropw + push.{word_3} push.{addr_2} mem_storew dropw + push.{word_4} push.{addr_3} mem_storew dropw # push the number of values and pointer to the inputs on the stack push.5.4000 @@ -408,7 +409,7 @@ fn test_build_metadata() -> miette::Result<()> { begin exec.prologue::prepare_transaction - push.{execution_hint}.{note_type}.{aux}.{tag} + push.{execution_hint} push.{note_type} push.{aux} push.{tag} exec.output_note::build_metadata # truncate the stack @@ -522,8 +523,8 @@ fn test_public_key_as_note_input() -> anyhow::Result<()> { let sec_key = SecretKey::with_rng(&mut rng); // this value will be used both as public key in the RPO component of the target account and as // well as the input of the input note - let public_key = sec_key.public_key(); - let public_key_value: Word = public_key.into(); + let public_key = PublicKeyCommitment::from(sec_key.public_key()); + let public_key_value = Word::from(public_key); let (rpo_component, authenticator) = Auth::BasicAuth.build_component(); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index ba836e126b..e6dee6be6d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -947,7 +947,7 @@ fn test_get_assets() -> anyhow::Result<()> { let mut check_assets_code = format!( r#" # push the note index and memory destination pointer - push.{note_idx}.{dest_ptr} + push.{note_idx} push.{dest_ptr} # => [dest_ptr, note_index] # write the assets to the memory diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index 8e2d306b4d..af0e5d12ef 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -148,7 +148,7 @@ fn test_transaction_prologue() -> anyhow::Result<()> { let tx_script = TransactionScript::new(mock_tx_script_program); - let note_args = [Word::from([91, 91, 91, 91u32]), Word::from([92, 92, 92, 92u32])]; + let note_args = [Word::from([91u32; 4]), Word::from([92u32; 4])]; let note_args_map = BTreeMap::from([ (tx_context.input_notes().get_note(0).note().id(), note_args[0]), diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 406ebeaf3d..2a092ef8fc 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -9,7 +9,7 @@ use miden_lib::account::wallets::BasicWallet; use miden_lib::note::create_p2id_note; use miden_lib::testing::account_component::IncrNonceAuthComponent; use miden_lib::testing::mock_account::MockAccountExt; -use miden_lib::transaction::{TransactionEvent, TransactionKernel}; +use miden_lib::transaction::TransactionKernel; use miden_lib::utils::ScriptBuilder; use miden_objects::account::{ Account, @@ -452,10 +452,10 @@ fn executed_transaction_output_notes() -> anyhow::Result<()> { /// component to result in a [`TransactionExecutorError::Unauthorized`] error. #[test] fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { - let source_code = format!( - " + let source_code = r#" use.miden::auth use.miden::tx + const.ABORT_EVENT=event("miden::auth::unauthorized") #! Inputs: [AUTH_ARGS, pad(12)] #! Outputs: [pad(16)] export.auth_abort_tx @@ -475,11 +475,9 @@ fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { exec.auth::hash_tx_summary # => [MESSAGE, pad(16)] - emit.{abort_event} + emit.ABORT_EVENT end - ", - abort_event = TransactionEvent::Unauthorized as u32 - ); + "#; let auth_component = AccountComponent::compile(source_code, TransactionKernel::assembler(), vec![]) diff --git a/crates/miden-testing/src/mock_chain/auth.rs b/crates/miden-testing/src/mock_chain/auth.rs index 5077b730a3..2b47c7d56a 100644 --- a/crates/miden-testing/src/mock_chain/auth.rs +++ b/crates/miden-testing/src/mock_chain/auth.rs @@ -7,11 +7,12 @@ use miden_lib::account::auth::{ AuthRpoFalcon512Acl, AuthRpoFalcon512AclConfig, AuthRpoFalcon512Multisig, + PublicKeyCommitment, }; use miden_lib::testing::account_component::{ConditionalAuthComponent, IncrNonceAuthComponent}; use miden_objects::Word; use miden_objects::account::{AccountComponent, AuthSecretKey}; -use miden_objects::crypto::dsa::rpo_falcon512::{PublicKey, SecretKey}; +use miden_objects::crypto::dsa::rpo_falcon512::SecretKey; use miden_objects::testing::noop_auth_component::NoopAuthComponent; use miden_tx::auth::BasicAuthenticator; use rand::SeedableRng; @@ -59,7 +60,8 @@ impl Auth { Auth::BasicAuth => { let mut rng = ChaCha20Rng::from_seed(Default::default()); let sec_key = SecretKey::with_rng(&mut rng); - let pub_key = sec_key.public_key(); + let pub_key = + miden_lib::account::auth::PublicKeyCommitment::from(sec_key.public_key()); let component = AuthRpoFalcon512::new(pub_key).into(); let authenticator = BasicAuthenticator::::new_with_rng( @@ -70,7 +72,8 @@ impl Auth { (component, Some(authenticator)) }, Auth::Multisig { threshold, approvers } => { - let pub_keys: Vec<_> = approvers.iter().map(|word| PublicKey::new(*word)).collect(); + let pub_keys: Vec<_> = + approvers.iter().map(|word| PublicKeyCommitment::from(*word)).collect(); let component = AuthRpoFalcon512Multisig::new(*threshold, pub_keys) .expect("multisig component creation failed") @@ -85,7 +88,7 @@ impl Auth { } => { let mut rng = ChaCha20Rng::from_seed(Default::default()); let sec_key = SecretKey::with_rng(&mut rng); - let pub_key = sec_key.public_key(); + let pub_key = PublicKeyCommitment::from(sec_key.public_key()); let component = AuthRpoFalcon512Acl::new( pub_key, diff --git a/crates/miden-testing/src/mock_host.rs b/crates/miden-testing/src/mock_host.rs index 288c495371..6dcac84aa4 100644 --- a/crates/miden-testing/src/mock_host.rs +++ b/crates/miden-testing/src/mock_host.rs @@ -3,7 +3,8 @@ use alloc::rc::Rc; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_lib::transaction::{TransactionEvent, TransactionEventError}; +use miden_lib::StdLibrary; +use miden_lib::transaction::{EventId, TransactionEvent, TransactionEventError}; use miden_objects::account::{AccountCode, AccountVaultDelta}; use miden_objects::assembly::debuginfo::SourceManagerSync; use miden_objects::assembly::{DefaultSourceManager, SourceManager}; @@ -14,6 +15,7 @@ use miden_processor::{ BaseHost, ContextId, EventError, + EventHandlerRegistry, MastForest, MastForestStore, ProcessState, @@ -32,6 +34,8 @@ pub struct MockHost { acct_procedure_index_map: AccountProcedureIndexMap, mast_store: Rc, source_manager: Arc, + /// Handle the VM default events _before_ passing it to user defined ones. + stdlib_handlers: EventHandlerRegistry, } impl MockHost { @@ -49,10 +53,23 @@ impl MockHost { ) .expect("account procedure index map should be valid"); + let stdlib_handlers = { + let mut registry = EventHandlerRegistry::new(); + + let stdlib = StdLibrary::default(); + for (event_id, handler) in stdlib.handlers() { + registry + .register(event_id, handler) + .expect("There are no duplicates in the stdlibrary handlers"); + } + registry + }; + Self { acct_procedure_index_map: account_procedure_index_map, mast_store, source_manager: Arc::new(DefaultSourceManager::default()), + stdlib_handlers, } } @@ -80,10 +97,6 @@ impl MockHost { } impl BaseHost for MockHost { - fn get_mast_forest(&self, node_digest: &Word) -> Option> { - self.mast_store.get(node_digest) - } - fn get_label_and_source_file( &self, location: &miden_objects::assembly::debuginfo::Location, @@ -98,15 +111,19 @@ impl BaseHost for MockHost { } impl SyncHost for MockHost { - fn on_event( - &mut self, - process: &ProcessState, - event_id: u32, - ) -> Result, EventError> { + fn get_mast_forest(&self, node_digest: &Word) -> Option> { + self.mast_store.get(node_digest) + } + + fn on_event(&mut self, process: &ProcessState) -> Result, EventError> { + let event_id = EventId::from_felt(process.get_stack_item(0)); + if let Some(result) = self.stdlib_handlers.handle_event(event_id, process).transpose() { + return result; + } let event = TransactionEvent::try_from(event_id).map_err(Box::new)?; if process.ctx() != ContextId::root() { - return Err(Box::new(TransactionEventError::NotRootContext(event_id))); + return Err(Box::new(TransactionEventError::NotRootContext(event))); } let advice_mutations = match event { diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index c065881040..71e645a44d 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -51,7 +51,10 @@ fn setup_keys_and_authenticators( // Create authenticators only for the signers we'll actually use for i in 0..threshold { let authenticator = BasicAuthenticator::::new_with_rng( - &[(public_keys[i].into(), AuthSecretKey::RpoFalcon512(secret_keys[i].clone()))], + &[( + public_keys[i].to_commitment(), + AuthSecretKey::RpoFalcon512(secret_keys[i].clone()), + )], rng.clone(), ); authenticators.push(authenticator); @@ -66,7 +69,7 @@ fn create_multisig_account( public_keys: &[PublicKey], asset_amount: u64, ) -> anyhow::Result { - let approvers: Vec<_> = public_keys.iter().map(|pk| (*pk).into()).collect(); + let approvers: Vec<_> = public_keys.iter().map(|pk| pk.to_commitment()).collect(); let multisig_account = AccountBuilder::new([0; 32]) .with_auth_component(Auth::Multisig { threshold, approvers }) @@ -137,15 +140,19 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { let msg = tx_summary.as_ref().to_commitment(); let tx_summary = SigningInputs::TransactionSummary(tx_summary); - let sig_1 = authenticators[0].get_signature(public_keys[0].into(), &tx_summary).await?; - let sig_2 = authenticators[1].get_signature(public_keys[1].into(), &tx_summary).await?; + let sig_1 = authenticators[0] + .get_signature(public_keys[0].to_commitment(), &tx_summary) + .await?; + let sig_2 = authenticators[1] + .get_signature(public_keys[1].to_commitment(), &tx_summary) + .await?; // Execute transaction with signatures - should succeed let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? .extend_expected_output_notes(vec![OutputNote::Full(output_note)]) - .add_signature(public_keys[0], msg, sig_1) - .add_signature(public_keys[1], msg, sig_2) + .add_signature(public_keys[0].clone(), msg, sig_1) + .add_signature(public_keys[1].clone(), msg, sig_2) .auth_args(salt) .build()? .execute() @@ -216,18 +223,18 @@ async fn test_multisig_2_of_4_all_signer_combinations() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[*signer1_idx] - .get_signature(public_keys[*signer1_idx].into(), &tx_summary) + .get_signature(public_keys[*signer1_idx].to_commitment(), &tx_summary) .await?; let sig_2 = authenticators[*signer2_idx] - .get_signature(public_keys[*signer2_idx].into(), &tx_summary) + .get_signature(public_keys[*signer2_idx].to_commitment(), &tx_summary) .await?; // Execute transaction with signatures - should succeed for any combination let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? .auth_args(salt) - .add_signature(public_keys[*signer1_idx], msg, sig_1) - .add_signature(public_keys[*signer2_idx], msg, sig_2) + .add_signature(public_keys[*signer1_idx].clone(), msg, sig_1) + .add_signature(public_keys[*signer2_idx].clone(), msg, sig_2) .build()?; let executed_tx = tx_context_execute.execute().await.unwrap_or_else(|_| { @@ -282,14 +289,18 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { let msg = tx_summary.as_ref().to_commitment(); let tx_summary = SigningInputs::TransactionSummary(tx_summary); - let sig_1 = authenticators[0].get_signature(public_keys[0].into(), &tx_summary).await?; - let sig_2 = authenticators[1].get_signature(public_keys[1].into(), &tx_summary).await?; + let sig_1 = authenticators[0] + .get_signature(public_keys[0].to_commitment(), &tx_summary) + .await?; + let sig_2 = authenticators[1] + .get_signature(public_keys[1].to_commitment(), &tx_summary) + .await?; // Execute transaction with signatures - should succeed (first execution) let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .add_signature(public_keys[0], msg, sig_1.clone()) - .add_signature(public_keys[1], msg, sig_2.clone()) + .add_signature(public_keys[0].clone(), msg, sig_1.clone()) + .add_signature(public_keys[1].clone(), msg, sig_2.clone()) .auth_args(salt) .build()?; @@ -302,8 +313,8 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { // Now attempt to execute the same transaction again - should fail due to replay protection let tx_context_replay = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .add_signature(public_keys[0], msg, sig_1) - .add_signature(public_keys[1], msg, sig_2) + .add_signature(public_keys[0].clone(), msg, sig_1) + .add_signature(public_keys[1].clone(), msg, sig_2) .auth_args(salt) .build()?; diff --git a/crates/miden-testing/tests/scripts/swap.rs b/crates/miden-testing/tests/scripts/swap.rs index a0fdf92d9d..1493ed46e7 100644 --- a/crates/miden-testing/tests/scripts/swap.rs +++ b/crates/miden-testing/tests/scripts/swap.rs @@ -236,7 +236,7 @@ fn settle_coincidence_of_wants() -> anyhow::Result<()> { // Create two different assets for the swap let faucet0 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; let faucet1 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1)?; - let asset_a = FungibleAsset::new(faucet0, 10_000)?.into(); + let asset_a = FungibleAsset::new(faucet0, 10_777)?.into(); let asset_b = FungibleAsset::new(faucet1, 10)?.into(); let mut builder = MockChain::builder(); diff --git a/crates/miden-testing/tests/wallet/mod.rs b/crates/miden-testing/tests/wallet/mod.rs index 84f498e88f..c66b72cda4 100644 --- a/crates/miden-testing/tests/wallet/mod.rs +++ b/crates/miden-testing/tests/wallet/mod.rs @@ -17,7 +17,7 @@ fn wallet_creation() { let mut rng = ChaCha20Rng::from_seed(seed); let sec_key = SecretKey::with_rng(&mut rng); - let pub_key = sec_key.public_key(); + let pub_key = miden_lib::account::auth::PublicKeyCommitment::from(sec_key.public_key()); let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key }; // we need to use an initial seed to create the wallet account @@ -40,6 +40,5 @@ fn wallet_creation() { assert!(wallet.is_regular_account()); assert_eq!(wallet.code().commitment(), expected_code_commitment); - let pub_key_word: Word = pub_key.into(); - assert_eq!(wallet.storage().get_item(0).unwrap(), pub_key_word); + assert_eq!(wallet.storage().get_item(0).unwrap(), Word::from(pub_key)); } diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 0bb17b32d7..065505ce06 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -2,7 +2,7 @@ use alloc::collections::BTreeMap; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_lib::transaction::{TransactionAdviceInputs, TransactionEvent}; +use miden_lib::transaction::{EventId, TransactionAdviceInputs}; use miden_objects::account::{AccountCode, AccountDelta, AccountId, PartialAccount}; use miden_objects::assembly::debuginfo::Location; use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; @@ -390,10 +390,6 @@ where STORE: DataStore, AUTH: TransactionAuthenticator, { - fn get_mast_forest(&self, procedure_root: &Word) -> Option> { - self.base_host.get_mast_forest(procedure_root) - } - fn get_label_and_source_file( &self, location: &Location, @@ -410,16 +406,20 @@ where STORE: DataStore + Sync, AUTH: TransactionAuthenticator + Sync, { + fn get_mast_forest(&self, node_digest: &Word) -> impl FutureMaybeSend>> { + let mast_forest = self.base_host.get_mast_forest(node_digest); + async move { mast_forest } + } + fn on_event( &mut self, process: &ProcessState, - event_id: u32, ) -> impl FutureMaybeSend, EventError>> { + let event_id = EventId::from_felt(process.get_stack_item(0)); + // TODO: Eventually, refactor this to let TransactionEvent contain the data directly, which // should be cleaner. - let event_handling_result = TransactionEvent::try_from(event_id) - .map_err(EventError::from) - .and_then(|transaction_event| self.base_host.handle_event(process, transaction_event)); + let event_handling_result = self.base_host.handle_event(process, event_id); async move { let event_handling = event_handling_result?; diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index a0e013ef77..63d9a0c937 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -188,10 +188,12 @@ where self.prepare_transaction(&tx_inputs, &tx_args, None).await?; let processor = FastProcessor::new_debug(stack_inputs.as_slice(), advice_inputs); - let (stack_outputs, advice_provider) = processor + let output = processor .execute(&TransactionKernel::main(), &mut host) .await .map_err(map_execution_error)?; + let stack_outputs = output.stack; + let advice_provider = output.advice; // The stack is not necessary since it is being reconstructed when re-executing. let (_stack, advice_map, merkle_store) = advice_provider.into_parts(); @@ -235,10 +237,11 @@ where let processor = FastProcessor::new_with_advice_inputs(stack_inputs.as_slice(), advice_inputs); - let (stack_outputs, _advice_provider) = processor + let output = processor .execute(&TransactionKernel::tx_script_main(), &mut host) .await .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?; + let stack_outputs = output.stack; Ok(*stack_outputs) } diff --git a/crates/miden-tx/src/host/account_procedures.rs b/crates/miden-tx/src/host/account_procedures.rs index 9fb242278f..8d7d2a9d8b 100644 --- a/crates/miden-tx/src/host/account_procedures.rs +++ b/crates/miden-tx/src/host/account_procedures.rs @@ -82,7 +82,7 @@ impl AccountProcedureIndexMap { .expect("current account code commitment was not initialized") }; - let proc_root = process.get_stack_word(0); + let proc_root = process.get_stack_word(1); self.0 .get(&code_commitment) diff --git a/crates/miden-tx/src/host/link_map.rs b/crates/miden-tx/src/host/link_map.rs index 3bfe0d5ce2..cfb19f7d25 100644 --- a/crates/miden-tx/src/host/link_map.rs +++ b/crates/miden-tx/src/host/link_map.rs @@ -42,13 +42,8 @@ impl<'process> LinkMap<'process> { /// Expected operand stack state before: [map_ptr, KEY, NEW_VALUE] /// Advice stack state after: [set_operation, entry_ptr] pub fn handle_set_event(process: &ProcessState<'_>) -> Result, EventError> { - let map_ptr = process.get_stack_item(0); - let map_key = Word::from([ - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - process.get_stack_item(1), - ]); + let map_ptr = process.get_stack_item(1); + let map_key = process.get_stack_word(2); let link_map = LinkMap::new(map_ptr, process); @@ -65,13 +60,8 @@ impl<'process> LinkMap<'process> { /// Expected operand stack state before: [map_ptr, KEY] /// Advice stack state after: [get_operation, entry_ptr] pub fn handle_get_event(process: &ProcessState<'_>) -> Result, EventError> { - let map_ptr = process.get_stack_item(0); - let map_key = Word::from([ - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - process.get_stack_item(1), - ]); + let map_ptr = process.get_stack_item(1); + let map_key = process.get_stack_word(2); let link_map = LinkMap::new(map_ptr, process); let (get_op, entry_ptr) = link_map.compute_get_operation(LexicographicWord::from(map_key)); diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 40406b55e5..6982b26476 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -11,6 +11,7 @@ mod account_procedures; pub use account_procedures::AccountProcedureIndexMap; mod note_builder; +use miden_lib::StdLibrary; use note_builder::OutputNoteBuilder; mod script_mast_forest_store; @@ -27,7 +28,7 @@ use miden_lib::transaction::memory::{ ACTIVE_INPUT_NOTE_PTR, NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR, }; -use miden_lib::transaction::{TransactionEvent, TransactionEventError}; +use miden_lib::transaction::{EventId, TransactionEvent, TransactionEventError}; use miden_objects::account::{ AccountCode, AccountDelta, @@ -55,6 +56,7 @@ use miden_processor::{ AdviceMutation, ContextId, EventError, + EventHandlerRegistry, ExecutionError, Felt, MastForest, @@ -105,6 +107,9 @@ pub struct TransactionBaseHost<'store, STORE> { /// /// The progress is updated event handlers. tx_progress: TransactionProgress, + + /// Handle the VM default events _before_ passing it to user defined ones. + stdlib_handlers: EventHandlerRegistry, } impl<'store, STORE> TransactionBaseHost<'store, STORE> @@ -122,6 +127,17 @@ where scripts_mast_store: ScriptMastForestStore, acct_procedure_index_map: AccountProcedureIndexMap, ) -> Self { + let stdlib_handlers = { + let mut registry = EventHandlerRegistry::new(); + + let stdlib = StdLibrary::default(); + for (event_id, handler) in stdlib.handlers() { + registry + .register(event_id, handler) + .expect("There are no duplicates in the stdlibrary handlers"); + } + registry + }; Self { mast_store, scripts_mast_store, @@ -135,6 +151,7 @@ where output_notes: BTreeMap::default(), input_notes, tx_progress: TransactionProgress::default(), + stdlib_handlers, } } @@ -221,11 +238,17 @@ where pub(super) fn handle_event( &mut self, process: &ProcessState, - transaction_event: TransactionEvent, + event_id: EventId, ) -> Result { + if let Some(mutations) = self.stdlib_handlers.handle_event(event_id, process)? { + return Ok(TransactionEventHandling::Handled(mutations)); + } + + let transaction_event = TransactionEvent::try_from(event_id).map_err(EventError::from)?; + // Privileged events can only be emitted from the root context. if process.ctx() != ContextId::root() && transaction_event.is_privileged() { - return Err(Box::new(TransactionEventError::NotRootContext(transaction_event as u32))); + return Err(Box::new(TransactionEventError::NotRootContext(transaction_event))); } let advice_mutations = match transaction_event { @@ -369,12 +392,12 @@ where /// Extract all necessary data for requesting the data to access the foreign account that is /// being loaded. /// - /// Expected stack state: `[account_id_prefix, account_id_suffix]` + /// Expected stack state: `[event, account_id_prefix, account_id_suffix]` pub fn on_account_before_foreign_load( &self, process: &ProcessState, ) -> Result { - let account_id_word = process.get_stack_word(0); + let account_id_word = process.get_stack_word(1); let account_id = AccountId::try_from([account_id_word[3], account_id_word[2]]).map_err(|err| { TransactionKernelError::other_with_source( @@ -390,7 +413,7 @@ where /// Pushes a signature to the advice stack as a response to the `AuthRequest` event. /// - /// Expected stack state: `[MESSAGE, PUB_KEY]` + /// Expected stack state: `[event, MESSAGE, PUB_KEY]` /// /// The signature is fetched from the advice map using `hash(PUB_KEY, MESSAGE)` as the key. If /// not present in the advice map [`TransactionEventHandling::Unhandled`] is returned with the @@ -400,8 +423,8 @@ where &self, process: &ProcessState, ) -> Result { - let message = process.get_stack_word(0); - let pub_key_hash = process.get_stack_word(1); + let message = process.get_stack_word(1); + let pub_key_hash = process.get_stack_word(5); let signature_key = Hasher::merge(&[pub_key_hash, message]); let tx_summary = self.build_tx_summary(process, message)?; @@ -438,9 +461,7 @@ where /// /// Expected stack state: /// - /// ```text - /// [MESSAGE] - /// ``` + /// `[event, MESSAGE]` /// /// Expected advice map state: /// @@ -448,7 +469,7 @@ where /// MESSAGE -> [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT] /// ``` fn on_unauthorized(&self, process: &ProcessState) -> TransactionKernelError { - let msg = process.get_stack_word(0); + let msg = process.get_stack_word(1); let tx_summary = match self.build_tx_summary(process, msg) { Ok(s) => s, @@ -470,13 +491,13 @@ where /// Expected stack state: /// /// ```text - /// [FEE_ASSET] + /// `[event, FEE_ASSET]` /// ``` fn on_before_tx_fee_removed_from_account( &self, process: &ProcessState, ) -> Result { - let fee_asset = process.get_stack_word(0); + let fee_asset = process.get_stack_word(1); let fee_asset = FungibleAsset::try_from(fee_asset) .map_err(TransactionKernelError::FailedToConvertFeeAsset)?; @@ -488,12 +509,12 @@ where /// Creates a new [OutputNoteBuilder] from the data on the operand stack and stores it into the /// `output_notes` field of this [`TransactionBaseHost`]. /// - /// Expected stack state: `[NOTE_METADATA, RECIPIENT, ...]` + /// Expected stack state: `[event, NOTE_METADATA, RECIPIENT, ...]` fn on_note_after_created( &mut self, process: &ProcessState, ) -> Result<(), TransactionKernelError> { - let stack = process.get_stack_state(); + let stack = process.get_stack_state().split_off(1); // # => [NOTE_METADATA] let note_idx: usize = stack[9].as_int() as usize; @@ -509,7 +530,7 @@ where /// Adds an asset at the top of the [OutputNoteBuilder] identified by the note pointer. /// - /// Expected stack state: [ASSET, note_ptr, num_of_assets, note_idx] + /// Expected stack state: `[event, ASSET, note_ptr, num_of_assets, note_idx]` fn on_note_before_add_asset( &mut self, process: &ProcessState, @@ -517,11 +538,12 @@ where let stack = process.get_stack_state(); //# => [ASSET, note_ptr, num_of_assets, note_idx] - let note_idx = stack[6].as_int(); + let note_idx = stack[7].as_int(); assert!(note_idx < self.output_notes.len() as u64); let node_idx = note_idx as usize; - let asset = Asset::try_from(process.get_stack_word(0)).map_err(|source| { + let asset_word = process.get_stack_word(1); + let asset = Asset::try_from(asset_word).map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_note_before_add_asset", source, @@ -540,7 +562,7 @@ where /// Loads the index of the procedure root onto the advice stack. /// - /// Expected stack state: [PROC_ROOT, ...] + /// Expected stack state: `[event, PROC_ROOT, ...]` fn on_account_push_procedure_index( &mut self, process: &ProcessState, @@ -565,13 +587,13 @@ where /// Extracts information from the process state about the storage slot being updated and /// records the latest value of this storage slot. /// - /// Expected stack state: [slot_index, NEW_SLOT_VALUE, CURRENT_SLOT_VALUE, ...] + /// Expected stack state: `[event, slot_index, NEW_SLOT_VALUE, CURRENT_SLOT_VALUE, ...]` pub fn on_account_storage_after_set_item( &mut self, process: &ProcessState, ) -> Result<(), TransactionKernelError> { // get slot index from the stack and make sure it is valid - let slot_index = process.get_stack_item(0); + let slot_index = process.get_stack_item(1); // get number of storage slots initialized by the account let num_storage_slot = Self::get_num_storage_slots(process)?; @@ -584,20 +606,10 @@ where } // get the value to which the slot is being updated - let new_slot_value = Word::new([ - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - process.get_stack_item(1), - ]); + let new_slot_value = process.get_stack_word(2); // get the current value for the slot - let current_slot_value = Word::new([ - process.get_stack_item(8), - process.get_stack_item(7), - process.get_stack_item(6), - process.get_stack_item(5), - ]); + let current_slot_value = process.get_stack_word(6); self.account_delta.storage().set_item( slot_index.as_int() as u8, @@ -611,14 +623,14 @@ where /// Checks if the necessary witness for accessing the map item is already in the merkle store, /// and if not, extracts all necessary data for requesting it. /// - /// Expected stack state: `[KEY, ROOT, index]` + /// Expected stack state: `[event, KEY, ROOT, index]` pub fn on_account_storage_before_get_map_item( &self, process: &ProcessState, ) -> Result { - let map_key = process.get_stack_word(0); - let current_map_root = process.get_stack_word(1); - let slot_index = process.get_stack_item(8); + let map_key = process.get_stack_word(1); + let current_map_root = process.get_stack_word(5); + let slot_index = process.get_stack_item(9); self.on_account_storage_before_get_or_set_map_item( slot_index, @@ -631,24 +643,14 @@ where /// Checks if the necessary witness for accessing the map item is already in the merkle store, /// and if not, extracts all necessary data for requesting it. /// - /// Expected stack state: `[index, KEY, NEW_VALUE, OLD_ROOT]` + /// Expected stack state: `[event, index, KEY, NEW_VALUE, OLD_ROOT]` pub fn on_account_storage_before_set_map_item( &self, process: &ProcessState, ) -> Result { - let slot_index = process.get_stack_item(0); - let map_key = Word::from([ - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - process.get_stack_item(1), - ]); - let current_map_root = Word::from([ - process.get_stack_item(12), - process.get_stack_item(11), - process.get_stack_item(10), - process.get_stack_item(9), - ]); + let slot_index = process.get_stack_item(1); + let map_key = process.get_stack_word(2); + let current_map_root = process.get_stack_word(10); self.on_account_storage_before_get_or_set_map_item( slot_index, @@ -719,13 +721,13 @@ where /// Extracts information from the process state about the storage map being updated and /// records the latest values of this storage map. /// - /// Expected stack state: [slot_index, KEY, PREV_MAP_VALUE, NEW_MAP_VALUE] + /// Expected stack state: `[event, slot_index, KEY, PREV_MAP_VALUE, NEW_MAP_VALUE]` pub fn on_account_storage_after_set_map_item( &mut self, process: &ProcessState, ) -> Result<(), TransactionKernelError> { // get slot index from the stack and make sure it is valid - let slot_index = process.get_stack_item(0); + let slot_index = process.get_stack_item(1); // get number of storage slots initialized by the account let num_storage_slot = Self::get_num_storage_slots(process)?; @@ -738,28 +740,13 @@ where } // get the KEY to which the slot is being updated - let key = Word::new([ - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - process.get_stack_item(1), - ]); + let key = process.get_stack_word(2); // get the previous VALUE of the slot - let prev_map_value = Word::new([ - process.get_stack_item(8), - process.get_stack_item(7), - process.get_stack_item(6), - process.get_stack_item(5), - ]); + let prev_map_value = process.get_stack_word(6); // get the VALUE to which the slot is being updated - let new_map_value = Word::new([ - process.get_stack_item(12), - process.get_stack_item(11), - process.get_stack_item(10), - process.get_stack_item(9), - ]); + let new_map_value = process.get_stack_word(10); self.account_delta.storage().set_map_item( slot_index.as_int() as u8, @@ -777,12 +764,12 @@ where /// Extracts the asset that is being added to the account's vault from the process state and /// updates the appropriate fungible or non-fungible asset map. /// - /// Expected stack state: [ASSET, ...] + /// Expected stack state: `[event, ASSET, ...]` pub fn on_account_vault_after_add_asset( &mut self, process: &ProcessState, ) -> Result<(), TransactionKernelError> { - let asset: Asset = process.get_stack_word(0).try_into().map_err(|source| { + let asset: Asset = process.get_stack_word(1).try_into().map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_after_add_asset", source, @@ -799,19 +786,20 @@ where /// Checks if the necessary witness for accessing the asset is already in the merkle store, /// and if not, extracts all necessary data for requesting it. /// - /// Expected stack state: `[ASSET, account_vault_root_ptr]` + /// Expected stack state: `[event, ASSET, account_vault_root_ptr]` pub fn on_account_vault_before_add_or_remove_asset( &self, process: &ProcessState, ) -> Result { - let asset: Asset = process.get_stack_word(0).try_into().map_err(|source| { + let asset_word = process.get_stack_word(1); + let asset = Asset::try_from(asset_word).map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_before_add_or_remove_asset", source, } })?; - let vault_root_ptr = process.get_stack_item(4); + let vault_root_ptr = process.get_stack_item(5); let vault_root_ptr = u32::try_from(vault_root_ptr).map_err(|_err| { TransactionKernelError::other(format!( "vault root ptr should fit into a u32, but was {vault_root_ptr}" @@ -836,12 +824,12 @@ where /// Extracts the asset that is being removed from the account's vault from the process state /// and updates the appropriate fungible or non-fungible asset map. /// - /// Expected stack state: [ASSET, ...] + /// Expected stack state: `[event, ASSET, ...]` pub fn on_account_vault_after_remove_asset( &mut self, process: &ProcessState, ) -> Result<(), TransactionKernelError> { - let asset: Asset = process.get_stack_word(0).try_into().map_err(|source| { + let asset: Asset = process.get_stack_word(1).try_into().map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_after_remove_asset", source, @@ -858,12 +846,12 @@ where /// Checks if the necessary witness for accessing the asset is already in the merkle store, /// and if not, extracts all necessary data for requesting it. /// - /// Expected stack state: `[faucet_id_prefix, faucet_id_suffix, vault_root_ptr]` + /// Expected stack state: `[event, faucet_id_prefix, faucet_id_suffix, vault_root_ptr]` pub fn on_account_vault_before_get_balance( &self, process: &ProcessState, ) -> Result { - let stack_top = process.get_stack_word(0); + let stack_top = process.get_stack_word(1); let faucet_id = AccountId::try_from([stack_top[3], stack_top[2]]).map_err(|err| { TransactionKernelError::other_with_source( "failed to convert faucet ID word into faucet ID", @@ -889,17 +877,17 @@ where /// Checks if the necessary witness for accessing the asset is already in the merkle store, /// and if not, extracts all necessary data for requesting it. /// - /// Expected stack state: `[ASSET, vault_root_ptr]` + /// Expected stack state: `[event, ASSET, vault_root_ptr]` pub fn on_account_vault_before_has_non_fungible_asset( &self, process: &ProcessState, ) -> Result { - let asset_word = process.get_stack_word(0); + let asset_word = process.get_stack_word(1); let asset = Asset::try_from(asset_word).map_err(|err| { TransactionKernelError::other_with_source("provided asset is not a valid asset", err) })?; - let vault_root_ptr = process.get_stack_item(4); + let vault_root_ptr = process.get_stack_item(5); let vault_root = Self::get_vault_root(process, vault_root_ptr)?; self.on_account_vault_asset_accessed(process, asset, vault_root) diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index e2afb67a82..113683b261 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -2,6 +2,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_lib::transaction::TransactionKernel; +use miden_objects::AccountError; use miden_objects::account::delta::AccountUpdateDetails; use miden_objects::account::{ Account, @@ -199,7 +200,8 @@ fn partial_account_to_full(partial_account: PartialAccount) -> Account { // For new accounts, the partial storage must represent the full initial account // storage. - let storage = partial_storage_to_full(partial_storage); + let storage = partial_storage_to_full(partial_storage) + .expect("partial account should ensure internal consistency for seed"); // The vault of a new account should be empty. debug_assert_eq!(partial_vault.leaves().count(), 0); @@ -209,7 +211,9 @@ fn partial_account_to_full(partial_account: PartialAccount) -> Account { .expect("partial account should ensure internal consistency for seed") } -fn partial_storage_to_full(partial_storage: PartialStorage) -> AccountStorage { +fn partial_storage_to_full( + partial_storage: PartialStorage, +) -> Result { let (_, header, mut maps) = partial_storage.into_parts(); let mut storage_slots = Vec::new(); for (slot_type, slot_value) in header.slots() { @@ -218,25 +222,26 @@ fn partial_storage_to_full(partial_storage: PartialStorage) -> AccountStorage { storage_slots.push(StorageSlot::Value(*slot_value)); }, StorageSlotType::Map => { - let storage_map = maps - .remove(slot_value) - .map(partial_storage_map_to_storage_map) - .expect("partial storage map should be present in partial storage"); + let storage_map = + maps.remove(slot_value) + .map(partial_storage_map_to_storage_map) + .expect("partial storage map should be present in partial storage")?; storage_slots.push(StorageSlot::Map(storage_map)); }, } } AccountStorage::new(storage_slots) - .expect("partial storage should not contain more than max allowed storage slots") } -fn partial_storage_map_to_storage_map(partial_storage_map: PartialStorageMap) -> StorageMap { +fn partial_storage_map_to_storage_map( + partial_storage_map: PartialStorageMap, +) -> Result { let mut storage_map = StorageMap::new(); for (key, value) in partial_storage_map.entries() { - storage_map.insert(*key, *value); + storage_map.insert(*key, *value)?; } - storage_map + Ok(storage_map) } #[cfg(any(feature = "testing", test))] diff --git a/crates/miden-tx/src/prover/prover_host.rs b/crates/miden-tx/src/prover/prover_host.rs index 6c460a828a..2bb773656c 100644 --- a/crates/miden-tx/src/prover/prover_host.rs +++ b/crates/miden-tx/src/prover/prover_host.rs @@ -1,8 +1,7 @@ -use alloc::boxed::Box; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_lib::transaction::TransactionEvent; +use miden_lib::transaction::EventId; use miden_objects::Word; use miden_objects::account::{AccountDelta, PartialAccount}; use miden_objects::assembly::debuginfo::Location; @@ -86,10 +85,6 @@ impl BaseHost for TransactionProverHost<'_, STORE> where STORE: MastForestStore, { - fn get_mast_forest(&self, procedure_root: &Word) -> Option> { - self.base_host.get_mast_forest(procedure_root) - } - fn get_label_and_source_file( &self, _location: &Location, @@ -105,14 +100,14 @@ impl SyncHost for TransactionProverHost<'_, STORE> where STORE: MastForestStore, { - fn on_event( - &mut self, - process: &ProcessState, - event_id: u32, - ) -> Result, EventError> { - let transaction_event = TransactionEvent::try_from(event_id).map_err(Box::new)?; + fn get_mast_forest(&self, node_digest: &Word) -> Option> { + self.base_host.get_mast_forest(node_digest) + } + + fn on_event(&mut self, process: &ProcessState) -> Result, EventError> { + let event_id = EventId::from_felt(process.get_stack_item(0)); - match self.base_host.handle_event(process, transaction_event)? { + match self.base_host.handle_event(process, event_id)? { TransactionEventHandling::Unhandled(event_data) => { // We match on the event_data here so that if a new // variant is added to the enum, this fails compilation and we can adapt From 462d7a419547ea268887550cd522b66ed17713ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:56:44 +0900 Subject: [PATCH 063/133] chore: add a rust-toolchain file, script to compute MSRV adjustments (#1947) --- .github/workflows/msrv.yml | 31 ++++++++ rust-toolchain.toml | 5 ++ scripts/check-msrv.sh | 153 +++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 .github/workflows/msrv.yml create mode 100644 rust-toolchain.toml create mode 100755 scripts/check-msrv.sh diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml new file mode 100644 index 0000000000..2f39de1937 --- /dev/null +++ b/.github/workflows/msrv.yml @@ -0,0 +1,31 @@ +name: Check MSRV + +on: + push: + branches: [next] + pull_request: + types: [opened, reopened, synchronize] + +# Limits workflow concurrency to only the latest commit in the PR. +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + +permissions: + contents: read + +jobs: + # Check MSRV (aka `rust-version`) in `Cargo.toml` is valid for workspace members + msrv: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y jq + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-msrv + run: cargo install cargo-msrv + - name: Check MSRV for each workspace member + run: | + ./scripts/check-msrv.sh diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..6744e56e15 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "1.90" +components = ["clippy", "rust-src", "rustfmt"] +profile = "minimal" +targets = ["wasm32-unknown-unknown"] diff --git a/scripts/check-msrv.sh b/scripts/check-msrv.sh new file mode 100755 index 0000000000..0bde2955f0 --- /dev/null +++ b/scripts/check-msrv.sh @@ -0,0 +1,153 @@ +#!/bin/bash +set -e +set -o pipefail + +# Enhanced MSRV checking script for workspace repository +# Checks MSRV for each workspace member and provides helpful error messages + +# ---- utilities -------------------------------------------------------------- + +check_command() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "ERROR: Required command '$1' is not installed or not in PATH" + exit 1 + fi +} + +# Check required commands +check_command "cargo" +check_command "jq" +check_command "rustup" +check_command "sed" +check_command "grep" +check_command "awk" + +# Portable in-place sed (GNU/macOS); usage: sed_i 's/foo/bar/' file +# shellcheck disable=SC2329 # used quoted +sed_i() { + if sed --version >/dev/null 2>&1; then + sed -i "$@" + else + sed -i '' "$@" + fi +} + +# ---- repo root -------------------------------------------------------------- + +# Get the directory where this script is located and change to the parent directory +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$DIR/.." + +echo "Checking MSRV for workspace members..." + +# ---- metadata -------------------------------------------------------------- + +metadata_json="$(cargo metadata --no-deps --format-version 1)" +workspace_root="$(printf '%s' "$metadata_json" | jq -r '.workspace_root')" + +failed_packages="" + +# Iterate actual workspace packages with manifest paths and (maybe) rust_version +# Fields per line (TSV): id name manifest_path rust_version_or_empty +while IFS=$'\t' read -r pkg_id package_name manifest_path rust_version; do + # Derive package directory (avoid external dirname for portability) + package_dir="${manifest_path%/*}" + if [[ -z "$package_dir" || "$package_dir" == "$manifest_path" ]]; then + package_dir="." + fi + + echo "Checking $package_name ($pkg_id) in $package_dir" + + if [[ ! -f "$package_dir/Cargo.toml" ]]; then + echo "WARNING: No Cargo.toml found in $package_dir, skipping..." + continue + fi + + # Prefer cargo metadata's effective rust_version if present + current_msrv="$rust_version" + if [[ -z "$current_msrv" ]]; then + # If the crate inherits: rust-version.workspace = true + if grep -Eq '^\s*rust-version\.workspace\s*=\s*true\b' "$package_dir/Cargo.toml"; then + # Read from workspace root [workspace.package] + current_msrv="$(grep -Eo '^\s*rust-version\s*=\s*"[^"]+"' "$workspace_root/Cargo.toml" | head -n1 | sed -E 's/.*"([^"]+)".*/\1/')" + if [[ -n "$current_msrv" ]]; then + echo " Using workspace MSRV: $current_msrv" + fi + fi + fi + + if [[ -z "$current_msrv" ]]; then + echo "WARNING: No rust-version found (package or workspace) for $package_name" + continue + fi + + echo " Current MSRV: $current_msrv" + + # Try to verify the MSRV + if ! cargo msrv verify --manifest-path "$package_dir/Cargo.toml" >/dev/null 2>&1; then + echo "ERROR: MSRV check failed for $package_name" + failed_packages="$failed_packages $package_name" + + echo "Searching for correct MSRV for $package_name..." + + # Determine the currently-installed stable toolchain version (e.g., "1.81.0") + latest_stable="$(rustup run stable rustc --version 2>/dev/null | awk '{print $2}')" + if [[ -z "$latest_stable" ]]; then latest_stable="1.81.0"; fi + + # Search for the actual MSRV starting from the current one + if actual_msrv=$(cargo msrv find \ + --manifest-path "$package_dir/Cargo.toml" \ + --min "$current_msrv" \ + --max "$latest_stable" \ + --output-format minimal 2>/dev/null); then + echo " Found actual MSRV: $actual_msrv" + echo "" + echo "ERROR SUMMARY for $package_name:" + echo " Package: $package_name" + echo " Directory: $package_dir" + echo " Current (incorrect) MSRV: $current_msrv" + echo " Correct MSRV: $actual_msrv" + echo "" + echo "TO FIX:" + echo " Update rust-version in $package_dir/Cargo.toml from \"$current_msrv\" to \"$actual_msrv\"" + echo "" + echo " Or run this command (portable in-place edit):" + echo " sed_i 's/^\\s*rust-version\\s*=\\s*\"$current_msrv\"/rust-version = \"$actual_msrv\"/' \"$package_dir/Cargo.toml\"" + else + echo " Could not determine correct MSRV automatically" + echo "" + echo "ERROR SUMMARY for $package_name:" + echo " Package: $package_name" + echo " Directory: $package_dir" + echo " Current (incorrect) MSRV: $current_msrv" + echo " Could not automatically determine correct MSRV" + echo "" + echo "TO FIX:" + echo " Run manually: cargo msrv find --manifest-path \"$package_dir/Cargo.toml\"" + fi + echo "-------------------------------------------------------------------------------" + else + echo "OK: MSRV check passed for $package_name" + fi + echo "" + +done < <( + printf '%s' "$metadata_json" \ + | jq -r '. as $m + | $m.workspace_members[] + | . as $id + | ($m.packages[] | select(.id == $id) + | [ .id, .name, .manifest_path, (.rust_version // "") ] | @tsv)' +) + +if [[ -n "$failed_packages" ]]; then + echo "MSRV CHECK FAILED" + echo "" + echo "The following packages have incorrect MSRV settings:$failed_packages" + echo "" + echo "Please fix the rust-version fields in the affected Cargo.toml files as shown above." + exit 1 +else + echo "ALL WORKSPACE MEMBERS PASSED MSRV CHECKS!" + exit 0 +fi \ No newline at end of file From 5035714d11116c6fa6dd90d4ab20d29f821fd13d Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Wed, 1 Oct 2025 14:05:10 +0530 Subject: [PATCH 064/133] Change the signature of TransactionAuthenticator to return the native signature (#1945) * feat: Change the signature of TransactionAuthenticator to return the native signature * fix: add changelog * Apply suggestions from code review --------- Co-authored-by: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com> Co-authored-by: Marti --- CHANGELOG.md | 1 + crates/miden-testing/tests/auth/multisig.rs | 18 ++++++++++++------ crates/miden-tx/src/auth/tx_authenticator.rs | 10 +++++----- crates/miden-tx/src/executor/exec_host.rs | 3 ++- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b3d3fc81..7f58f41fe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ - [BREAKING] Remove `MockChain::add_pending_note` to simplify mock chain internals ([#1903](https://github.com/0xMiden/miden-base/pull/1903)). - [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). - [BREAKING] Remove account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). +- [BREAKING] Change the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). - [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). ## 0.11.4 (2025-09-17) diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index 71e645a44d..8dfd51832d 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -142,10 +142,12 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { let sig_1 = authenticators[0] .get_signature(public_keys[0].to_commitment(), &tx_summary) - .await?; + .await? + .to_prepared_signature(); let sig_2 = authenticators[1] .get_signature(public_keys[1].to_commitment(), &tx_summary) - .await?; + .await? + .to_prepared_signature(); // Execute transaction with signatures - should succeed let tx_context_execute = mock_chain @@ -224,10 +226,12 @@ async fn test_multisig_2_of_4_all_signer_combinations() -> anyhow::Result<()> { let sig_1 = authenticators[*signer1_idx] .get_signature(public_keys[*signer1_idx].to_commitment(), &tx_summary) - .await?; + .await? + .to_prepared_signature(); let sig_2 = authenticators[*signer2_idx] .get_signature(public_keys[*signer2_idx].to_commitment(), &tx_summary) - .await?; + .await? + .to_prepared_signature(); // Execute transaction with signatures - should succeed for any combination let tx_context_execute = mock_chain @@ -291,10 +295,12 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { let sig_1 = authenticators[0] .get_signature(public_keys[0].to_commitment(), &tx_summary) - .await?; + .await? + .to_prepared_signature(); let sig_2 = authenticators[1] .get_signature(public_keys[1].to_commitment(), &tx_summary) - .await?; + .await? + .to_prepared_signature(); // Execute transaction with signatures - should succeed (first execution) let tx_context_execute = mock_chain diff --git a/crates/miden-tx/src/auth/tx_authenticator.rs b/crates/miden-tx/src/auth/tx_authenticator.rs index 507613cc38..b248dab9c3 100644 --- a/crates/miden-tx/src/auth/tx_authenticator.rs +++ b/crates/miden-tx/src/auth/tx_authenticator.rs @@ -141,7 +141,7 @@ pub trait TransactionAuthenticator { &self, pub_key: Word, signing_inputs: &SigningInputs, - ) -> impl FutureMaybeSend, AuthenticationError>>; + ) -> impl FutureMaybeSend>; } /// A placeholder type for the generic trait bound of `TransactionAuthenticator<'_,'_,_,T>` @@ -160,7 +160,7 @@ impl TransactionAuthenticator for UnreachableAuth { &self, _pub_key: Word, _signing_inputs: &SigningInputs, - ) -> impl FutureMaybeSend, AuthenticationError>> { + ) -> impl FutureMaybeSend> { async { unreachable!("Type `UnreachableAuth` must not be instantiated") } } } @@ -219,7 +219,7 @@ impl TransactionAuthenticator for BasicAuthenticator { &self, pub_key: Word, signing_inputs: &SigningInputs, - ) -> impl FutureMaybeSend, AuthenticationError>> { + ) -> impl FutureMaybeSend> { let message = signing_inputs.to_commitment(); async move { @@ -231,7 +231,7 @@ impl TransactionAuthenticator for BasicAuthenticator { falcon_key.sign_with_rng(message, &mut *rng).into() }, }; - Ok(signature.to_prepared_signature()) + Ok(signature) }, None => Err(AuthenticationError::UnknownPublicKey(format!( "public key {pub_key} is not contained in the authenticator's keys", @@ -250,7 +250,7 @@ impl TransactionAuthenticator for () { &self, _pub_key: Word, _signing_inputs: &SigningInputs, - ) -> impl FutureMaybeSend, AuthenticationError>> { + ) -> impl FutureMaybeSend> { async { Err(AuthenticationError::RejectedSignature( "default authenticator cannot provide signatures".to_string(), diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 065505ce06..d0eb988787 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -184,7 +184,8 @@ where let signature: Vec = authenticator .get_signature(pub_key_hash, &signing_inputs) .await - .map_err(TransactionKernelError::SignatureGenerationFailed)?; + .map_err(TransactionKernelError::SignatureGenerationFailed)? + .to_prepared_signature(); let signature_key = Hasher::merge(&[pub_key_hash, signing_inputs.to_commitment()]); From 89827d30adb962a87a0f791cc7402401152414e0 Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:00:04 +0300 Subject: [PATCH 065/133] Add functionality to update owner public keys & threshold config in multisig (#1871) --- CHANGELOG.md | 1 + .../multisig_rpo_falcon_512.masm | 155 ++++- .../account/auth/rpo_falcon_512_multisig.rs | 4 +- .../miden-lib/src/account/components/mod.rs | 4 +- .../src/account/interface/component.rs | 12 +- crates/miden-lib/src/account/interface/mod.rs | 7 +- .../miden-lib/src/account/interface/test.rs | 23 +- .../src/errors/note_script_errors.rs | 6 + .../src/testing/account_interface.rs | 28 + crates/miden-lib/src/testing/mod.rs | 1 + crates/miden-testing/tests/auth/multisig.rs | 596 +++++++++++++++++- 11 files changed, 798 insertions(+), 39 deletions(-) create mode 100644 crates/miden-lib/src/testing/account_interface.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f58f41fe0..fe928c43f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [BREAKING] Enabled lazy loading of assets and storage map items for foreign accounts during transaction execution ([#1888](https://github.com/0xMiden/miden-base/pull/1888)). - Added `get_item_init` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). - Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) +- Added `update_signers_and_threshold` procedure to update owner public keys and threshold config in multisig authentication component ([#1707](https://github.com/0xMiden/miden-base/issues/1707)). - Implement `SlotName` for named storage slots ([#1932](https://github.com/0xMiden/miden-base/issues/1932)) - [BREAKING] Removed `get_falcon_signature` from `miden-tx` crate ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Created a `Signature` wrapper to simplify the preparation of "native" signatures for use in the VM ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). diff --git a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm index 7fcd47c845..940a2617f8 100644 --- a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm @@ -42,6 +42,10 @@ const.IS_EXECUTED_FLAG=[1, 0, 0, 0] # ERRORS const.ERR_TX_ALREADY_EXECUTED="failed to approve multisig transaction as it was already executed" +const.ERR_MALFORMED_MULTISIG_CONFIG="number of approvers must be equal to or greater than threshold" + +const.ERR_ZERO_IN_MULTISIG_CONFIG="number of approvers or threshold must not be zero" + #! Check if transaction has already been executed and add it to executed transactions for replay protection. #! #! Inputs: [MSG] @@ -70,6 +74,155 @@ proc.assert_new_tx # => [] end +#! Remove old owner public keys from the owner public key mapping. +#! +#! This procedure cleans up the storage by removing public keys of owners that are no longer +#! part of the multisig configuration. This procedure assumes that init_num_of_approvers and +#! new_num_of_approvers are u32 values. +#! +#! Inputs: [init_num_of_approvers, new_num_of_approvers] +#! Outputs: [] +#! +#! Where: +#! - init_num_of_approvers is the original number of approvers before the update +#! - new_num_of_approvers is the new number of approvers after the update +proc.cleanup_pubkey_mapping + dup.1 dup.1 + u32assert2 u32lt + # => [should_loop, i = init_num_of_approvers, new_num_of_approvers] + + while.true + # => [i, new_num_of_approvers] + + sub.1 + # => [i-1, new_num_of_approvers] + + dup + # => [i-1, i-1, new_num_of_approvers] + + push.0.0.0 + # => [[0, 0, 0, i-1], i-1, new_num_of_approvers] + + padw swapw + # => [[0, 0, 0, i-1], EMPTY_WORD, i-1, new_num_of_approvers] + + push.PUBLIC_KEYS_MAP_SLOT + # => [pub_key_slot_idx, [0, 0, 0, i-1], EMPTY_WORD, i-1, new_num_of_approvers] + + exec.account::set_map_item + # => [OLD_MAP_ROOT, OLD_MAP_VALUE, i-1, new_num_of_approvers] + + dropw dropw + # => [i-1, new_num_of_approvers] + + dup.1 dup.1 + u32lt + # => [should_loop, i-1, new_num_of_approvers] + end + + drop drop + # => [] +end + +#! Update threshold config and add / remove owners +#! +#! Inputs: +#! Operand stack: [MULTISIG_CONFIG_HASH, pad(12)] +#! Advice map: { +#! MULTISIG_CONFIG_HASH => [CONFIG, PUB_KEY_N, PUB_KEY_N-1, ..., PUB_KEY_0] +#! } +#! Outputs: +#! Operand stack: [] +#! +#! Where: +#! - MULTISIG_CONFIG_HASH is the hash of the threshold and new public key vector +#! - MULTISIG_CONFIG is [threshold, num_approvers, 0, 0] +#! - PUB_KEY_i is the public key of the i-th signer +#! +#! Locals: +#! 0: new_num_of_approvers +#! 1: init_num_of_approvers +export.update_signers_and_threshold.2 + adv.push_mapval + # => [MULTISIG_CONFIG_HASH, pad(12)] + + adv_loadw + # => [MULTISIG_CONFIG, pad(12)] + + # store new_num_of_approvers for later + dup.2 loc_store.0 + # => [MULTISIG_CONFIG, pad(12)] + + dup.3 dup.3 + # => [num_approvers, threshold, MULTISIG_CONFIG, pad(12)] + + # make sure that the threshold is smaller than the number of approvers + u32assert2.err=ERR_MALFORMED_MULTISIG_CONFIG + u32gt assertz.err=ERR_MALFORMED_MULTISIG_CONFIG + # => [MULTISIG_CONFIG, pad(12)] + + dup.3 dup.3 + # => [num_approvers, threshold, MULTISIG_CONFIG, pad(12)] + + # make sure that threshold or num_approvers are not zero + eq.0 assertz.err=ERR_ZERO_IN_MULTISIG_CONFIG + eq.0 assertz.err=ERR_ZERO_IN_MULTISIG_CONFIG + # => [MULTISIG_CONFIG, pad(12)] + + push.THRESHOLD_CONFIG_SLOT + # => [slot, MULTISIG_CONFIG, pad(12)] + + exec.account::set_item + # => [OLD_THRESHOLD_CONFIG, pad(12)] + + # store init_num_of_approvers for later + drop drop loc_store.1 drop + # => [pad(12)] + + loc_load.0 + # => [num_approvers] + + dup neq.0 + while.true + sub.1 + # => [i-1, pad(12)] + + dup push.0.0.0 + # => [[0, 0, 0, i-1], i-1, pad(12)] + + padw adv_loadw + # => [PUB_KEY, [0, 0, 0, i-1], i-1, pad(12)] + + swapw + # => [[0, 0, 0, i-1], PUB_KEY, i-1, pad(12)] + + push.PUBLIC_KEYS_MAP_SLOT + # => [pub_key_slot_idx, [0, 0, 0, i-1], PUB_KEY, i-1, pad(12)] + + exec.account::set_map_item + # => [OLD_MAP_ROOT, OLD_MAP_VALUE, i-1, pad(12)] + + dropw dropw + # => [i-1, pad(12)] + + dup neq.0 + # => [is_non_zero, i-1, pad(12)] + end + # => [pad(13)] + + drop + # => [pad(12)] + + # compare initial vs current multisig config + + # load init_num_of_approvers & new_num_of_approvers + loc_load.0 loc_load.1 + # => [init_num_of_approvers, new_num_of_approvers, pad(12)] + + exec.cleanup_pubkey_mapping + # => [pad(12)] +end + #! Authenticate a transaction using the Falcon signature scheme with multi-signature support. #! #! This procedure implements multi-signature authentication by: @@ -123,7 +276,7 @@ export.auth_tx_rpo_falcon512_multisig push.THRESHOLD_CONFIG_SLOT # => [index, TX_SUMMARY_COMMITMENT] - exec.account::get_item + exec.account::get_item_init # => [0, 0, num_of_approvers, threshold, TX_SUMMARY_COMMITMENT] drop drop diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs index 81ac08b9a5..df441dc6be 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs @@ -4,7 +4,7 @@ use miden_objects::account::{AccountComponent, StorageMap, StorageSlot}; use miden_objects::{AccountError, Word}; use crate::account::auth::PublicKeyCommitment; -use crate::account::components::multisig_library; +use crate::account::components::rpo_falcon_512_multisig_library; // MULTISIG AUTHENTICATION COMPONENT // ================================================================================================ @@ -73,7 +73,7 @@ impl From for AccountComponent { let executed_transactions = StorageMap::default(); storage_slots.push(StorageSlot::Map(executed_transactions)); - AccountComponent::new(multisig_library(), storage_slots) + AccountComponent::new(rpo_falcon_512_multisig_library(), storage_slots) .expect("Multisig auth component should satisfy the requirements of a valid account component") .with_supports_all_types() } diff --git a/crates/miden-lib/src/account/components/mod.rs b/crates/miden-lib/src/account/components/mod.rs index 749b8b12cf..e433c9ad36 100644 --- a/crates/miden-lib/src/account/components/mod.rs +++ b/crates/miden-lib/src/account/components/mod.rs @@ -82,8 +82,8 @@ pub fn no_auth_library() -> Library { NO_AUTH_LIBRARY.clone() } -/// Returns the Multisig Library. -pub fn multisig_library() -> Library { +/// Returns the RPO Falcon 512 Multisig Library. +pub fn rpo_falcon_512_multisig_library() -> Library { RPO_FALCON_512_MULTISIG_LIBRARY.clone() } diff --git a/crates/miden-lib/src/account/interface/component.rs b/crates/miden-lib/src/account/interface/component.rs index 1cf9aa929b..66815e1da4 100644 --- a/crates/miden-lib/src/account/interface/component.rs +++ b/crates/miden-lib/src/account/interface/component.rs @@ -289,13 +289,18 @@ fn extract_multisig_auth_scheme(storage: &AccountStorage, storage_index: u8) -> let threshold = config[0].as_int() as u32; let num_approvers = config[1].as_int() as u8; - // The public keys are stored in a map at the next slot (storage_index + 1) + // The multisig component has a fixed storage layout: + // - Slot 0: [threshold, num_approvers, 0, 0] + // - Slot 1: Map with public keys + // - Slot 2: Map with executed transactions + // The public keys are always stored in slot 1, regardless of storage_index let pub_keys_map_slot = storage_index + 1; let mut pub_keys = Vec::new(); // Read each public key from the map for key_index in 0..num_approvers { + // The multisig component stores keys using pattern [index, 0, 0, 0] let map_key = [Felt::new(key_index as u64), Felt::ZERO, Felt::ZERO, Felt::ZERO]; match storage.get_map_item(pub_keys_map_slot, map_key.into()) { @@ -305,9 +310,10 @@ fn extract_multisig_auth_scheme(storage: &AccountStorage, storage_index: u8) -> Err(_) => { // If we can't read a public key, panic with a clear error message panic!( - "Failed to read public key {} from multisig configuration at storage index {}. \ + "Failed to read public key {} from multisig configuration at storage slot {}. \ + Expected key pattern [index, 0, 0, 0]. \ This indicates corrupted multisig storage or incorrect storage layout.", - key_index, storage_index + key_index, pub_keys_map_slot ); }, } diff --git a/crates/miden-lib/src/account/interface/mod.rs b/crates/miden-lib/src/account/interface/mod.rs index bca596435a..7d6666a6b7 100644 --- a/crates/miden-lib/src/account/interface/mod.rs +++ b/crates/miden-lib/src/account/interface/mod.rs @@ -15,10 +15,10 @@ use crate::AuthScheme; use crate::account::components::{ basic_fungible_faucet_library, basic_wallet_library, - multisig_library, no_auth_library, rpo_falcon_512_acl_library, rpo_falcon_512_library, + rpo_falcon_512_multisig_library, }; use crate::errors::ScriptBuilderError; use crate::note::well_known_note::WellKnownNote; @@ -149,8 +149,9 @@ impl AccountInterface { .extend(rpo_falcon_512_acl_library().mast_forest().procedure_digests()); }, AccountComponentInterface::AuthRpoFalcon512Multisig(_) => { - component_proc_digests - .extend(multisig_library().mast_forest().procedure_digests()); + component_proc_digests.extend( + rpo_falcon_512_multisig_library().mast_forest().procedure_digests(), + ); }, AccountComponentInterface::AuthNoAuth => { component_proc_digests diff --git a/crates/miden-lib/src/account/interface/test.rs b/crates/miden-lib/src/account/interface/test.rs index 2cffe34064..e27e61d452 100644 --- a/crates/miden-lib/src/account/interface/test.rs +++ b/crates/miden-lib/src/account/interface/test.rs @@ -3,7 +3,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use assert_matches::assert_matches; -use miden_objects::account::{Account, AccountBuilder, AccountComponent, AccountType, StorageSlot}; +use miden_objects::account::{AccountBuilder, AccountComponent, AccountType, StorageSlot}; use miden_objects::assembly::diagnostics::NamedSource; use miden_objects::assembly::{Assembler, DefaultSourceManager}; use miden_objects::asset::{FungibleAsset, NonFungibleAsset, TokenSymbol}; @@ -39,6 +39,7 @@ use crate::account::interface::{ }; use crate::account::wallets::BasicWallet; use crate::note::{create_p2id_note, create_p2ide_note, create_swap_note}; +use crate::testing::account_interface::get_public_keys_from_account; use crate::transaction::TransactionKernel; use crate::utils::ScriptBuilder; @@ -871,26 +872,6 @@ fn test_account_interface_get_auth_scheme() { // accounts are required to have auth components in the current system design } -fn get_public_keys_from_account(account: &Account) -> Vec { - let mut pub_keys = vec![]; - let interface: AccountInterface = account.into(); - - for auth in interface.auth() { - match auth { - AuthScheme::NoAuth => {}, - AuthScheme::RpoFalcon512 { pub_key } => pub_keys.push(Word::from(*pub_key)), - AuthScheme::RpoFalcon512Multisig { pub_keys: multisig_keys, .. } => { - for key in multisig_keys { - pub_keys.push(Word::from(*key)); - } - }, - AuthScheme::Unknown => {}, - } - } - - pub_keys -} - #[test] fn test_public_key_extraction_regular_account() { let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes(); diff --git a/crates/miden-lib/src/errors/note_script_errors.rs b/crates/miden-lib/src/errors/note_script_errors.rs index 953761897d..0de640e957 100644 --- a/crates/miden-lib/src/errors/note_script_errors.rs +++ b/crates/miden-lib/src/errors/note_script_errors.rs @@ -13,6 +13,9 @@ use crate::errors::MasmError; /// Error Message: "auth procedure had been called from outside the epilogue" pub const ERR_AUTH_PROCEDURE_CALLED_FROM_WRONG_CONTEXT: MasmError = MasmError::from_static_str("auth procedure had been called from outside the epilogue"); +/// Error Message: "number of approvers must be equal to or greater than threshold" +pub const ERR_MALFORMED_MULTISIG_CONFIG: MasmError = MasmError::from_static_str("number of approvers must be equal to or greater than threshold"); + /// Error Message: "failed to reclaim P2IDE note because the reclaiming account is not the sender" pub const ERR_P2IDE_RECLAIM_ACCT_IS_NOT_SENDER: MasmError = MasmError::from_static_str("failed to reclaim P2IDE note because the reclaiming account is not the sender"); /// Error Message: "P2IDE reclaim is disabled" @@ -33,3 +36,6 @@ pub const ERR_P2ID_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_st pub const ERR_SWAP_WRONG_NUMBER_OF_ASSETS: MasmError = MasmError::from_static_str("SWAP script requires exactly 1 note asset"); /// Error Message: "SWAP script expects exactly 12 note inputs" pub const ERR_SWAP_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("SWAP script expects exactly 12 note inputs"); + +/// Error Message: "number of approvers or threshold must not be zero" +pub const ERR_ZERO_IN_MULTISIG_CONFIG: MasmError = MasmError::from_static_str("number of approvers or threshold must not be zero"); diff --git a/crates/miden-lib/src/testing/account_interface.rs b/crates/miden-lib/src/testing/account_interface.rs new file mode 100644 index 0000000000..082b006f03 --- /dev/null +++ b/crates/miden-lib/src/testing/account_interface.rs @@ -0,0 +1,28 @@ +use alloc::vec::Vec; + +use miden_objects::Word; +use miden_objects::account::Account; + +use crate::AuthScheme; +use crate::account::interface::AccountInterface; + +/// Helper function to extract public keys from an account +pub fn get_public_keys_from_account(account: &Account) -> Vec { + let mut pub_keys = vec![]; + let interface: AccountInterface = account.into(); + + for auth in interface.auth() { + match auth { + AuthScheme::NoAuth => {}, + AuthScheme::RpoFalcon512 { pub_key } => pub_keys.push(Word::from(*pub_key)), + AuthScheme::RpoFalcon512Multisig { pub_keys: multisig_keys, .. } => { + for key in multisig_keys { + pub_keys.push(Word::from(*key)); + } + }, + AuthScheme::Unknown => {}, + } + } + + pub_keys +} diff --git a/crates/miden-lib/src/testing/mod.rs b/crates/miden-lib/src/testing/mod.rs index 2ae6333ac9..fa43a14205 100644 --- a/crates/miden-lib/src/testing/mod.rs +++ b/crates/miden-lib/src/testing/mod.rs @@ -1,4 +1,5 @@ pub mod account_component; +pub mod account_interface; pub mod mock_account; pub mod mock_account_code; pub mod mock_util_lib; diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index 8dfd51832d..fbec199b37 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -1,5 +1,9 @@ +use miden_lib::account::components::rpo_falcon_512_multisig_library; use miden_lib::account::wallets::BasicWallet; use miden_lib::errors::tx_kernel_errors::ERR_TX_ALREADY_EXECUTED; +use miden_lib::note::create_p2id_note; +use miden_lib::testing::account_interface::get_public_keys_from_account; +use miden_lib::utils::ScriptBuilder; use miden_objects::account::{ Account, AccountBuilder, @@ -16,7 +20,11 @@ use miden_objects::testing::account_id::{ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, }; use miden_objects::transaction::OutputNote; -use miden_objects::{Felt, Word}; +use miden_objects::vm::AdviceMap; +use miden_objects::{Felt, Hasher, Word}; +use miden_processor::AdviceInputs; +use miden_processor::crypto::RpoRandomCoin; +use miden_testing::utils::create_spawn_note; use miden_testing::{Auth, MockChainBuilder, assert_transaction_executor_error}; use miden_tx::TransactionExecutorError; use miden_tx::auth::{BasicAuthenticator, SigningInputs, TransactionAuthenticator}; @@ -34,7 +42,8 @@ fn setup_keys_and_authenticators( num_approvers: usize, threshold: usize, ) -> anyhow::Result { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let seed: [u8; 32] = rand::random(); + let mut rng = ChaCha20Rng::from_seed(seed); let mut secret_keys = Vec::new(); let mut public_keys = Vec::new(); @@ -48,7 +57,7 @@ fn setup_keys_and_authenticators( public_keys.push(pub_key); } - // Create authenticators only for the signers we'll actually use + // Create authenticators for required signers for i in 0..threshold { let authenticator = BasicAuthenticator::::new_with_rng( &[( @@ -109,7 +118,7 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { let mut mock_chain_builder = MockChainBuilder::with_accounts([multisig_account.clone()]).unwrap(); - // Create output note using add_p2id_note for spawn note + // Create output note for spawn note let output_note = mock_chain_builder.add_p2id_note( multisig_account.id(), ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE.try_into().unwrap(), @@ -117,7 +126,7 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { NoteType::Public, )?; - // Create spawn note that will create the output note + // Create spawn note to generate the output note let input_note = mock_chain_builder.add_spawn_note([&output_note])?; let mut mock_chain = mock_chain_builder.build().unwrap(); @@ -316,7 +325,7 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { mock_chain.add_pending_executed_transaction(&executed_tx)?; mock_chain.prove_next_block()?; - // Now attempt to execute the same transaction again - should fail due to replay protection + // Attempt to execute the same transaction again - should fail due to replay protection let tx_context_replay = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? .add_signature(public_keys[0].clone(), msg, sig_1) @@ -324,9 +333,582 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { .auth_args(salt) .build()?; - // This should fail - due to replay protection + // This should fail due to replay protection let result = tx_context_replay.execute().await; assert_transaction_executor_error!(result, ERR_TX_ALREADY_EXECUTED); Ok(()) } + +/// Tests multisig signer update functionality. +/// +/// This test verifies that a multisig account can: +/// 1. Execute a transaction script to update signers and threshold +/// 2. Create a second transaction signed by the new owners +/// 3. Properly handle multisig authentication with the updated signers +/// +/// **Roles:** +/// - 2 Original Approvers (multisig signers) +/// - 4 New Approvers (updated multisig signers) +/// - 1 Multisig Contract +/// - 1 Transaction Script calling multisig procedures +#[tokio::test] +async fn test_multisig_update_signers() -> anyhow::Result<()> { + let (_secret_keys, public_keys, authenticators) = setup_keys_and_authenticators(2, 2)?; + + let multisig_account = create_multisig_account(2, &public_keys, 10)?; + + // SECTION 1: Execute a transaction script to update signers and threshold + // ================================================================================ + + let mut mock_chain_builder = + MockChainBuilder::with_accounts([multisig_account.clone()]).unwrap(); + + let output_note_asset = FungibleAsset::mock(0); + + // Create output note for spawn note + let output_note = mock_chain_builder.add_p2id_note( + multisig_account.id(), + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE.try_into().unwrap(), + &[output_note_asset], + NoteType::Public, + )?; + + let mut mock_chain = mock_chain_builder.clone().build().unwrap(); + + let salt = Word::from([Felt::new(3); 4]); + + // Setup new signers + let mut advice_map = AdviceMap::default(); + let (_new_secret_keys, new_public_keys, _new_authenticators) = + setup_keys_and_authenticators(4, 4)?; + + let threshold = 3u64; + let num_of_approvers = 4u64; + + // Create vector with threshold config and public keys (4 field elements each) + let mut config_and_pubkeys_vector = Vec::new(); + config_and_pubkeys_vector.extend_from_slice(&[ + Felt::new(threshold), + Felt::new(num_of_approvers), + Felt::new(0), + Felt::new(0), + ]); + + // Add each public key to the vector + for public_key in new_public_keys.iter().rev() { + let key_word: Word = public_key.to_commitment(); + config_and_pubkeys_vector.extend_from_slice(key_word.as_elements()); + } + + // Hash the vector to create config hash + let multisig_config_hash = Hasher::hash_elements(&config_and_pubkeys_vector); + + // Insert config and public keys into advice map + advice_map.insert(multisig_config_hash, config_and_pubkeys_vector); + + // Create a transaction script that calls the update_signers procedure + let tx_script_code = " + begin + call.::update_signers_and_threshold + end + "; + + let tx_script = ScriptBuilder::new(true) + .with_dynamically_linked_library(&rpo_falcon_512_multisig_library())? + .compile_tx_script(tx_script_code)?; + + let advice_inputs = AdviceInputs { + map: advice_map.clone(), + ..Default::default() + }; + + // Pass the MULTISIG_CONFIG_HASH as the tx_script_args + let tx_script_args: Word = multisig_config_hash; + + // Execute transaction without signatures first to get tx summary + let tx_context_init = mock_chain + .build_tx_context(multisig_account.id(), &[], &[])? + .tx_script(tx_script.clone()) + .tx_script_args(tx_script_args) + .extend_advice_inputs(advice_inputs.clone()) + .auth_args(salt) + .build()?; + + let tx_summary = match tx_context_init.execute().await.unwrap_err() { + TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, + error => panic!("expected abort with tx effects: {error:?}"), + }; + + // Get signatures from both approvers + let msg = tx_summary.as_ref().to_commitment(); + let tx_summary = SigningInputs::TransactionSummary(tx_summary); + + let sig_1 = authenticators[0] + .get_signature(public_keys[0].to_commitment(), &tx_summary) + .await?; + let sig_2 = authenticators[1] + .get_signature(public_keys[1].to_commitment(), &tx_summary) + .await?; + + // Execute transaction with signatures - should succeed + let update_approvers_tx = mock_chain + .build_tx_context(multisig_account.id(), &[], &[])? + .tx_script(tx_script) + .tx_script_args(multisig_config_hash) + .add_signature(public_keys[0].clone(), msg, sig_1) + .add_signature(public_keys[1].clone(), msg, sig_2) + .auth_args(salt) + .extend_advice_inputs(advice_inputs) + .build()? + .execute() + .await + .unwrap(); + + // Verify the transaction executed successfully + assert_eq!(update_approvers_tx.account_delta().nonce_delta(), Felt::new(1)); + + mock_chain.add_pending_executed_transaction(&update_approvers_tx)?; + mock_chain.prove_next_block()?; + + // Apply the delta to get the updated account with new signers + let mut updated_multisig_account = multisig_account.clone(); + updated_multisig_account.apply_delta(update_approvers_tx.account_delta())?; + + // Verify that the public keys were actually updated in storage + for (i, expected_key) in new_public_keys.iter().enumerate() { + let storage_key = [Felt::new(i as u64), Felt::new(0), Felt::new(0), Felt::new(0)].into(); + let storage_item = updated_multisig_account.storage().get_map_item(1, storage_key).unwrap(); + + let expected_word: Word = expected_key.to_commitment(); + + assert_eq!(storage_item, expected_word, "Public key {} doesn't match expected value", i); + } + + // Verify the threshold was updated by checking storage slot 0 + let threshold_config_storage = updated_multisig_account.storage().get_item(0).unwrap(); + + assert_eq!( + threshold_config_storage[0], + Felt::new(threshold), + "Threshold was not updated correctly" + ); + assert_eq!( + threshold_config_storage[1], + Felt::new(num_of_approvers), + "Num approvers was not updated correctly" + ); + + // Extract public keys using the interface function + let extracted_pub_keys = get_public_keys_from_account(&updated_multisig_account); + + // Verify that we have the expected number of public keys (4 new ones) + assert_eq!( + extracted_pub_keys.len(), + 4, + "get_public_keys_from_account should return 4 public keys after update" + ); + + // Verify that the extracted public keys match the new ones we set + for (i, expected_key) in new_public_keys.iter().enumerate() { + let expected_word: Word = expected_key.to_commitment(); + + // Find the matching key in extracted keys (order might be different) + let found_key = extracted_pub_keys.iter().find(|&key| *key == expected_word); + + assert!( + found_key.is_some(), + "Public key {} not found in extracted keys: expected {:?}, got {:?}", + i, + expected_word, + extracted_pub_keys + ); + } + + // SECTION 2: Create a second transaction signed by the new owners + // ================================================================================ + + // Now test creating a note with the new signers + // Setup authenticators for the new signers (we need 3 out of 4 for threshold 3) + let mut new_authenticators = Vec::new(); + for i in 0..3 { + let authenticator = BasicAuthenticator::::new_with_rng( + &[( + new_public_keys[i].to_commitment(), + AuthSecretKey::RpoFalcon512(_new_secret_keys[i].clone()), + )], + ChaCha20Rng::from_seed([0u8; 32]), + ); + new_authenticators.push(authenticator); + } + + // Create a new output note for the second transaction with new signers + let output_note_new = create_p2id_note( + updated_multisig_account.id(), + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE.try_into().unwrap(), + vec![output_note_asset], + NoteType::Public, + Default::default(), + &mut RpoRandomCoin::new(Word::empty()), + )?; + + // Create a new spawn note for the second transaction + let input_note_new = create_spawn_note([&output_note_new])?; + + let salt_new = Word::from([Felt::new(4); 4]); + + // Build the new mock chain with the updated account and notes + let mut new_mock_chain_builder = + MockChainBuilder::with_accounts([updated_multisig_account.clone()]).unwrap(); + new_mock_chain_builder.add_output_note(OutputNote::Full(input_note_new.clone())); + let new_mock_chain = new_mock_chain_builder.build().unwrap(); + + // Execute transaction without signatures first to get tx summary + let tx_context_init_new = new_mock_chain + .build_tx_context(updated_multisig_account.id(), &[input_note_new.id()], &[])? + .extend_expected_output_notes(vec![OutputNote::Full(output_note.clone())]) + .auth_args(salt_new) + .build()?; + + let tx_summary_new = match tx_context_init_new.execute().await.unwrap_err() { + TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, + error => panic!("expected abort with tx effects: {error:?}"), + }; + + // Get signatures from 3 of the 4 new approvers (threshold is 3) + let msg_new = tx_summary_new.as_ref().to_commitment(); + let tx_summary_new = SigningInputs::TransactionSummary(tx_summary_new); + + let sig_1_new = new_authenticators[0] + .get_signature(new_public_keys[0].to_commitment(), &tx_summary_new) + .await?; + let sig_2_new = new_authenticators[1] + .get_signature(new_public_keys[1].to_commitment(), &tx_summary_new) + .await?; + let sig_3_new = new_authenticators[2] + .get_signature(new_public_keys[2].to_commitment(), &tx_summary_new) + .await?; + + // SECTION 3: Properly handle multisig authentication with the updated signers + // ================================================================================ + + // Execute transaction with new signatures - should succeed + let tx_context_execute_new = new_mock_chain + .build_tx_context(updated_multisig_account.id(), &[input_note_new.id()], &[])? + .extend_expected_output_notes(vec![OutputNote::Full(output_note_new)]) + .add_signature(new_public_keys[0].clone(), msg_new, sig_1_new) + .add_signature(new_public_keys[1].clone(), msg_new, sig_2_new) + .add_signature(new_public_keys[2].clone(), msg_new, sig_3_new) + .auth_args(salt_new) + .build()? + .execute() + .await?; + + // Verify the transaction executed successfully with new signers + assert_eq!(tx_context_execute_new.account_delta().nonce_delta(), Felt::new(1)); + + Ok(()) +} + +/// Tests multisig signer update functionality with owner removal. +/// +/// This test verifies that a multisig account can: +/// 1. Start with 5 owners and threshold 4 +/// 2. Execute a transaction to remove 3 owners (updating to 2 owners) +/// 3. Verify that all removed owners' storage slots are properly cleared +/// +/// **Roles:** +/// - 5 Original Approvers (multisig signers, threshold 4) +/// - 2 Updated Approvers (after removing 3 owners) +/// - 1 Multisig Contract +/// - 1 Transaction Script calling multisig procedures +#[tokio::test] +async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { + // Setup 5 original owners with threshold 4 + let (_secret_keys, public_keys, authenticators) = setup_keys_and_authenticators(5, 5)?; + let multisig_account = create_multisig_account(4, &public_keys, 10)?; + + // Build mock chain + let mock_chain_builder = MockChainBuilder::with_accounts([multisig_account.clone()]).unwrap(); + let mut mock_chain = mock_chain_builder.build().unwrap(); + + // Setup new signers (remove the last 3 owners, keeping first 2) + let new_public_keys = &public_keys[0..2]; + let threshold = 1u64; + let num_of_approvers = 2u64; + + // Create multisig config vector + let mut config_and_pubkeys_vector = + vec![Felt::new(threshold), Felt::new(num_of_approvers), Felt::new(0), Felt::new(0)]; + + // Add public keys in reverse order + for public_key in new_public_keys.iter().rev() { + let key_word: Word = public_key.to_commitment(); + config_and_pubkeys_vector.extend_from_slice(key_word.as_elements()); + } + + // Create config hash and advice map + let multisig_config_hash = Hasher::hash_elements(&config_and_pubkeys_vector); + let mut advice_map = AdviceMap::default(); + advice_map.insert(multisig_config_hash, config_and_pubkeys_vector); + + // Create transaction script + let tx_script = ScriptBuilder::new(true) + .with_dynamically_linked_library(&rpo_falcon_512_multisig_library())? + .compile_tx_script("begin\n call.::update_signers_and_threshold\nend")?; + + let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() }; + + let salt = Word::from([Felt::new(3); 4]); + + // Execute without signatures to get tx summary + let tx_context_init = mock_chain + .build_tx_context(multisig_account.id(), &[], &[])? + .tx_script(tx_script.clone()) + .tx_script_args(multisig_config_hash) + .extend_advice_inputs(advice_inputs.clone()) + .auth_args(salt) + .build()?; + + let tx_summary = match tx_context_init.execute().await.unwrap_err() { + TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, + error => panic!("expected abort with tx effects: {error:?}"), + }; + + // Get signatures from 4 of the 5 original approvers (threshold is 4) + let msg = tx_summary.as_ref().to_commitment(); + let tx_summary = SigningInputs::TransactionSummary(tx_summary); + + let sig_1 = authenticators[0] + .get_signature(public_keys[0].to_commitment(), &tx_summary) + .await?; + let sig_2 = authenticators[1] + .get_signature(public_keys[1].to_commitment(), &tx_summary) + .await?; + let sig_3 = authenticators[2] + .get_signature(public_keys[2].to_commitment(), &tx_summary) + .await?; + let sig_4 = authenticators[3] + .get_signature(public_keys[3].to_commitment(), &tx_summary) + .await?; + + // Execute with signatures + let update_approvers_tx = mock_chain + .build_tx_context(multisig_account.id(), &[], &[])? + .tx_script(tx_script) + .tx_script_args(multisig_config_hash) + .add_signature(public_keys[0].clone(), msg, sig_1) + .add_signature(public_keys[1].clone(), msg, sig_2) + .add_signature(public_keys[2].clone(), msg, sig_3) + .add_signature(public_keys[3].clone(), msg, sig_4) + .auth_args(salt) + .extend_advice_inputs(advice_inputs) + .build()? + .execute() + .await + .unwrap(); + + // Verify transaction success + assert_eq!(update_approvers_tx.account_delta().nonce_delta(), Felt::new(1)); + + mock_chain.add_pending_executed_transaction(&update_approvers_tx)?; + mock_chain.prove_next_block()?; + + // Apply delta to get updated account + let mut updated_multisig_account = multisig_account.clone(); + updated_multisig_account.apply_delta(update_approvers_tx.account_delta())?; + + // Verify public keys were updated + for (i, expected_key) in new_public_keys.iter().enumerate() { + let storage_key = [Felt::new(i as u64), Felt::new(0), Felt::new(0), Felt::new(0)].into(); + let storage_item = updated_multisig_account.storage().get_map_item(1, storage_key).unwrap(); + let expected_word: Word = expected_key.to_commitment(); + assert_eq!(storage_item, expected_word, "Public key {} doesn't match", i); + } + + // Verify threshold and num_approvers + let threshold_config = updated_multisig_account.storage().get_item(0).unwrap(); + assert_eq!(threshold_config[0], Felt::new(threshold), "Threshold not updated"); + assert_eq!(threshold_config[1], Felt::new(num_of_approvers), "Num approvers not updated"); + + // Verify extracted public keys + let extracted_pub_keys = get_public_keys_from_account(&updated_multisig_account); + assert_eq!(extracted_pub_keys.len(), 2, "Should have 2 public keys after update"); + + for expected_key in new_public_keys.iter() { + let expected_word: Word = expected_key.to_commitment(); + assert!( + extracted_pub_keys.contains(&expected_word), + "Public key not found in extracted keys" + ); + } + + // Verify removed owners' slots are empty (indices 2, 3, and 4 should be cleared) + for removed_idx in 2..5 { + let removed_owner_key = + [Felt::new(removed_idx), Felt::new(0), Felt::new(0), Felt::new(0)].into(); + let removed_owner_slot = + updated_multisig_account.storage().get_map_item(1, removed_owner_key).unwrap(); + assert_eq!( + removed_owner_slot, + Word::empty(), + "Removed owner's slot at index {} should be empty", + removed_idx + ); + } + + // Verify only 2 non-empty keys remain (at indices 0 and 1) + let mut non_empty_count = 0; + for i in 0..5 { + let storage_key = [Felt::new(i as u64), Felt::new(0), Felt::new(0), Felt::new(0)].into(); + let storage_item = updated_multisig_account.storage().get_map_item(1, storage_key).unwrap(); + + if storage_item != Word::empty() { + non_empty_count += 1; + assert!(i < 2, "Found non-empty key at index {} which should be removed", i); + + let expected_word: Word = new_public_keys.get(i).unwrap().to_commitment(); + assert_eq!(storage_item, expected_word, "Key at index {} doesn't match", i); + } + } + assert_eq!( + non_empty_count, 2, + "Should have exactly 2 non-empty keys after removing 3 owners" + ); + + Ok(()) +} + +/// Tests that newly added approvers cannot sign transactions before the signer update is executed. +/// +/// This is a regression test to ensure that unauthorized parties cannot add their own public keys +/// to the multisig configuration and immediately use them to sign transactions before +/// the current approvers have validated and executed the signer update. +/// +/// **Test Flow:** +/// 1. Create a multisig account with 2 original approvers +/// 2. Prepare a signer update transaction with new approvers +/// 3. Try to sign the transaction with the NEW approvers (should fail) +/// 4. Verify that only the CURRENT approvers can sign the update transaction +#[tokio::test] +async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Result<()> { + // SECTION 1: Create a multisig account with 2 original approvers + // ================================================================================ + + let (_secret_keys, public_keys, _authenticators) = setup_keys_and_authenticators(2, 2)?; + + let multisig_account = create_multisig_account(2, &public_keys, 10)?; + + let mock_chain = MockChainBuilder::with_accounts([multisig_account.clone()]) + .unwrap() + .build() + .unwrap(); + + let salt = Word::from([Felt::new(5); 4]); + + // SECTION 2: Prepare a signer update transaction with new approvers + // ================================================================================ + + // Get the multisig library + + // Setup new signers (these should NOT be able to sign the update transaction) + let mut advice_map = AdviceMap::default(); + let (_new_secret_keys, new_public_keys, new_authenticators) = + setup_keys_and_authenticators(4, 4)?; + + let threshold = 3u64; + let num_of_approvers = 4u64; + + // Create vector with threshold config and public keys (4 field elements each) + let mut config_and_pubkeys_vector = Vec::new(); + config_and_pubkeys_vector.extend_from_slice(&[ + Felt::new(threshold), + Felt::new(num_of_approvers), + Felt::new(0), + Felt::new(0), + ]); + + // Add each public key to the vector + for public_key in new_public_keys.iter().rev() { + let key_word: Word = public_key.to_commitment(); + config_and_pubkeys_vector.extend_from_slice(key_word.as_elements()); + } + + // Hash the vector to create config hash + let multisig_config_hash = Hasher::hash_elements(&config_and_pubkeys_vector); + + // Insert config and public keys into advice map + advice_map.insert(multisig_config_hash, config_and_pubkeys_vector); + + // Create a transaction script that calls the update_signers procedure + let tx_script_code = " + begin + call.::update_signers_and_threshold + end + "; + + let tx_script = ScriptBuilder::new(true) + .with_dynamically_linked_library(&rpo_falcon_512_multisig_library())? + .compile_tx_script(tx_script_code)?; + + let advice_inputs = AdviceInputs { + map: advice_map.clone(), + ..Default::default() + }; + + // Pass the MULTISIG_CONFIG_HASH as the tx_script_args + let tx_script_args: Word = multisig_config_hash; + + // Execute transaction without signatures first to get tx summary + let tx_context_init = mock_chain + .build_tx_context(multisig_account.id(), &[], &[])? + .tx_script(tx_script.clone()) + .tx_script_args(tx_script_args) + .extend_advice_inputs(advice_inputs.clone()) + .auth_args(salt) + .build()?; + + let tx_summary = match tx_context_init.execute().await.unwrap_err() { + TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, + error => panic!("expected abort with tx effects: {error:?}"), + }; + + // SECTION 3: Try to sign the transaction with the NEW approvers (should fail) + // ================================================================================ + + // Get signatures from the NEW approvers (these should NOT work) + let msg = tx_summary.as_ref().to_commitment(); + let tx_summary_signing = SigningInputs::TransactionSummary(tx_summary.clone()); + + let new_sig_1 = new_authenticators[0] + .get_signature(new_public_keys[0].to_commitment(), &tx_summary_signing) + .await?; + let new_sig_2 = new_authenticators[1] + .get_signature(new_public_keys[1].to_commitment(), &tx_summary_signing) + .await?; + + // Try to execute transaction with NEW signatures - should FAIL + let tx_context_with_new_sigs = mock_chain + .build_tx_context(multisig_account.id(), &[], &[])? + .tx_script(tx_script.clone()) + .tx_script_args(multisig_config_hash) + .add_signature(new_public_keys[0].clone(), msg, new_sig_1) + .add_signature(new_public_keys[1].clone(), msg, new_sig_2) + .auth_args(salt) + .extend_advice_inputs(advice_inputs.clone()) + .build()?; + + // SECTION 4: Verify that only the CURRENT approvers can sign the update transaction + // ================================================================================ + + // Should fail - new approvers not yet authorized + let result = tx_context_with_new_sigs.execute().await; + + // Assert that the transaction fails as expected + assert!( + result.is_err(), + "Transaction should fail when signed by unauthorized new approvers" + ); + + Ok(()) +} From 5a74f49e5d77aea11ca8b2ea6d9f9a5e5ef59b70 Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:11:00 +0300 Subject: [PATCH 066/133] fix: resolve signature type mismatch breaking CI tests (#1957) * fix: convert Signature to Vec in multisig tests * refactor: use Signature type in add_signature methods for type safety * rustfmt --- .../miden-objects/src/transaction/tx_args.rs | 12 +++++++----- crates/miden-testing/src/tx_context/builder.rs | 5 +++-- crates/miden-testing/tests/auth/multisig.rs | 18 ++++++------------ 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index 08bd4c220a..8c9de8a459 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -7,6 +7,7 @@ use miden_crypto::merkle::InnerNodeInfo; use miden_processor::MastNodeExt; use super::{AccountInputs, Felt, Hasher, Word}; +use crate::account::Signature; use crate::note::{NoteId, NoteRecipient}; use crate::utils::serde::{ ByteReader, @@ -193,11 +194,12 @@ impl TransactionArgs { /// /// The advice inputs' map is extended with the following key: /// - /// - hash(public_key, message) |-> signature. - pub fn add_signature(&mut self, public_key: PublicKey, message: Word, signature: Vec) { - self.advice_inputs - .map - .insert(Hasher::merge(&[public_key.to_commitment(), message]), signature); + /// - hash(public_key, message) |-> signature (prepared for VM execution). + pub fn add_signature(&mut self, public_key: PublicKey, message: Word, signature: Signature) { + self.advice_inputs.map.insert( + Hasher::merge(&[public_key.to_commitment(), message]), + signature.to_prepared_signature(), + ); } /// Populates the advice inputs with the specified note recipient details. diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index fc437c3143..7fa79ba3b1 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -16,6 +16,7 @@ use miden_objects::account::{ PartialAccount, PartialStorage, PartialStorageMap, + Signature, StorageSlot, }; use miden_objects::assembly::DefaultSourceManager; @@ -86,7 +87,7 @@ pub struct TransactionContextBuilder { note_args: BTreeMap, transaction_inputs: Option, auth_args: Word, - signatures: Vec<(PublicKey, Word, Vec)>, + signatures: Vec<(PublicKey, Word, Signature)>, is_lazy_loading_enabled: bool, } @@ -252,7 +253,7 @@ impl TransactionContextBuilder { mut self, public_key: PublicKey, message: Word, - signature: Vec, + signature: Signature, ) -> Self { self.signatures.push((public_key, message, signature)); self diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index fbec199b37..7b1d8096f1 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -151,12 +151,10 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { let sig_1 = authenticators[0] .get_signature(public_keys[0].to_commitment(), &tx_summary) - .await? - .to_prepared_signature(); + .await?; let sig_2 = authenticators[1] .get_signature(public_keys[1].to_commitment(), &tx_summary) - .await? - .to_prepared_signature(); + .await?; // Execute transaction with signatures - should succeed let tx_context_execute = mock_chain @@ -235,12 +233,10 @@ async fn test_multisig_2_of_4_all_signer_combinations() -> anyhow::Result<()> { let sig_1 = authenticators[*signer1_idx] .get_signature(public_keys[*signer1_idx].to_commitment(), &tx_summary) - .await? - .to_prepared_signature(); + .await?; let sig_2 = authenticators[*signer2_idx] .get_signature(public_keys[*signer2_idx].to_commitment(), &tx_summary) - .await? - .to_prepared_signature(); + .await?; // Execute transaction with signatures - should succeed for any combination let tx_context_execute = mock_chain @@ -304,12 +300,10 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { let sig_1 = authenticators[0] .get_signature(public_keys[0].to_commitment(), &tx_summary) - .await? - .to_prepared_signature(); + .await?; let sig_2 = authenticators[1] .get_signature(public_keys[1].to_commitment(), &tx_summary) - .await? - .to_prepared_signature(); + .await?; // Execute transaction with signatures - should succeed (first execution) let tx_context_execute = mock_chain From 7fe21f95bd63f049d97cb7c30d7854fee14107b6 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 2 Oct 2025 13:26:18 +0200 Subject: [PATCH 067/133] feat: Remove pending output notes from `MockChain` block building (#1942) * feat: Remove pending output notes from `MockChain` * feat: Update mock chain docs * chore: Remove unused rng in mock chain * feat: Document `MockChainBuilder` * chore: add changelog * chore: Remove erroneously added doc line * fix: superfluous doc line --- CHANGELOG.md | 1 + crates/miden-testing/src/mock_chain/chain.rs | 224 ++++++------------ .../src/mock_chain/chain_builder.rs | 51 +++- 3 files changed, 122 insertions(+), 154 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe928c43f1..d5fe62435d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ - [BREAKING] Remove `MockChain::add_pending_note` to simplify mock chain internals ([#1903](https://github.com/0xMiden/miden-base/pull/1903)). - [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). - [BREAKING] Remove account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). +- Simplify `MockChain` internals and rework its documentation ([#1942]https://github.com/0xMiden/miden-base/pull/1942). - [BREAKING] Change the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). - [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index dc1f115447..4fbdfbe480 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -1,5 +1,4 @@ use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::string::ToString; use alloc::vec::Vec; use anyhow::Context; @@ -30,7 +29,6 @@ use miden_objects::transaction::{ ProvenTransaction, TransactionInputs, }; -use miden_objects::{MAX_BATCHES_PER_BLOCK, MAX_OUTPUT_NOTES_PER_BATCH}; use miden_processor::{DeserializationError, Word}; use miden_tx::LocalTransactionProver; use miden_tx::auth::BasicAuthenticator; @@ -47,55 +45,23 @@ use crate::{MockChainBuilder, TransactionContextBuilder}; // ================================================================================================ /// The [`MockChain`] simulates a simplified blockchain environment for testing purposes. -/// It allows creating and managing accounts, minting assets, executing transactions, and applying -/// state updates. /// -/// This struct is designed to mock transaction workflows, asset transfers, and -/// note creation in a test setting. Once entities are set up, [`TransactionContextBuilder`] objects -/// can be obtained in order to execute transactions accordingly. -/// -/// The primary way to interact with the mock chain is by generating transactions yourself and -/// adding them to the mock chain "mempool" using [`MockChain::add_pending_executed_transaction`] -/// or [`MockChain::add_pending_proven_transaction`]. Once some transactions have been added, they -/// can be proven into a block using [`MockChain::prove_next_block`], which commits them to the -/// chain state. +/// The typical usage of a mock chain is: +/// - Creating it using a [`MockChainBuilder`], which allows adding accounts and notes to the +/// genesis state. +/// - Creating transactions against the chain state and executing them. +/// - Adding executed or proven transactions to the set of pending transactions (the "mempool"), +/// e.g. using [`MockChain::add_pending_executed_transaction`]. +/// - Proving a block, which adds all pending transactions to the chain state, e.g. using +/// [`MockChain::prove_next_block`]. /// /// The mock chain uses the batch and block provers underneath to process pending transactions, so /// the generated blocks are realistic and indistinguishable from a real node. The only caveat is /// that no real ZK proofs are generated or validated as part of transaction, batch or block -/// building. If realistic data is important for your use case, avoid using any pending APIs except -/// for [`MockChain::add_pending_executed_transaction`] and -/// [`MockChain::add_pending_proven_transaction`]. +/// building. /// /// # Examples /// -/// ## Create mock objects and build a transaction context -/// ``` -/// # use anyhow::Result; -/// # use miden_objects::{Felt, asset::{Asset, FungibleAsset}, note::NoteType}; -/// # use miden_testing::{Auth, MockChain, TransactionContextBuilder}; -/// -/// # fn main() -> Result<()> { -/// let mut builder = MockChain::builder(); -/// -/// let faucet = builder.create_new_faucet(Auth::BasicAuth, "USDT", 100_000)?; -/// let asset = Asset::from(FungibleAsset::new(faucet.id(), 10)?); -/// -/// let sender = builder.create_new_wallet(Auth::BasicAuth)?; -/// let target = builder.create_new_wallet(Auth::BasicAuth)?; -/// -/// let note = builder.add_p2id_note(faucet.id(), target.id(), &[asset], NoteType::Public)?; -/// -/// let mock_chain = builder.build()?; -/// -/// // The target account is a new account so we move it into the build_tx_context, since the -/// // chain's committed accounts do not yet contain it. -/// let tx_context = mock_chain.build_tx_context(target, &[note.id()], &[])?.build()?; -/// let executed_transaction = tx_context.execute_blocking()?; -/// # Ok(()) -/// # } -/// ``` -/// /// ## Executing a simple transaction /// ``` /// # use anyhow::Result; @@ -104,8 +70,12 @@ use crate::{MockChainBuilder, TransactionContextBuilder}; /// # note::NoteType, /// # }; /// # use miden_testing::{Auth, MockChain}; +/// # +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> Result<()> { +/// // Build a genesis state for a mock chain using a MockChainBuilder. +/// // -------------------------------------------------------------------------------------------- /// -/// # fn main() -> Result<()> { /// let mut builder = MockChain::builder(); /// /// // Add a recipient wallet. @@ -113,9 +83,9 @@ use crate::{MockChainBuilder, TransactionContextBuilder}; /// /// // Add a wallet with assets. /// let sender = builder.add_existing_wallet(Auth::IncrNonce)?; -/// let fungible_asset = FungibleAsset::mock(10).unwrap_fungible(); /// -/// // Add a P2ID note to the chain. +/// let fungible_asset = FungibleAsset::mock(10).unwrap_fungible(); +/// // Add a P2ID note with a fungible asset to the chain. /// let note = builder.add_p2id_note( /// sender.id(), /// receiver.id(), @@ -123,19 +93,27 @@ use crate::{MockChainBuilder, TransactionContextBuilder}; /// NoteType::Public, /// )?; /// -/// let mut mock_chain = builder.build()?; +/// let mut mock_chain: MockChain = builder.build()?; +/// +/// // Create a transaction against the receiver account consuming the note. +/// // -------------------------------------------------------------------------------------------- /// /// let transaction = mock_chain /// .build_tx_context(receiver.id(), &[note.id()], &[])? /// .build()? -/// .execute_blocking()?; +/// .execute() +/// .await?; +/// +/// // Add the transaction to the chain state. +/// // -------------------------------------------------------------------------------------------- /// /// // Add the transaction to the mock chain's "mempool" of pending transactions. -/// mock_chain.add_pending_executed_transaction(&transaction); +/// mock_chain.add_pending_executed_transaction(&transaction)?; /// /// // Prove the next block to include the transaction in the chain state. /// mock_chain.prove_next_block()?; /// +/// // The receiver account should now have the asset in its account vault. /// assert_eq!( /// mock_chain /// .committed_account(receiver.id())? @@ -146,6 +124,35 @@ use crate::{MockChainBuilder, TransactionContextBuilder}; /// # Ok(()) /// # } /// ``` +/// +/// ## Create mock objects and build a transaction context +/// +/// ``` +/// # use anyhow::Result; +/// # use miden_objects::{Felt, asset::{Asset, FungibleAsset}, note::NoteType}; +/// # use miden_testing::{Auth, MockChain, TransactionContextBuilder}; +/// # +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> Result<()> { +/// let mut builder = MockChain::builder(); +/// +/// let faucet = builder.create_new_faucet(Auth::BasicAuth, "USDT", 100_000)?; +/// let asset = Asset::from(FungibleAsset::new(faucet.id(), 10)?); +/// +/// let sender = builder.create_new_wallet(Auth::BasicAuth)?; +/// let target = builder.create_new_wallet(Auth::BasicAuth)?; +/// +/// let note = builder.add_p2id_note(faucet.id(), target.id(), &[asset], NoteType::Public)?; +/// +/// let mock_chain = builder.build()?; +/// +/// // The target account is a new account so we move it into the build_tx_context, since the +/// // chain's committed accounts do not yet contain it. +/// let tx_context = mock_chain.build_tx_context(target, &[note.id()], &[])?.build()?; +/// let executed_transaction = tx_context.execute().await?; +/// # Ok(()) +/// # } +/// ``` #[derive(Debug, Clone)] pub struct MockChain { /// An append-only structure used to represent the history of blocks produced for this chain. @@ -160,11 +167,6 @@ pub struct MockChain { /// Tree containing the state commitments of all accounts. account_tree: AccountTree, - /// Note batches created in transactions in the block. - /// - /// These will become available once the block is proven. - pending_output_notes: Vec, - /// Transactions that have been submitted to the chain but have not yet been included in a /// block. pending_transactions: Vec, @@ -183,9 +185,6 @@ pub struct MockChain { /// AccountId |-> AccountAuthenticator mapping to store the authenticator for accounts to /// simplify transaction creation. account_authenticators: BTreeMap, - - // The RNG used to generate note serial numbers, account seeds or cryptographic keys. - rng: ChaCha20Rng, } impl MockChain { @@ -223,13 +222,10 @@ impl MockChain { blocks: vec![], nullifier_tree: NullifierTree::default(), account_tree, - pending_output_notes: Vec::new(), pending_transactions: Vec::new(), committed_notes: BTreeMap::new(), committed_accounts: BTreeMap::new(), account_authenticators, - // Initialize RNG with default seed. - rng: ChaCha20Rng::from_seed(Default::default()), }; // We do not have to apply the tree changes, because the account tree is already initialized @@ -770,16 +766,18 @@ impl MockChain { // PUBLIC MUTATORS // ---------------------------------------------------------------------------------------- - /// Creates the next block in the mock chain. + /// Proves the next block in the mock chain. /// - /// This will make all the objects currently pending available for use. + /// This will commit all the currently pending transactions into the chain state. pub fn prove_next_block(&mut self) -> anyhow::Result { - self.prove_block_inner(None) + self.prove_and_apply_block(None) } /// Proves the next block in the mock chain at the given timestamp. + /// + /// This will commit all the currently pending transactions into the chain state. pub fn prove_next_block_at(&mut self, timestamp: u32) -> anyhow::Result { - self.prove_block_inner(Some(timestamp)) + self.prove_and_apply_block(Some(timestamp)) } /// Proves new blocks until the block with the given target block number has been created. @@ -810,11 +808,6 @@ impl MockChain { Ok(last_block.expect("at least one block should have been created")) } - /// Sets the seed for the internal RNG. - pub fn set_rng_seed(&mut self, seed: [u8; 32]) { - self.rng = ChaCha20Rng::from_seed(seed); - } - // PUBLIC MUTATORS (PENDING APIS) // ---------------------------------------------------------------------------------------- @@ -822,8 +815,6 @@ impl MockChain { /// /// A block has to be created to apply the transaction effects to the chain state, e.g. using /// [`MockChain::prove_next_block`]. - /// - /// Returns the resulting state of the executing account after executing the transaction. pub fn add_pending_executed_transaction( &mut self, transaction: &ExecutedTransaction, @@ -901,7 +892,7 @@ impl MockChain { block_note_index.leaf_index_value(), note_path, ) - .context("failed to construct note inclusion proof")?; + .context("failed to create inclusion proof for output note")?; if let OutputNote::Full(note) = created_note { self.committed_notes @@ -952,83 +943,16 @@ impl MockChain { Ok(vec![proven_batch]) } - fn apply_pending_notes_to_block( - &mut self, - proven_block: &mut ProvenBlock, - ) -> anyhow::Result<()> { - // Add pending output notes to block. - let output_notes_block: BTreeSet = - proven_block.output_notes().map(|(_, output_note)| output_note.id()).collect(); - - // We could distribute notes over multiple batches (if space is available), but most likely - // one is sufficient. - if self.pending_output_notes.len() > MAX_OUTPUT_NOTES_PER_BATCH { - return Err(anyhow::anyhow!( - "too many pending output notes: {}, max allowed: {MAX_OUTPUT_NOTES_PER_BATCH}", - self.pending_output_notes.len(), - )); - } - - let mut pending_note_batch = Vec::with_capacity(self.pending_output_notes.len()); - let pending_output_notes = core::mem::take(&mut self.pending_output_notes); - for (note_idx, output_note) in pending_output_notes.into_iter().enumerate() { - if output_notes_block.contains(&output_note.id()) { - return Err(anyhow::anyhow!( - "output note {} is already created in block through transactions", - output_note.id() - )); - } - - pending_note_batch.push((note_idx, output_note)); - } - - if (proven_block.output_note_batches().len() + 1) > MAX_BATCHES_PER_BLOCK { - return Err(anyhow::anyhow!( - "too many batches in block: cannot add more pending notes".to_string(), - )); - } - - proven_block.output_note_batches_mut().push(pending_note_batch); - - let updated_block_note_tree = proven_block.build_output_note_tree().root(); - - // Update note tree root in the block header. - let block_header = proven_block.header(); - let updated_header = BlockHeader::new( - block_header.version(), - block_header.prev_block_commitment(), - block_header.block_num(), - block_header.chain_commitment(), - block_header.account_root(), - block_header.nullifier_root(), - updated_block_note_tree, - block_header.tx_commitment(), - block_header.tx_kernel_commitment(), - block_header.proof_commitment(), - block_header.fee_parameters().clone(), - block_header.timestamp(), - ); - proven_block.set_block_header(updated_header); - - Ok(()) - } - /// Creates a new block in the mock chain. /// - /// This will make all the objects currently pending available for use. - /// - /// If a `timestamp` is provided, it will be set on the block. - /// - /// Block building is divided into a few steps: + /// Block building is divided into two steps: /// /// 1. Build batches from pending transactions and a block from those batches. This results in a /// block. - /// 2. Take the pending notes and insert them directly into the proven block. This means we have - /// to update the header of the block with the updated block note tree root. - /// 3. Finally, the block contains both the updates from the regular transactions/batches as - /// well as the pending notes. Now insert all the accounts, nullifier and notes into the - /// chain state. - fn prove_block_inner(&mut self, timestamp: Option) -> anyhow::Result { + /// 2. Insert all the account updates, nullifiers and notes from the block into the chain state. + /// + /// If a `timestamp` is provided, it will be set on the block. + fn prove_and_apply_block(&mut self, timestamp: Option) -> anyhow::Result { // Create batches from pending transactions. // ---------------------------------------------------------------------------------------- @@ -1043,11 +967,10 @@ impl MockChain { let proposed_block = self .propose_block_at(batches, block_timestamp) .context("failed to create proposed block")?; - let mut proven_block = self.prove_block(proposed_block).context("failed to prove block")?; + let proven_block = self.prove_block(proposed_block).context("failed to prove block")?; - if !self.pending_output_notes.is_empty() { - self.apply_pending_notes_to_block(&mut proven_block)?; - } + // Apply block. + // ---------------------------------------------------------------------------------------- self.apply_block(proven_block.clone()).context("failed to apply block")?; @@ -1070,7 +993,6 @@ impl Serializable for MockChain { self.blocks.write_into(target); self.nullifier_tree.write_into(target); self.account_tree.write_into(target); - self.pending_output_notes.write_into(target); self.pending_transactions.write_into(target); self.committed_accounts.write_into(target); self.committed_notes.write_into(target); @@ -1084,7 +1006,6 @@ impl Deserializable for MockChain { let blocks = Vec::::read_from(source)?; let nullifier_tree = NullifierTree::read_from(source)?; let account_tree = AccountTree::read_from(source)?; - let pending_output_notes = Vec::::read_from(source)?; let pending_transactions = Vec::::read_from(source)?; let committed_accounts = BTreeMap::::read_from(source)?; let committed_notes = BTreeMap::::read_from(source)?; @@ -1096,12 +1017,10 @@ impl Deserializable for MockChain { blocks, nullifier_tree, account_tree, - pending_output_notes, pending_transactions, committed_notes, committed_accounts, account_authenticators, - rng: ChaCha20Rng::from_os_rng(), }) } } @@ -1327,7 +1246,6 @@ mod tests { assert_eq!(chain.blocks, deserialized.blocks); assert_eq!(chain.nullifier_tree, deserialized.nullifier_tree); assert_eq!(chain.account_tree, deserialized.account_tree); - assert_eq!(chain.pending_output_notes, deserialized.pending_output_notes); assert_eq!(chain.pending_transactions, deserialized.pending_transactions); assert_eq!(chain.committed_accounts, deserialized.committed_accounts); assert_eq!(chain.committed_notes, deserialized.committed_notes); diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index fc5f8be3d3..c89645d7c4 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -42,7 +42,56 @@ use crate::mock_chain::chain::AccountAuthenticator; use crate::utils::{create_p2any_note, create_spawn_note}; use crate::{AccountState, Auth, MockChain}; -/// A builder for a [`MockChain`]. +/// A builder for a [`MockChain`]'s genesis block. +/// +/// ## Example +/// +/// ``` +/// # use anyhow::Result; +/// # use miden_objects::{ +/// # asset::{Asset, FungibleAsset}, +/// # note::NoteType, +/// # }; +/// # use miden_testing::{Auth, MockChain}; +/// # +/// # fn main() -> Result<()> { +/// let mut builder = MockChain::builder(); +/// let existing_wallet = +/// builder.add_existing_wallet_with_assets(Auth::IncrNonce, [FungibleAsset::mock(500)])?; +/// let new_wallet = builder.create_new_wallet(Auth::IncrNonce)?; +/// +/// let existing_note = builder.add_p2id_note( +/// existing_wallet.id(), +/// new_wallet.id(), +/// &[FungibleAsset::mock(100)], +/// NoteType::Private, +/// )?; +/// let new_note = builder.create_p2id_note( +/// existing_wallet.id(), +/// new_wallet.id(), +/// [FungibleAsset::mock(100)], +/// NoteType::Private, +/// )?; +/// let chain = builder.build()?; +/// +/// // The existing wallet and note should be part of the chain state. +/// assert!(chain.committed_account(existing_wallet.id()).is_ok()); +/// assert!(chain.committed_notes().get(&existing_note.id()).is_some()); +/// +/// // The new wallet and note should *not* be part of the chain state - they must be created in +/// // a transaction first. +/// assert!(chain.committed_account(new_wallet.id()).is_err()); +/// assert!(chain.committed_notes().get(&new_note.id()).is_none()); +/// +/// # Ok(()) +/// # } +/// ``` +/// +/// Note the distinction between `add_` and `create_` APIs. Any `add_` APIs will add something to +/// the genesis chain state while `create_` APIs do not mutate the genesis state. The latter are +/// simply convenient for creating accounts or notes that will be created by transactions. +/// +/// See also the [`MockChain`] docs for examples on using the mock chain. #[derive(Debug, Clone)] pub struct MockChainBuilder { accounts: BTreeMap, From 7d0d15cd738d9f7abdf2b5c00fe66c980bc12eff Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Thu, 2 Oct 2025 17:42:32 +0300 Subject: [PATCH 068/133] refactor: rename UnconsumableWithoutAuthorization into ConsumableWithAuthorization --- .../src/kernel_tests/tx/test_account_interface.rs | 2 +- crates/miden-testing/tests/auth/multisig.rs | 10 +++++----- crates/miden-tx/src/executor/notes_checker.rs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index de6ab9d159..968065e837 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -439,7 +439,7 @@ async fn test_check_note_consumability_without_signatures() -> anyhow::Result<() ) .await?; - assert_eq!(consumability_info, NoteConsumptionStatus::UnconsumableWithoutAuthorization); + assert_eq!(consumability_info, NoteConsumptionStatus::ConsumableWithAuthorization); Ok(()) } diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index 5982ed4584..6a7f831a0e 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -949,9 +949,9 @@ async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { let notes_checker = NoteConsumptionChecker::new(&tx_executor); - // this check should return `UnconsumableWithoutAuthorization` variant: the note is consumable, - // but authentication is failing - let unconsumable_without_authorization = notes_checker + // this check should return `ConsumableWithAuthorization` variant: the note is consumable, but + // authentication is failing + let consumable_with_authorization = notes_checker .can_consume( multisig_account.id(), block_ref, @@ -960,8 +960,8 @@ async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { ) .await?; assert_matches!( - unconsumable_without_authorization, - NoteConsumptionStatus::UnconsumableWithoutAuthorization + consumable_with_authorization, + NoteConsumptionStatus::ConsumableWithAuthorization ); // execute the transaction to get the summary diff --git a/crates/miden-tx/src/executor/notes_checker.rs b/crates/miden-tx/src/executor/notes_checker.rs index 53f17c395f..9305d04764 100644 --- a/crates/miden-tx/src/executor/notes_checker.rs +++ b/crates/miden-tx/src/executor/notes_checker.rs @@ -379,7 +379,7 @@ fn handle_epilogue_error(epilogue_error: TransactionExecutorError) -> NoteConsum | TransactionExecutorError::MissingAuthenticator => { // Both these cases signal that there is a probability that the provided note could be // consumed if the authentication is provided. - NoteConsumptionStatus::UnconsumableWithoutAuthorization + NoteConsumptionStatus::ConsumableWithAuthorization }, // TODO: apply additional checks to get the verbose error reason _ => NoteConsumptionStatus::Unconsumable, @@ -400,8 +400,8 @@ pub enum NoteConsumptionStatus { Consumable, /// The note can be consumed by the account after the required block height is achieved. ConsumableAfter(BlockNumber), - /// The note cannot be consumed by the account without the authorization. - UnconsumableWithoutAuthorization, + /// The note can be consumed by the account if proper authorization is provided. + ConsumableWithAuthorization, /// The note cannot be consumed by the account at the specified conditions (i.e., block /// height and account state). Unconsumable, From 191c5943d50811ade643837e23f810c5570c09b9 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Thu, 2 Oct 2025 20:16:15 +0300 Subject: [PATCH 069/133] chore: tiny changelog formatting --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 924d383fd5..df001de820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ - Simplify `MockChain` internals and rework its documentation ([#1942]https://github.com/0xMiden/miden-base/pull/1942). - [BREAKING] Change the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). - [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). + ## 0.11.5 (2025-10-01) - Add new `can_consume` method to the `NoteConsumptionChecker` ([#1928](https://github.com/0xMiden/miden-base/pull/1928)). From 648323f7e5d4e7143b567c0a1786133ecbdd685d Mon Sep 17 00:00:00 2001 From: Marti Date: Thu, 2 Oct 2025 23:56:57 +0200 Subject: [PATCH 070/133] chore: refactor `get_signature` and `add_signature` methods to use `PublicKeyCommitment` (#1962) --- crates/miden-lib/src/account/auth/mod.rs | 3 - .../src/account/auth/public_key_commitment.rs | 29 -------- .../src/account/auth/rpo_falcon_512.rs | 3 +- .../src/account/auth/rpo_falcon_512_acl.rs | 9 ++- .../account/auth/rpo_falcon_512_multisig.rs | 3 +- crates/miden-lib/src/account/faucets/mod.rs | 3 +- .../src/account/interface/component.rs | 8 ++- .../miden-lib/src/account/interface/test.rs | 15 ++-- crates/miden-lib/src/account/wallets/mod.rs | 2 +- crates/miden-lib/src/auth.rs | 2 +- crates/miden-objects/src/account/auth.rs | 26 ++++++- crates/miden-objects/src/account/mod.rs | 2 +- .../miden-objects/src/transaction/tx_args.rs | 18 +++-- .../src/kernel_tests/tx/test_note.rs | 3 +- crates/miden-testing/src/mock_chain/auth.rs | 6 +- .../miden-testing/src/tx_context/builder.rs | 12 ++-- crates/miden-testing/tests/auth/multisig.rs | 72 +++++++++---------- crates/miden-testing/tests/wallet/mod.rs | 2 +- crates/miden-tx/src/auth/tx_authenticator.rs | 11 +-- crates/miden-tx/src/executor/exec_host.rs | 10 ++- 20 files changed, 124 insertions(+), 115 deletions(-) delete mode 100644 crates/miden-lib/src/account/auth/public_key_commitment.rs diff --git a/crates/miden-lib/src/account/auth/mod.rs b/crates/miden-lib/src/account/auth/mod.rs index b16071780f..2861b784b0 100644 --- a/crates/miden-lib/src/account/auth/mod.rs +++ b/crates/miden-lib/src/account/auth/mod.rs @@ -1,9 +1,6 @@ mod no_auth; pub use no_auth::NoAuth; -mod public_key_commitment; -pub use public_key_commitment::PublicKeyCommitment; - mod rpo_falcon_512; pub use rpo_falcon_512::AuthRpoFalcon512; diff --git a/crates/miden-lib/src/account/auth/public_key_commitment.rs b/crates/miden-lib/src/account/auth/public_key_commitment.rs deleted file mode 100644 index 7aa63275b3..0000000000 --- a/crates/miden-lib/src/account/auth/public_key_commitment.rs +++ /dev/null @@ -1,29 +0,0 @@ -use miden_core::Word; -use miden_core::crypto::dsa::rpo_falcon512::PublicKey; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PublicKeyCommitment(pub Word); - -impl PublicKeyCommitment { - pub fn empty() -> Self { - Self(Word::empty()) - } -} - -impl From for PublicKeyCommitment { - fn from(value: PublicKey) -> Self { - Self(value.to_commitment()) - } -} - -impl From for Word { - fn from(value: PublicKeyCommitment) -> Self { - value.0 - } -} - -impl From for PublicKeyCommitment { - fn from(value: Word) -> Self { - Self(value) - } -} diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512.rs index 582adeb779..e35b95b4c4 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512.rs @@ -1,6 +1,5 @@ -use miden_objects::account::{AccountComponent, StorageSlot}; +use miden_objects::account::{AccountComponent, PublicKeyCommitment, StorageSlot}; -use crate::account::auth::PublicKeyCommitment; use crate::account::components::rpo_falcon_512_library; /// An [`AccountComponent`] implementing the RpoFalcon512 signature scheme for authentication of diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs index b8815da658..699935d001 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs @@ -1,9 +1,14 @@ use alloc::vec::Vec; -use miden_objects::account::{AccountCode, AccountComponent, StorageMap, StorageSlot}; +use miden_objects::account::{ + AccountCode, + AccountComponent, + PublicKeyCommitment, + StorageMap, + StorageSlot, +}; use miden_objects::{AccountError, Word}; -use crate::account::auth::PublicKeyCommitment; use crate::account::components::rpo_falcon_512_acl_library; /// Configuration for [`AuthRpoFalcon512Acl`] component. diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs index df441dc6be..f3842537be 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs @@ -1,9 +1,8 @@ use alloc::vec::Vec; -use miden_objects::account::{AccountComponent, StorageMap, StorageSlot}; +use miden_objects::account::{AccountComponent, PublicKeyCommitment, StorageMap, StorageSlot}; use miden_objects::{AccountError, Word}; -use crate::account::auth::PublicKeyCommitment; use crate::account::components::rpo_falcon_512_multisig_library; // MULTISIG AUTHENTICATION COMPONENT diff --git a/crates/miden-lib/src/account/faucets/mod.rs b/crates/miden-lib/src/account/faucets/mod.rs index 657a22d090..0e2a91030d 100644 --- a/crates/miden-lib/src/account/faucets/mod.rs +++ b/crates/miden-lib/src/account/faucets/mod.rs @@ -354,6 +354,7 @@ pub enum FungibleFaucetError { #[cfg(test)] mod tests { use assert_matches::assert_matches; + use miden_objects::account::PublicKeyCommitment; use miden_objects::{FieldElement, ONE, Word}; use super::{ @@ -367,7 +368,7 @@ mod tests { TokenSymbol, create_basic_fungible_faucet, }; - use crate::account::auth::{AuthRpoFalcon512, PublicKeyCommitment}; + use crate::account::auth::AuthRpoFalcon512; use crate::account::wallets::BasicWallet; #[test] diff --git a/crates/miden-lib/src/account/interface/component.rs b/crates/miden-lib/src/account/interface/component.rs index 66815e1da4..938a1034d7 100644 --- a/crates/miden-lib/src/account/interface/component.rs +++ b/crates/miden-lib/src/account/interface/component.rs @@ -2,12 +2,16 @@ use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use miden_objects::account::{AccountId, AccountProcedureInfo, AccountStorage}; +use miden_objects::account::{ + AccountId, + AccountProcedureInfo, + AccountStorage, + PublicKeyCommitment, +}; use miden_objects::note::PartialNote; use miden_objects::{Felt, FieldElement, Word}; use crate::AuthScheme; -use crate::account::auth::PublicKeyCommitment; use crate::account::components::WellKnownComponent; use crate::account::interface::AccountInterfaceError; diff --git a/crates/miden-lib/src/account/interface/test.rs b/crates/miden-lib/src/account/interface/test.rs index e27e61d452..18fef5f477 100644 --- a/crates/miden-lib/src/account/interface/test.rs +++ b/crates/miden-lib/src/account/interface/test.rs @@ -3,7 +3,13 @@ use alloc::sync::Arc; use alloc::vec::Vec; use assert_matches::assert_matches; -use miden_objects::account::{AccountBuilder, AccountComponent, AccountType, StorageSlot}; +use miden_objects::account::{ + AccountBuilder, + AccountComponent, + AccountType, + PublicKeyCommitment, + StorageSlot, +}; use miden_objects::assembly::diagnostics::NamedSource; use miden_objects::assembly::{Assembler, DefaultSourceManager}; use miden_objects::asset::{FungibleAsset, NonFungibleAsset, TokenSymbol}; @@ -25,12 +31,7 @@ use miden_objects::testing::account_id::{ use miden_objects::{AccountError, Felt, NoteError, Word, ZERO}; use crate::AuthScheme; -use crate::account::auth::{ - AuthRpoFalcon512, - AuthRpoFalcon512Multisig, - NoAuth, - PublicKeyCommitment, -}; +use crate::account::auth::{AuthRpoFalcon512, AuthRpoFalcon512Multisig, NoAuth}; use crate::account::faucets::BasicFungibleFaucet; use crate::account::interface::{ AccountComponentInterface, diff --git a/crates/miden-lib/src/account/wallets/mod.rs b/crates/miden-lib/src/account/wallets/mod.rs index ed0b4276d3..8fb0337ec5 100644 --- a/crates/miden-lib/src/account/wallets/mod.rs +++ b/crates/miden-lib/src/account/wallets/mod.rs @@ -158,11 +158,11 @@ pub fn create_basic_wallet( #[cfg(test)] mod tests { + use miden_objects::account::PublicKeyCommitment; use miden_objects::{ONE, Word}; use miden_processor::utils::{Deserializable, Serializable}; use super::{Account, AccountStorageMode, AccountType, AuthScheme, create_basic_wallet}; - use crate::account::auth::PublicKeyCommitment; use crate::account::wallets::BasicWallet; #[test] diff --git a/crates/miden-lib/src/auth.rs b/crates/miden-lib/src/auth.rs index 116f00617d..2fa553faab 100644 --- a/crates/miden-lib/src/auth.rs +++ b/crates/miden-lib/src/auth.rs @@ -1,6 +1,6 @@ use alloc::vec::Vec; -use crate::account::auth::PublicKeyCommitment; +use miden_objects::account::PublicKeyCommitment; /// Defines authentication schemes available to standard and faucet accounts. pub enum AuthScheme { diff --git a/crates/miden-objects/src/account/auth.rs b/crates/miden-objects/src/account/auth.rs index 4a67408f89..613ab5d075 100644 --- a/crates/miden-objects/src/account/auth.rs +++ b/crates/miden-objects/src/account/auth.rs @@ -1,5 +1,7 @@ use alloc::vec::Vec; +use miden_crypto::dsa::rpo_falcon512::PublicKey as RpoFalconPublicKey; + use crate::crypto::dsa::rpo_falcon512::{self, Polynomial, SecretKey}; use crate::utils::serde::{ ByteReader, @@ -8,7 +10,7 @@ use crate::utils::serde::{ DeserializationError, Serializable, }; -use crate::{Felt, Hasher}; +use crate::{Felt, Hasher, Word}; // AUTH SECRET KEY // ================================================================================================ @@ -57,6 +59,28 @@ impl Deserializable for AuthSecretKey { // SIGNATURE // ================================================================================================ +/// Commitment to a public key +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PublicKeyCommitment(pub Word); + +impl From for PublicKeyCommitment { + fn from(value: RpoFalconPublicKey) -> Self { + Self(value.to_commitment()) + } +} + +impl From for Word { + fn from(value: PublicKeyCommitment) -> Self { + value.0 + } +} + +impl From for PublicKeyCommitment { + fn from(value: Word) -> Self { + Self(value) + } +} + /// Represents a signature object ready for native verification. /// /// In order to use this signature within the Miden VM, a preparation step may be necessary to diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index 6a2aba0891..e7293af79a 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -23,7 +23,7 @@ pub use account_id::{ pub mod auth; -pub use auth::{AuthSecretKey, Signature}; +pub use auth::{AuthSecretKey, PublicKeyCommitment, Signature}; mod builder; pub use builder::AccountBuilder; diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index 8c9de8a459..cc1e32e910 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -2,12 +2,11 @@ use alloc::collections::{BTreeMap, BTreeSet}; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_crypto::dsa::rpo_falcon512::PublicKey; use miden_crypto::merkle::InnerNodeInfo; use miden_processor::MastNodeExt; use super::{AccountInputs, Felt, Hasher, Word}; -use crate::account::Signature; +use crate::account::{PublicKeyCommitment, Signature}; use crate::note::{NoteId, NoteRecipient}; use crate::utils::serde::{ ByteReader, @@ -195,11 +194,16 @@ impl TransactionArgs { /// The advice inputs' map is extended with the following key: /// /// - hash(public_key, message) |-> signature (prepared for VM execution). - pub fn add_signature(&mut self, public_key: PublicKey, message: Word, signature: Signature) { - self.advice_inputs.map.insert( - Hasher::merge(&[public_key.to_commitment(), message]), - signature.to_prepared_signature(), - ); + pub fn add_signature( + &mut self, + public_key_commitment: PublicKeyCommitment, + message: Word, + signature: Signature, + ) { + let pk_word: Word = public_key_commitment.into(); + self.advice_inputs + .map + .insert(Hasher::merge(&[pk_word, message]), signature.to_prepared_signature()); } /// Populates the advice inputs with the specified note recipient details. diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 5dbcfd6409..f5ba4751de 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -3,14 +3,13 @@ use alloc::sync::Arc; use alloc::vec::Vec; use anyhow::Context; -use miden_lib::account::auth::PublicKeyCommitment; use miden_lib::account::wallets::BasicWallet; use miden_lib::errors::MasmError; use miden_lib::testing::note::NoteBuilder; use miden_lib::transaction::TransactionKernel; use miden_lib::transaction::memory::ACTIVE_INPUT_NOTE_PTR; use miden_lib::utils::ScriptBuilder; -use miden_objects::account::{AccountBuilder, AccountId}; +use miden_objects::account::{AccountBuilder, AccountId, PublicKeyCommitment}; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::diagnostics::miette::{self, miette}; use miden_objects::asset::FungibleAsset; diff --git a/crates/miden-testing/src/mock_chain/auth.rs b/crates/miden-testing/src/mock_chain/auth.rs index 2b47c7d56a..2cc2bf639e 100644 --- a/crates/miden-testing/src/mock_chain/auth.rs +++ b/crates/miden-testing/src/mock_chain/auth.rs @@ -7,11 +7,10 @@ use miden_lib::account::auth::{ AuthRpoFalcon512Acl, AuthRpoFalcon512AclConfig, AuthRpoFalcon512Multisig, - PublicKeyCommitment, }; use miden_lib::testing::account_component::{ConditionalAuthComponent, IncrNonceAuthComponent}; use miden_objects::Word; -use miden_objects::account::{AccountComponent, AuthSecretKey}; +use miden_objects::account::{AccountComponent, AuthSecretKey, PublicKeyCommitment}; use miden_objects::crypto::dsa::rpo_falcon512::SecretKey; use miden_objects::testing::noop_auth_component::NoopAuthComponent; use miden_tx::auth::BasicAuthenticator; @@ -60,8 +59,7 @@ impl Auth { Auth::BasicAuth => { let mut rng = ChaCha20Rng::from_seed(Default::default()); let sec_key = SecretKey::with_rng(&mut rng); - let pub_key = - miden_lib::account::auth::PublicKeyCommitment::from(sec_key.public_key()); + let pub_key = PublicKeyCommitment::from(sec_key.public_key()); let component = AuthRpoFalcon512::new(pub_key).into(); let authenticator = BasicAuthenticator::::new_with_rng( diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 7fa79ba3b1..da2c8d056f 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -16,13 +16,13 @@ use miden_objects::account::{ PartialAccount, PartialStorage, PartialStorageMap, + PublicKeyCommitment, Signature, StorageSlot, }; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::debuginfo::SourceManagerSync; use miden_objects::asset::PartialVault; -use miden_objects::crypto::dsa::rpo_falcon512::PublicKey; use miden_objects::note::{Note, NoteId}; use miden_objects::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; use miden_objects::testing::noop_auth_component::NoopAuthComponent; @@ -87,7 +87,7 @@ pub struct TransactionContextBuilder { note_args: BTreeMap, transaction_inputs: Option, auth_args: Word, - signatures: Vec<(PublicKey, Word, Signature)>, + signatures: Vec<(PublicKeyCommitment, Word, Signature)>, is_lazy_loading_enabled: bool, } @@ -251,11 +251,11 @@ impl TransactionContextBuilder { /// Add a new signature for the message and the public key. pub fn add_signature( mut self, - public_key: PublicKey, + public_key_commitment: PublicKeyCommitment, message: Word, signature: Signature, ) -> Self { - self.signatures.push((public_key, message, signature)); + self.signatures.push((public_key_commitment, message, signature)); self } @@ -322,8 +322,8 @@ impl TransactionContextBuilder { tx_args.extend_advice_inputs(self.advice_inputs.clone()); tx_args.extend_output_note_recipients(self.expected_output_notes.clone()); - for (public_key, message, signature) in self.signatures { - tx_args.add_signature(public_key, message, signature); + for (public_key_commitment, message, signature) in self.signatures { + tx_args.add_signature(public_key_commitment, message, signature); } let mast_store = { diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index 7b1d8096f1..43de73414a 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -150,18 +150,18 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment(), &tx_summary) + .get_signature(public_keys[0].to_commitment().into(), &tx_summary) .await?; let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment(), &tx_summary) + .get_signature(public_keys[1].to_commitment().into(), &tx_summary) .await?; // Execute transaction with signatures - should succeed let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? .extend_expected_output_notes(vec![OutputNote::Full(output_note)]) - .add_signature(public_keys[0].clone(), msg, sig_1) - .add_signature(public_keys[1].clone(), msg, sig_2) + .add_signature(public_keys[0].clone().into(), msg, sig_1) + .add_signature(public_keys[1].clone().into(), msg, sig_2) .auth_args(salt) .build()? .execute() @@ -232,18 +232,18 @@ async fn test_multisig_2_of_4_all_signer_combinations() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[*signer1_idx] - .get_signature(public_keys[*signer1_idx].to_commitment(), &tx_summary) + .get_signature(public_keys[*signer1_idx].to_commitment().into(), &tx_summary) .await?; let sig_2 = authenticators[*signer2_idx] - .get_signature(public_keys[*signer2_idx].to_commitment(), &tx_summary) + .get_signature(public_keys[*signer2_idx].to_commitment().into(), &tx_summary) .await?; // Execute transaction with signatures - should succeed for any combination let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? .auth_args(salt) - .add_signature(public_keys[*signer1_idx].clone(), msg, sig_1) - .add_signature(public_keys[*signer2_idx].clone(), msg, sig_2) + .add_signature(public_keys[*signer1_idx].clone().into(), msg, sig_1) + .add_signature(public_keys[*signer2_idx].clone().into(), msg, sig_2) .build()?; let executed_tx = tx_context_execute.execute().await.unwrap_or_else(|_| { @@ -299,17 +299,17 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment(), &tx_summary) + .get_signature(public_keys[0].to_commitment().into(), &tx_summary) .await?; let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment(), &tx_summary) + .get_signature(public_keys[1].to_commitment().into(), &tx_summary) .await?; // Execute transaction with signatures - should succeed (first execution) let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .add_signature(public_keys[0].clone(), msg, sig_1.clone()) - .add_signature(public_keys[1].clone(), msg, sig_2.clone()) + .add_signature(public_keys[0].clone().into(), msg, sig_1.clone()) + .add_signature(public_keys[1].clone().into(), msg, sig_2.clone()) .auth_args(salt) .build()?; @@ -322,8 +322,8 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { // Attempt to execute the same transaction again - should fail due to replay protection let tx_context_replay = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .add_signature(public_keys[0].clone(), msg, sig_1) - .add_signature(public_keys[1].clone(), msg, sig_2) + .add_signature(public_keys[0].clone().into(), msg, sig_1) + .add_signature(public_keys[1].clone().into(), msg, sig_2) .auth_args(salt) .build()?; @@ -439,10 +439,10 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment(), &tx_summary) + .get_signature(public_keys[0].to_commitment().into(), &tx_summary) .await?; let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment(), &tx_summary) + .get_signature(public_keys[1].to_commitment().into(), &tx_summary) .await?; // Execute transaction with signatures - should succeed @@ -450,8 +450,8 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { .build_tx_context(multisig_account.id(), &[], &[])? .tx_script(tx_script) .tx_script_args(multisig_config_hash) - .add_signature(public_keys[0].clone(), msg, sig_1) - .add_signature(public_keys[1].clone(), msg, sig_2) + .add_signature(public_keys[0].clone().into(), msg, sig_1) + .add_signature(public_keys[1].clone().into(), msg, sig_2) .auth_args(salt) .extend_advice_inputs(advice_inputs) .build()? @@ -574,13 +574,13 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { let tx_summary_new = SigningInputs::TransactionSummary(tx_summary_new); let sig_1_new = new_authenticators[0] - .get_signature(new_public_keys[0].to_commitment(), &tx_summary_new) + .get_signature(new_public_keys[0].to_commitment().into(), &tx_summary_new) .await?; let sig_2_new = new_authenticators[1] - .get_signature(new_public_keys[1].to_commitment(), &tx_summary_new) + .get_signature(new_public_keys[1].to_commitment().into(), &tx_summary_new) .await?; let sig_3_new = new_authenticators[2] - .get_signature(new_public_keys[2].to_commitment(), &tx_summary_new) + .get_signature(new_public_keys[2].to_commitment().into(), &tx_summary_new) .await?; // SECTION 3: Properly handle multisig authentication with the updated signers @@ -590,9 +590,9 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { let tx_context_execute_new = new_mock_chain .build_tx_context(updated_multisig_account.id(), &[input_note_new.id()], &[])? .extend_expected_output_notes(vec![OutputNote::Full(output_note_new)]) - .add_signature(new_public_keys[0].clone(), msg_new, sig_1_new) - .add_signature(new_public_keys[1].clone(), msg_new, sig_2_new) - .add_signature(new_public_keys[2].clone(), msg_new, sig_3_new) + .add_signature(new_public_keys[0].clone().into(), msg_new, sig_1_new) + .add_signature(new_public_keys[1].clone().into(), msg_new, sig_2_new) + .add_signature(new_public_keys[2].clone().into(), msg_new, sig_3_new) .auth_args(salt_new) .build()? .execute() @@ -674,16 +674,16 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment(), &tx_summary) + .get_signature(public_keys[0].to_commitment().into(), &tx_summary) .await?; let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment(), &tx_summary) + .get_signature(public_keys[1].to_commitment().into(), &tx_summary) .await?; let sig_3 = authenticators[2] - .get_signature(public_keys[2].to_commitment(), &tx_summary) + .get_signature(public_keys[2].to_commitment().into(), &tx_summary) .await?; let sig_4 = authenticators[3] - .get_signature(public_keys[3].to_commitment(), &tx_summary) + .get_signature(public_keys[3].to_commitment().into(), &tx_summary) .await?; // Execute with signatures @@ -691,10 +691,10 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { .build_tx_context(multisig_account.id(), &[], &[])? .tx_script(tx_script) .tx_script_args(multisig_config_hash) - .add_signature(public_keys[0].clone(), msg, sig_1) - .add_signature(public_keys[1].clone(), msg, sig_2) - .add_signature(public_keys[2].clone(), msg, sig_3) - .add_signature(public_keys[3].clone(), msg, sig_4) + .add_signature(public_keys[0].clone().into(), msg, sig_1) + .add_signature(public_keys[1].clone().into(), msg, sig_2) + .add_signature(public_keys[2].clone().into(), msg, sig_3) + .add_signature(public_keys[3].clone().into(), msg, sig_4) .auth_args(salt) .extend_advice_inputs(advice_inputs) .build()? @@ -875,10 +875,10 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu let tx_summary_signing = SigningInputs::TransactionSummary(tx_summary.clone()); let new_sig_1 = new_authenticators[0] - .get_signature(new_public_keys[0].to_commitment(), &tx_summary_signing) + .get_signature(new_public_keys[0].to_commitment().into(), &tx_summary_signing) .await?; let new_sig_2 = new_authenticators[1] - .get_signature(new_public_keys[1].to_commitment(), &tx_summary_signing) + .get_signature(new_public_keys[1].to_commitment().into(), &tx_summary_signing) .await?; // Try to execute transaction with NEW signatures - should FAIL @@ -886,8 +886,8 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu .build_tx_context(multisig_account.id(), &[], &[])? .tx_script(tx_script.clone()) .tx_script_args(multisig_config_hash) - .add_signature(new_public_keys[0].clone(), msg, new_sig_1) - .add_signature(new_public_keys[1].clone(), msg, new_sig_2) + .add_signature(new_public_keys[0].clone().into(), msg, new_sig_1) + .add_signature(new_public_keys[1].clone().into(), msg, new_sig_2) .auth_args(salt) .extend_advice_inputs(advice_inputs.clone()) .build()?; diff --git a/crates/miden-testing/tests/wallet/mod.rs b/crates/miden-testing/tests/wallet/mod.rs index c66b72cda4..2dad45c036 100644 --- a/crates/miden-testing/tests/wallet/mod.rs +++ b/crates/miden-testing/tests/wallet/mod.rs @@ -17,7 +17,7 @@ fn wallet_creation() { let mut rng = ChaCha20Rng::from_seed(seed); let sec_key = SecretKey::with_rng(&mut rng); - let pub_key = miden_lib::account::auth::PublicKeyCommitment::from(sec_key.public_key()); + let pub_key = miden_objects::account::PublicKeyCommitment::from(sec_key.public_key()); let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key }; // we need to use an initial seed to create the wallet account diff --git a/crates/miden-tx/src/auth/tx_authenticator.rs b/crates/miden-tx/src/auth/tx_authenticator.rs index b248dab9c3..10e96c9cb9 100644 --- a/crates/miden-tx/src/auth/tx_authenticator.rs +++ b/crates/miden-tx/src/auth/tx_authenticator.rs @@ -4,7 +4,7 @@ use alloc::string::ToString; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_objects::account::{AuthSecretKey, Signature}; +use miden_objects::account::{AuthSecretKey, PublicKeyCommitment, Signature}; use miden_objects::crypto::SequentialCommit; use miden_objects::transaction::TransactionSummary; use miden_objects::{Felt, Hasher, Word}; @@ -139,7 +139,7 @@ pub trait TransactionAuthenticator { /// signature computation. fn get_signature( &self, - pub_key: Word, + pub_key_commitment: PublicKeyCommitment, signing_inputs: &SigningInputs, ) -> impl FutureMaybeSend>; } @@ -158,7 +158,7 @@ impl TransactionAuthenticator for UnreachableAuth { #[allow(clippy::manual_async_fn)] fn get_signature( &self, - _pub_key: Word, + _pub_key_commitment: PublicKeyCommitment, _signing_inputs: &SigningInputs, ) -> impl FutureMaybeSend> { async { unreachable!("Type `UnreachableAuth` must not be instantiated") } @@ -217,13 +217,14 @@ impl TransactionAuthenticator for BasicAuthenticator { /// [`AuthenticationError::UnknownPublicKey`] is returned. fn get_signature( &self, - pub_key: Word, + pub_key_commitment: PublicKeyCommitment, signing_inputs: &SigningInputs, ) -> impl FutureMaybeSend> { let message = signing_inputs.to_commitment(); async move { let mut rng = self.rng.write().await; + let pub_key: Word = pub_key_commitment.into(); match self.keys.get(&pub_key) { Some(key) => { let signature: Signature = match key { @@ -248,7 +249,7 @@ impl TransactionAuthenticator for () { #[allow(clippy::manual_async_fn)] fn get_signature( &self, - _pub_key: Word, + _pub_key_commitment: PublicKeyCommitment, _signing_inputs: &SigningInputs, ) -> impl FutureMaybeSend> { async { diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index d0eb988787..c1bf3273a0 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -3,7 +3,13 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_lib::transaction::{EventId, TransactionAdviceInputs}; -use miden_objects::account::{AccountCode, AccountDelta, AccountId, PartialAccount}; +use miden_objects::account::{ + AccountCode, + AccountDelta, + AccountId, + PartialAccount, + PublicKeyCommitment, +}; use miden_objects::assembly::debuginfo::Location; use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; use miden_objects::asset::{Asset, AssetWitness, FungibleAsset}; @@ -182,7 +188,7 @@ where self.authenticator.ok_or(TransactionKernelError::MissingAuthenticator)?; let signature: Vec = authenticator - .get_signature(pub_key_hash, &signing_inputs) + .get_signature(PublicKeyCommitment::from(pub_key_hash), &signing_inputs) .await .map_err(TransactionKernelError::SignatureGenerationFailed)? .to_prepared_signature(); From 479c2870f459b20060b305e3f47261b70e8e0437 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Thu, 2 Oct 2025 15:01:29 -0700 Subject: [PATCH 071/133] chore: minor renaming --- crates/miden-objects/src/account/auth.rs | 9 ++++++--- crates/miden-objects/src/transaction/tx_args.rs | 8 ++++---- crates/miden-testing/src/tx_context/builder.rs | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/miden-objects/src/account/auth.rs b/crates/miden-objects/src/account/auth.rs index 613ab5d075..c66a43db25 100644 --- a/crates/miden-objects/src/account/auth.rs +++ b/crates/miden-objects/src/account/auth.rs @@ -56,12 +56,12 @@ impl Deserializable for AuthSecretKey { } } -// SIGNATURE +// PUBLIC KEY // ================================================================================================ -/// Commitment to a public key +/// Commitment to a public key. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PublicKeyCommitment(pub Word); +pub struct PublicKeyCommitment(Word); impl From for PublicKeyCommitment { fn from(value: RpoFalconPublicKey) -> Self { @@ -81,6 +81,9 @@ impl From for PublicKeyCommitment { } } +// SIGNATURE +// ================================================================================================ + /// Represents a signature object ready for native verification. /// /// In order to use this signature within the Miden VM, a preparation step may be necessary to diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index cc1e32e910..cd5fa54918 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -189,18 +189,18 @@ impl TransactionArgs { self.advice_inputs.extend(AdviceInputs::default().with_map(new_elements)); } - /// Adds the `signature` corresponding to `public_key` on `message` to the advice inputs' map. + /// Adds the `signature` corresponding to `pub_key` on `message` to the advice inputs' map. /// /// The advice inputs' map is extended with the following key: /// - /// - hash(public_key, message) |-> signature (prepared for VM execution). + /// - hash(pub_key, message) |-> signature (prepared for VM execution). pub fn add_signature( &mut self, - public_key_commitment: PublicKeyCommitment, + pub_key: PublicKeyCommitment, message: Word, signature: Signature, ) { - let pk_word: Word = public_key_commitment.into(); + let pk_word: Word = pub_key.into(); self.advice_inputs .map .insert(Hasher::merge(&[pk_word, message]), signature.to_prepared_signature()); diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index da2c8d056f..8d1880af57 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -251,11 +251,11 @@ impl TransactionContextBuilder { /// Add a new signature for the message and the public key. pub fn add_signature( mut self, - public_key_commitment: PublicKeyCommitment, + pub_key: PublicKeyCommitment, message: Word, signature: Signature, ) -> Self { - self.signatures.push((public_key_commitment, message, signature)); + self.signatures.push((pub_key, message, signature)); self } From 587986753e1e304e123a3cc35fd68c725887a424 Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:03:36 +0300 Subject: [PATCH 072/133] feat: add `get_public_key_commitments` method to `AuthScheme` #1953 (#1967) --- crates/miden-lib/src/auth.rs | 36 ++++++++++++++----- .../src/testing/account_interface.rs | 22 ++++-------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/crates/miden-lib/src/auth.rs b/crates/miden-lib/src/auth.rs index 2fa553faab..c143fa9f9d 100644 --- a/crates/miden-lib/src/auth.rs +++ b/crates/miden-lib/src/auth.rs @@ -4,22 +4,40 @@ use miden_objects::account::PublicKeyCommitment; /// Defines authentication schemes available to standard and faucet accounts. pub enum AuthScheme { - /// A single-key authentication scheme which relies RPO Falcon512 signatures. RPO Falcon512 is - /// a variant of the [Falcon](https://falcon-sign.info/) signature scheme. This variant differs from - /// the standard in that instead of using SHAKE256 hash function in the hash-to-point algorithm - /// we use RPO256. This makes the signature more efficient to verify in Miden VM. + /// A minimal authentication scheme that provides no cryptographic authentication. + /// + /// It only increments the nonce if the account state has actually changed during transaction + /// execution, avoiding unnecessary nonce increments for transactions that don't modify the + /// account state. + NoAuth, + /// A single-key authentication scheme which relies RPO Falcon512 signatures. + /// + /// RPO Falcon512 is a variant of the [Falcon](https://falcon-sign.info/) signature scheme. + /// This variant differs from the standard in that instead of using SHAKE256 hash function in + /// the hash-to-point algorithm we use RPO256. This makes the signature more efficient to + /// verify in Miden VM. RpoFalcon512 { pub_key: PublicKeyCommitment }, /// A multi-signature authentication scheme using RPO Falcon512 signatures. + /// /// Requires a threshold number of signatures from the provided public keys. RpoFalcon512Multisig { threshold: u32, pub_keys: Vec, }, - /// A minimal authentication scheme that provides no cryptographic authentication. - /// It only increments the nonce if the account state has actually changed during - /// transaction execution, avoiding unnecessary nonce increments for transactions - /// that don't modify the account state. - NoAuth, /// A non-standard authentication scheme. Unknown, } + +impl AuthScheme { + /// Returns all public key commitments associated with this authentication scheme. + /// + /// For unknown schemes, an empty vector is returned. + pub fn get_public_key_commitments(&self) -> Vec { + match self { + AuthScheme::NoAuth => Vec::new(), + AuthScheme::RpoFalcon512 { pub_key } => vec![*pub_key], + AuthScheme::RpoFalcon512Multisig { pub_keys, .. } => pub_keys.clone(), + AuthScheme::Unknown => Vec::new(), + } + } +} diff --git a/crates/miden-lib/src/testing/account_interface.rs b/crates/miden-lib/src/testing/account_interface.rs index 082b006f03..dbac4986eb 100644 --- a/crates/miden-lib/src/testing/account_interface.rs +++ b/crates/miden-lib/src/testing/account_interface.rs @@ -3,26 +3,16 @@ use alloc::vec::Vec; use miden_objects::Word; use miden_objects::account::Account; -use crate::AuthScheme; use crate::account::interface::AccountInterface; /// Helper function to extract public keys from an account pub fn get_public_keys_from_account(account: &Account) -> Vec { - let mut pub_keys = vec![]; let interface: AccountInterface = account.into(); - for auth in interface.auth() { - match auth { - AuthScheme::NoAuth => {}, - AuthScheme::RpoFalcon512 { pub_key } => pub_keys.push(Word::from(*pub_key)), - AuthScheme::RpoFalcon512Multisig { pub_keys: multisig_keys, .. } => { - for key in multisig_keys { - pub_keys.push(Word::from(*key)); - } - }, - AuthScheme::Unknown => {}, - } - } - - pub_keys + interface + .auth() + .iter() + .flat_map(|auth| auth.get_public_key_commitments()) + .map(Word::from) + .collect() } From b8075dcec086e27682f13565e74c564bb5c4725f Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Fri, 3 Oct 2025 17:35:24 +0300 Subject: [PATCH 073/133] chore: update code after merge --- crates/miden-testing/tests/auth/multisig.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index c3056dcadc..cbff48ad82 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -975,18 +975,18 @@ async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment(), &tx_summary) + .get_signature(public_keys[0].to_commitment().into(), &tx_summary) .await?; let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment(), &tx_summary) + .get_signature(public_keys[1].to_commitment().into(), &tx_summary) .await?; // get the transaction context with signatures let tx_context_with_signatures = mock_chain .build_tx_context(multisig_account.id(), &[p2id_note.id()], &[])? .extend_expected_output_notes(vec![OutputNote::Full(p2id_note)]) - .add_signature(public_keys[0].clone(), msg, sig_1) - .add_signature(public_keys[1].clone(), msg, sig_2) + .add_signature(public_keys[0].clone().into(), msg, sig_1) + .add_signature(public_keys[1].clone().into(), msg, sig_2) .auth_args(salt) .build()?; From a2db1a961f54cb57589bdcbc652608d7969c236b Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Mon, 6 Oct 2025 13:40:05 +0200 Subject: [PATCH 074/133] cleanup: collect events from masm files, avoid some unnecessary conversions (#1954) --- CHANGELOG.md | 1 + Cargo.lock | 11 + crates/miden-lib/Cargo.toml | 1 + .../multisig_rpo_falcon_512.masm | 4 +- .../kernels/transaction/lib/asset_vault.masm | 11 +- .../asm/kernels/transaction/lib/epilogue.masm | 16 +- .../asm/kernels/transaction/main.masm | 40 ++-- .../asm/miden/auth/rpo_falcon512.masm | 6 +- crates/miden-lib/build.rs | 210 +++++++++++------- crates/miden-lib/src/transaction/events.rs | 25 +-- .../src/block/account_witness.rs | 36 ++- .../src/block/partial_account_tree.rs | 14 +- .../src/transaction/inputs/account.rs | 9 +- .../src/kernel_tests/tx/test_tx.rs | 4 +- crates/miden-testing/src/mock_host.rs | 4 +- crates/miden-tx/src/host/mod.rs | 8 +- 16 files changed, 225 insertions(+), 175 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6218766f75..cb68a56418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ - Simplify `MockChain` internals and rework its documentation ([#1942]https://github.com/0xMiden/miden-base/pull/1942). - [BREAKING] Change the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). - [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). +- Dynamically lookup all masm `EventId`s from source ([#1954](https://github.com/0xMiden/miden-base/pull/1954)). ## 0.11.5 (2025-10-02) diff --git a/Cargo.lock b/Cargo.lock index b0654532e7..ef2f23aeea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -1462,6 +1472,7 @@ dependencies = [ name = "miden-lib" version = "0.12.0" dependencies = [ + "Inflector", "anyhow", "assert_matches", "fs-err", diff --git a/crates/miden-lib/Cargo.toml b/crates/miden-lib/Cargo.toml index fe845f123a..288d2fb02b 100644 --- a/crates/miden-lib/Cargo.toml +++ b/crates/miden-lib/Cargo.toml @@ -32,6 +32,7 @@ rand = { optional = true, workspace = true } thiserror = { workspace = true } [build-dependencies] +Inflector = { version = "0.11" } fs-err = { version = "3" } miden-assembly = { workspace = true } miden-core = { workspace = true } diff --git a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm index 6812932a46..acd407060b 100644 --- a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm @@ -9,7 +9,7 @@ use.miden::auth # Auth Request Constants # The event emitted when a signature is not found for a required signer. -const.UNAUTHORIZED_EVENT=event("miden::auth::unauthorized") +const.AUTH_UNAUTHORIZED_EVENT=event("miden::auth::unauthorized") # Storage Layout Constants # @@ -298,7 +298,7 @@ export.auth_tx_rpo_falcon512_multisig # If signatures are non-existent the tx will fail here. if.true - emit.UNAUTHORIZED_EVENT + emit.AUTH_UNAUTHORIZED_EVENT push.0 assert.err="insufficient number of signatures" end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm index b1c8cb45e4..1afe27af2f 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm @@ -29,7 +29,10 @@ const.ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND="failed to remove non-exi # EVENTS # ================================================================================================= -const.SMT_PEEK=event("stdlib::collections::smt::smt_peek") +# The event is from `stdlib`, visible through the prefix and is _not_ a `TransactionEvent`. +# Care must be taken it stays in sync with the `miden-stdlib` definition. Note that generally speaking +# we should not emit `"stdlib"` events, consider this "hijacking", tread carefully. +const.SMT_PEEK_EVENT=event("stdlib::collections::smt::smt_peek") # CONSTANTS # ================================================================================================= @@ -111,7 +114,7 @@ export.peek_balance # => [ASSET_KEY, ASSET_VAULT_ROOT] # lookup asset - emit.SMT_PEEK + emit.SMT_PEEK_EVENT # OS => [ASSET_KEY, ASSET_VAULT_ROOT] # AS => [ASSET] @@ -204,7 +207,7 @@ export.add_fungible_asset mem_loadw swapw # => [ASSET_KEY, VAULT_ROOT, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] - emit.SMT_PEEK adv_loadw + emit.SMT_PEEK_EVENT adv_loadw # => [CUR_VAULT_VALUE, VAULT_ROOT, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] swapw # => [VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] @@ -536,7 +539,7 @@ proc.peek_asset # => [ASSET_KEY, ASSET_VAULT_ROOT] # lookup asset - emit.SMT_PEEK + emit.SMT_PEEK_EVENT # OS => [ASSET_KEY, ASSET_VAULT_ROOT] # AS => [ASSET] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm index 39d22e7900..c05d830cf5 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -22,16 +22,16 @@ const.ERR_AUTH_PROCEDURE_CALLED_FROM_WRONG_CONTEXT="auth procedure had been call # ================================================================================================= # Event emitted to signal that the compute_fee procedure has obtained the current number of cycles. -const.EPILOGUE_AFTER_TX_CYCLES_OBTAINED=event("miden::epilogue::after_tx_cycles_obtained") +const.EPILOGUE_AFTER_TX_CYCLES_OBTAINED_EVENT=event("miden::epilogue::after_tx_cycles_obtained") # Event emitted to signal that the fee was computed. -const.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT=event("miden::epilogue::before_tx_fee_removed_from_account") +const.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT_EVENT=event("miden::epilogue::before_tx_fee_removed_from_account") # Event emitted to signal that an execution of the authentication procedure has started. -const.EPILOGUE_AUTH_PROC_START=event("miden::epilogue::auth_proc_start") +const.EPILOGUE_AUTH_PROC_START_EVENT=event("miden::epilogue::auth_proc_start") # Event emitted to signal that an execution of the authentication procedure has ended. -const.EPILOGUE_AUTH_PROC_END=event("miden::epilogue::auth_proc_end") +const.EPILOGUE_AUTH_PROC_END_EVENT=event("miden::epilogue::auth_proc_end") # An additional number of cyclces to account for the number of cycles that smt::set will take when # removing the computed fee from the asset vault. @@ -204,7 +204,7 @@ end #! Inputs: [] #! Outputs: [] proc.execute_auth_procedure - emit.EPILOGUE_AUTH_PROC_START + emit.EPILOGUE_AUTH_PROC_START_EVENT padw padw padw # get the auth procedure arguments @@ -230,7 +230,7 @@ proc.execute_auth_procedure # clean up auth procedure outputs dropw dropw dropw dropw - emit.EPILOGUE_AUTH_PROC_END + emit.EPILOGUE_AUTH_PROC_END_EVENT end # FEE PROCEDURES @@ -252,7 +252,7 @@ proc.compute_fee clk # => [num_current_cycles] - emit.EPILOGUE_AFTER_TX_CYCLES_OBTAINED + emit.EPILOGUE_AFTER_TX_CYCLES_OBTAINED_EVENT # estimate the number of cycles the transaction will take add.ESTIMATED_AFTER_COMPUTE_FEE_CYCLES @@ -317,7 +317,7 @@ proc.compute_and_remove_fee exec.build_native_fee_asset # => [FEE_ASSET] - emit.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT + emit.EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT_EVENT # => [FEE_ASSET] # remove the fee from the native account's vault diff --git a/crates/miden-lib/asm/kernels/transaction/main.masm b/crates/miden-lib/asm/kernels/transaction/main.masm index dc399d0c91..d6f2c779f8 100644 --- a/crates/miden-lib/asm/kernels/transaction/main.masm +++ b/crates/miden-lib/asm/kernels/transaction/main.masm @@ -9,29 +9,29 @@ use.$kernel::prologue # ================================================================================================= # Event emitted to signal that an execution of the transaction prologue has started. -const.PROLOGUE_START=event("miden::tx::prologue_start") +const.PROLOGUE_START_EVENT=event("miden::tx::prologue_start") # Event emitted to signal that an execution of the transaction prologue has ended. -const.PROLOGUE_END=event("miden::tx::prologue_end") +const.PROLOGUE_END_EVENT=event("miden::tx::prologue_end") # Event emitted to signal that the notes processing has started. -const.NOTES_PROCESSING_START=event("miden::tx::notes_processing_start") +const.NOTES_PROCESSING_START_EVENT=event("miden::tx::notes_processing_start") # Event emitted to signal that the notes processing has ended. -const.NOTES_PROCESSING_END=event("miden::tx::notes_processing_end") +const.NOTES_PROCESSING_END_EVENT=event("miden::tx::notes_processing_end") # Event emitted to signal that the note consuming has started. -const.NOTE_EXECUTION_START=event("miden::tx::note_execution_start") +const.NOTE_EXECUTION_START_EVENT=event("miden::tx::note_execution_start") # Event emitted to signal that the note consuming has ended. -const.NOTE_EXECUTION_END=event("miden::tx::note_execution_end") +const.NOTE_EXECUTION_END_EVENT=event("miden::tx::note_execution_end") # Event emitted to signal that the transaction script processing has started. -const.TX_SCRIPT_PROCESSING_START=event("miden::tx::tx_script_processing_start") +const.TX_SCRIPT_PROCESSING_START_EVENT=event("miden::tx::tx_script_processing_start") # Event emitted to signal that the transaction script processing has ended. -const.TX_SCRIPT_PROCESSING_END=event("miden::tx::tx_script_processing_end") +const.TX_SCRIPT_PROCESSING_END_EVENT=event("miden::tx::tx_script_processing_end") # Event emitted to signal that an execution of the transaction epilogue has started. -const.EPILOGUE_START=event("miden::tx::epilogue_start") +const.EPILOGUE_START_EVENT=event("miden::tx::epilogue_start") # Event emitted to signal that an execution of the transaction epilogue has ended. -const.EPILOGUE_END=event("miden::tx::epilogue_end") +const.EPILOGUE_END_EVENT=event("miden::tx::epilogue_end") # MAIN # ================================================================================================= @@ -70,17 +70,17 @@ proc.main.1 # Prologue # --------------------------------------------------------------------------------------------- - emit.PROLOGUE_START + emit.PROLOGUE_START_EVENT exec.prologue::prepare_transaction # => [pad(16)] - emit.PROLOGUE_END + emit.PROLOGUE_END_EVENT # Note Processing # --------------------------------------------------------------------------------------------- - emit.NOTES_PROCESSING_START + emit.NOTES_PROCESSING_START_EVENT exec.memory::get_num_input_notes # => [num_input_notes, pad(16)] @@ -93,7 +93,7 @@ proc.main.1 # => [should_loop, pad(16)] while.true - emit.NOTE_EXECUTION_START + emit.NOTE_EXECUTION_START_EVENT # => [] exec.note::prepare_note @@ -114,18 +114,18 @@ proc.main.1 loc_load.0 neq # => [should_loop, pad(16)] - emit.NOTE_EXECUTION_END + emit.NOTE_EXECUTION_END_EVENT end exec.note::note_processing_teardown # => [pad(16)] - emit.NOTES_PROCESSING_END + emit.NOTES_PROCESSING_END_EVENT # Transaction Script Processing # --------------------------------------------------------------------------------------------- - emit.TX_SCRIPT_PROCESSING_START + emit.TX_SCRIPT_PROCESSING_START_EVENT # get the memory address of the transaction script root and load it to the stack exec.memory::get_tx_script_root_ptr @@ -157,12 +157,12 @@ proc.main.1 # => [pad(16)] end - emit.TX_SCRIPT_PROCESSING_END + emit.TX_SCRIPT_PROCESSING_END_EVENT # Epilogue # --------------------------------------------------------------------------------------------- - emit.EPILOGUE_START + emit.EPILOGUE_START_EVENT # execute the transaction epilogue exec.epilogue::finalize_transaction @@ -172,7 +172,7 @@ proc.main.1 repeat.13 movup.13 drop end # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET, tx_expiration_block_num, pad(3)] - emit.EPILOGUE_END + emit.EPILOGUE_END_EVENT end begin diff --git a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm index ab5944ea47..4394cb9d61 100644 --- a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm +++ b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm @@ -7,7 +7,7 @@ use.std::crypto::dsa::rpo_falcon512 # ================================================================================================= # The event to request an authentication signature. -const.AUTH_REQUEST=event("miden::auth::request") +const.AUTH_REQUEST_EVENT=event("miden::auth::request") # The slot in this component's storage layout where the public key is stored. const.PUBLIC_KEY_SLOT=0 @@ -56,7 +56,7 @@ export.authenticate_transaction # Fetch signature from advice provider and verify. # --------------------------------------------------------------------------------------------- # Emit the authentication request event that pushes a signature for the message to the advice stack - emit.AUTH_REQUEST + emit.AUTH_REQUEST_EVENT swapw # OS => [PUB_KEY, MESSAGE] # AS => [SIGNATURE] @@ -148,7 +148,7 @@ export.verify_signatures.16 # => [MSG, PK, MSG, i-1] # Emit the authentication request event that pushes a signature for the message to the advice stack. - emit.AUTH_REQUEST + emit.AUTH_REQUEST_EVENT swapw # OS => [PUB_KEY, MSG, MSG, i-1] diff --git a/crates/miden-lib/build.rs b/crates/miden-lib/build.rs index 37b3b918f3..060b109ec6 100644 --- a/crates/miden-lib/build.rs +++ b/crates/miden-lib/build.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet}; use std::env; use std::fmt::Write; use std::io::{self}; @@ -6,7 +6,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use fs_err as fs; -use miden_assembly::diagnostics::{IntoDiagnostic, Result, WrapErr}; +use miden_assembly::diagnostics::{IntoDiagnostic, Result, WrapErr, miette}; use miden_assembly::utils::Serializable; use miden_assembly::{ Assembler, @@ -118,7 +118,7 @@ fn main() -> Result<()> { generate_error_constants(&source_dir)?; - generate_event_constants(&target_dir).into_diagnostic()?; + generate_event_constants(&source_dir, &target_dir)?; Ok(()) } @@ -779,90 +779,140 @@ impl TxKernelErrorCategory { } } -fn generate_event_constants(dst: &Path) -> std::io::Result<()> { - let values = [ - ("ACCOUNT_BEFORE_FOREIGN_LOAD", "account::before_foreign_load"), - ("ACCOUNT_VAULT_BEFORE_ADD_ASSET", "account::vault_before_add_asset"), - ("ACCOUNT_VAULT_AFTER_ADD_ASSET", "account::vault_after_add_asset"), - ("ACCOUNT_VAULT_BEFORE_REMOVE_ASSET", "account::vault_before_remove_asset"), - ("ACCOUNT_VAULT_AFTER_REMOVE_ASSET", "account::vault_after_remove_asset"), - ("ACCOUNT_VAULT_BEFORE_GET_BALANCE", "account::vault_before_get_balance"), - ( - "ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET", - "account::vault_before_has_non_fungible_asset", - ), - ("ACCOUNT_STORAGE_BEFORE_SET_ITEM", "account::storage_before_set_item"), - ("ACCOUNT_STORAGE_AFTER_SET_ITEM", "account::storage_after_set_item"), - ("ACCOUNT_STORAGE_BEFORE_GET_MAP_ITEM", "account::storage_before_get_map_item"), - ("ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM", "account::storage_before_set_map_item"), - ("ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM", "account::storage_after_set_map_item"), - ("ACCOUNT_BEFORE_INCREMENT_NONCE", "account::before_increment_nonce"), - ("ACCOUNT_AFTER_INCREMENT_NONCE", "account::after_increment_nonce"), - ("ACCOUNT_PUSH_PROCEDURE_INDEX", "account::push_procedure_index"), - ("NOTE_BEFORE_CREATED", "note::before_created"), - ("NOTE_AFTER_CREATED", "note::after_created"), - ("NOTE_BEFORE_ADD_ASSET", "note::before_add_asset"), - ("NOTE_AFTER_ADD_ASSET", "note::after_add_asset"), - ("AUTH_REQUEST", "auth::request"), - ("PROLOGUE_START", "tx::prologue_start"), - ("PROLOGUE_END", "tx::prologue_end"), - ("NOTES_PROCESSING_START", "tx::notes_processing_start"), - ("NOTES_PROCESSING_END", "tx::notes_processing_end"), - ("NOTE_EXECUTION_START", "tx::note_execution_start"), - ("NOTE_EXECUTION_END", "tx::note_execution_end"), - ("TX_SCRIPT_PROCESSING_START", "tx::tx_script_processing_start"), - ("TX_SCRIPT_PROCESSING_END", "tx::tx_script_processing_end"), - ("EPILOGUE_START", "tx::epilogue_start"), - ("EPILOGUE_END", "tx::epilogue_end"), - ("EPILOGUE_AFTER_TX_CYCLES_OBTAINED", "epilogue::after_tx_cycles_obtained"), - ( - "EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT", - "epilogue::before_tx_fee_removed_from_account", - ), - ("LINK_MAP_SET_EVENT", "link_map::set"), - ("LINK_MAP_GET_EVENT", "link_map::get"), - ("UNAUTHORIZED_EVENT", "auth::unauthorized"), - ("EPILOGUE_AUTH_PROC_START", "epilogue::auth_proc_start"), - ("EPILOGUE_AUTH_PROC_END", "epilogue::auth_proc_end"), - ]; - - use std::io::Write as _; - - let map = HashMap::::from_iter(values.iter().map(|&(konst, name)| { - let name = format!("miden::{name}"); - let value = miden_core::EventId::from_name(&name).as_felt().as_int(); - (name, (konst, value)) - })); - - let path = dst.join("transaction_events.rs"); - let mut f = fs::OpenOptions::new().create(true).truncate(true).write(true).open(path)?; - for (_name, (konst, value)) in map.iter() { - writeln!(&mut f, "const {konst}: u64 = {value};")?; +// EVENT CONSTANTS FILE GENERATION +// ================================================================================================ + +/// Reads all MASM files from the `asm_source_dir` and extracts event definitions, +/// then generates the transaction_events.rs file with constants. +fn generate_event_constants(asm_source_dir: &Path, target_dir: &Path) -> Result<()> { + // Extract all event definitions from MASM files + let events = extract_all_event_definitions(asm_source_dir)?; + + // Generate the events file in OUT_DIR + let event_file_content = generate_event_file_content(&events).into_diagnostic()?; + let event_file_path = target_dir.join("transaction_events.rs"); + fs::write(event_file_path, event_file_content).into_diagnostic()?; + + Ok(()) +} + +/// Extract all `const.X=event("x")` definitions from all MASM files +fn extract_all_event_definitions(asm_source_dir: &Path) -> Result> { + // collect mappings event path to const variable name, we want a unique mapping + // which we use to generate the constants and enum variant names + let mut events = BTreeMap::new(); + + // Walk all MASM files + for entry in WalkDir::new(asm_source_dir) { + let entry = entry.into_diagnostic()?; + if !is_masm_file(entry.path()).into_diagnostic()? { + continue; + } + let file_contents = fs::read_to_string(entry.path()).into_diagnostic()?; + extract_event_definitions_from_file(&mut events, &file_contents, entry.path())?; } - #[cfg(feature = "std")] - { - writeln!(&mut f, "// This file is generated by build.rs, do not modify")?; + Ok(events) +} + +/// Extract `const.${X}=event("${x::path}")` definitions from a single MASM file +fn extract_event_definitions_from_file( + events: &mut BTreeMap, + file_contents: &str, + file_path: &Path, +) -> Result<()> { + let regex = Regex::new(r#"const\.(\w+)=event\("([^"]+)"\)"#).unwrap(); + + for capture in regex.captures_iter(file_contents) { + let const_name = capture.get(1).expect("const name should be captured"); + let event_path = capture.get(2).expect("event path should be captured"); + + let event_path = event_path.as_str(); + let const_name = const_name.as_str(); + + let const_name_wo_suffix = + if let Some((const_name_wo_suffix, _)) = const_name.rsplit_once("_EVENT") { + const_name_wo_suffix.to_string() + } else { + const_name.to_owned() + }; - // we require a form of lut, but we don't want to bother supporting non-std cases for now - let mut inner = String::new(); - for (name, (_, value)) in map.iter() { - inner.push_str(format!(" ({value}, \"{name}\"),\n").as_str()); + if !event_path.starts_with("miden::") { + // we ignore any `stdlib::` prefixed ones + if !event_path.starts_with("stdlib::") { + return Err(miette::miette!( + "unhandled `event_path={event_path}`, doesn't with `stdlib::` nor with `miden::`." + )); + } + continue; } - writeln!(&mut f)?; - writeln!(&mut f, "// Reverse lookup table for better error messages")?; - writeln!(&mut f)?; + // Check for duplicates with different definitions + if let Some(existing_const_name) = events.get(event_path) { + if existing_const_name != &const_name_wo_suffix { + println!( + "cargo:warning=Duplicate event definition found {event_path} with different definitions names: + '{existing_const_name}' vs '{const_name}' in {}", + file_path.display() + ); + } + } else { + events.insert(event_path.to_owned(), const_name_wo_suffix.to_owned()); + } + } + + Ok(()) +} + +/// Generate the content of the transaction_events.rs file +fn generate_event_file_content( + events: &BTreeMap, +) -> std::result::Result { + use std::fmt::Write; + + let mut output = String::new(); + + writeln!(&mut output, "// This file is generated by build.rs, do not modify")?; + writeln!(&mut output)?; + + // Generate constants + // + // Note: If we ever encounter two constants `const.X`, that are both named `X` we will error + // when attempting to generate the rust code. Currently this is a side-effect, but we + // want to error out as early as possible: + // TODO: make the error out at build-time to be able to present better error hints + for (event_path, event_name) in events { + let value = miden_core::EventId::from_name(event_path).as_felt().as_int(); + debug_assert!(!event_name.is_empty()); + writeln!(&mut output, "const {}: u64 = {};", event_name, value)?; + } + + { + writeln!(&mut output)?; + + writeln!(&mut output)?; + writeln!( - &mut f, + &mut output, r###" -pub(crate) static EVENT_NAME_LUT: ::miden_objects::utils::sync::LazyLock<::std::collections::HashMap> = ::miden_objects::utils::sync::LazyLock::new(|| {{ - ::std::collections::HashMap::from_iter([ - {inner} - ]) -}}); - "### +use alloc::collections::BTreeMap; + +pub(crate) static EVENT_NAME_LUT: ::miden_objects::utils::sync::LazyLock> = + ::miden_objects::utils::sync::LazyLock::new(|| {{ + BTreeMap::from_iter([ +"### + )?; + + for (event_path, const_name) in events { + writeln!(&mut output, " ({}, \"{}\"),", const_name, event_path)?; + } + + writeln!( + &mut output, + r###" ]) +}});"### )?; } - Ok(()) + + Ok(output) } diff --git a/crates/miden-lib/src/transaction/events.rs b/crates/miden-lib/src/transaction/events.rs index add77d7302..5f0069bbc7 100644 --- a/crates/miden-lib/src/transaction/events.rs +++ b/crates/miden-lib/src/transaction/events.rs @@ -28,9 +28,9 @@ pub enum TransactionEvent { AccountVaultBeforeRemoveAsset = ACCOUNT_VAULT_BEFORE_REMOVE_ASSET, AccountVaultAfterRemoveAsset = ACCOUNT_VAULT_AFTER_REMOVE_ASSET, - AccountVaultBeforeGetBalanceEvent = ACCOUNT_VAULT_BEFORE_GET_BALANCE, + AccountVaultBeforeGetBalance = ACCOUNT_VAULT_BEFORE_GET_BALANCE, - AccountVaultBeforeHasNonFungibleAssetEvent = ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET, + AccountVaultBeforeHasNonFungibleAsset = ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET, AccountStorageBeforeSetItem = ACCOUNT_STORAGE_BEFORE_SET_ITEM, AccountStorageAfterSetItem = ACCOUNT_STORAGE_AFTER_SET_ITEM, @@ -74,10 +74,10 @@ pub enum TransactionEvent { EpilogueAfterTxCyclesObtained = EPILOGUE_AFTER_TX_CYCLES_OBTAINED, EpilogueBeforeTxFeeRemovedFromAccount = EPILOGUE_BEFORE_TX_FEE_REMOVED_FROM_ACCOUNT, - LinkMapSetEvent = LINK_MAP_SET_EVENT, - LinkMapGetEvent = LINK_MAP_GET_EVENT, + LinkMapSet = LINK_MAP_SET, + LinkMapGet = LINK_MAP_GET, - Unauthorized = UNAUTHORIZED_EVENT, + Unauthorized = AUTH_UNAUTHORIZED, } impl TransactionEvent { @@ -101,10 +101,7 @@ impl TryFrom for TransactionEvent { fn try_from(value: EventId) -> Result { let raw = value.as_felt().as_int(); - #[cfg(feature = "std")] let name = EVENT_NAME_LUT.get(&raw).copied(); - #[cfg(not(feature = "std"))] - let name = Some(""); if value.is_reserved() { return Err(TransactionEventError::ReservedSystemEvent(value)); @@ -121,12 +118,10 @@ impl TryFrom for TransactionEvent { }, ACCOUNT_VAULT_AFTER_REMOVE_ASSET => Ok(TransactionEvent::AccountVaultAfterRemoveAsset), - ACCOUNT_VAULT_BEFORE_GET_BALANCE => { - Ok(TransactionEvent::AccountVaultBeforeGetBalanceEvent) - }, + ACCOUNT_VAULT_BEFORE_GET_BALANCE => Ok(TransactionEvent::AccountVaultBeforeGetBalance), ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET => { - Ok(TransactionEvent::AccountVaultBeforeHasNonFungibleAssetEvent) + Ok(TransactionEvent::AccountVaultBeforeHasNonFungibleAsset) }, ACCOUNT_STORAGE_BEFORE_SET_ITEM => Ok(TransactionEvent::AccountStorageBeforeSetItem), @@ -179,10 +174,10 @@ impl TryFrom for TransactionEvent { }, EPILOGUE_END => Ok(TransactionEvent::EpilogueEnd), - LINK_MAP_SET_EVENT => Ok(TransactionEvent::LinkMapSetEvent), - LINK_MAP_GET_EVENT => Ok(TransactionEvent::LinkMapGetEvent), + LINK_MAP_SET => Ok(TransactionEvent::LinkMapSet), + LINK_MAP_GET => Ok(TransactionEvent::LinkMapGet), - UNAUTHORIZED_EVENT => Ok(TransactionEvent::Unauthorized), + AUTH_UNAUTHORIZED => Ok(TransactionEvent::Unauthorized), _ => Err(TransactionEventError::InvalidTransactionEvent(value, name)), } diff --git a/crates/miden-objects/src/block/account_witness.rs b/crates/miden-objects/src/block/account_witness.rs index 5be295e653..6b7b0c003d 100644 --- a/crates/miden-objects/src/block/account_witness.rs +++ b/crates/miden-objects/src/block/account_witness.rs @@ -3,7 +3,6 @@ use alloc::string::ToString; use miden_crypto::merkle::{ InnerNodeInfo, LeafIndex, - MerklePath, SMT_DEPTH, SmtLeaf, SmtProof, @@ -40,7 +39,7 @@ pub struct AccountWitness { /// The state commitment of the account ID. commitment: Word, /// The merkle path of the account witness. - path: MerklePath, + path: SparseMerklePath, } impl AccountWitness { @@ -53,11 +52,11 @@ impl AccountWitness { pub fn new( account_id: AccountId, commitment: Word, - path: MerklePath, + path: SparseMerklePath, ) -> Result { - if path.len() != SMT_DEPTH as usize { + if path.depth() != SMT_DEPTH { return Err(AccountTreeError::WitnessMerklePathDepthDoesNotMatchAccountTreeDepth( - path.len(), + path.depth() as usize, )); } @@ -105,7 +104,7 @@ impl AccountWitness { // the account trees. debug_assert_eq!(proof.path().depth(), AccountTree::DEPTH); - AccountWitness::new_unchecked(witness_id, commitment, proof.into_parts().0.into()) + AccountWitness::new_unchecked(witness_id, commitment, proof.into_parts().0) } /// Constructs a new [`AccountWitness`] from the provided parts. @@ -113,7 +112,11 @@ impl AccountWitness { /// # Warning /// /// This does not validate any of the guarantees of this type. - pub(super) fn new_unchecked(account_id: AccountId, commitment: Word, path: MerklePath) -> Self { + pub(super) fn new_unchecked( + account_id: AccountId, + commitment: Word, + path: SparseMerklePath, + ) -> Self { Self { id: account_id, commitment, path } } @@ -127,8 +130,8 @@ impl AccountWitness { self.commitment } - /// Returns the [`MerklePath`] of the account witness. - pub fn path(&self) -> &MerklePath { + /// Returns the [`SparseMerklePath`] of the account witness. + pub fn path(&self) -> &SparseMerklePath { &self.path } @@ -147,13 +150,8 @@ impl AccountWitness { pub fn into_proof(self) -> SmtProof { let leaf = self.leaf(); debug_assert_eq!(self.path.depth(), AccountTree::DEPTH); - SmtProof::new( - SparseMerklePath::try_from(self.path).expect( - "only ever exists for merkle paths that match the SMT depth by construction", - ), - leaf, - ) - .expect("merkle path depth should be the SMT depth by construction") + SmtProof::new(self.path, leaf) + .expect("merkle path depth should be the SMT depth by construction") } /// Returns an iterator over every inner node of this witness' merkle path. @@ -186,11 +184,11 @@ impl Deserializable for AccountWitness { fn read_from(source: &mut R) -> Result { let id = AccountId::read_from(source)?; let commitment = Word::read_from(source)?; - let path = MerklePath::read_from(source)?; + let path = SparseMerklePath::read_from(source)?; - if path.len() != SMT_DEPTH as usize { + if path.depth() != SMT_DEPTH { return Err(DeserializationError::InvalidValue( - SmtProofError::InvalidMerklePathLength(path.len()).to_string(), + SmtProofError::InvalidMerklePathLength(path.depth() as usize).to_string(), )); } diff --git a/crates/miden-objects/src/block/partial_account_tree.rs b/crates/miden-objects/src/block/partial_account_tree.rs index b11db52277..44e09c43b5 100644 --- a/crates/miden-objects/src/block/partial_account_tree.rs +++ b/crates/miden-objects/src/block/partial_account_tree.rs @@ -270,16 +270,10 @@ mod tests { let proof1 = full_tree.open(&key1); assert_eq!(proof0.leaf(), proof1.leaf()); - let witness0 = AccountWitness::new_unchecked( - id0, - proof0.get(&key0).unwrap(), - proof0.into_parts().0.into(), - ); - let witness1 = AccountWitness::new_unchecked( - id1, - proof1.get(&key1).unwrap(), - proof1.into_parts().0.into(), - ); + let witness0 = + AccountWitness::new_unchecked(id0, proof0.get(&key0).unwrap(), proof0.into_parts().0); + let witness1 = + AccountWitness::new_unchecked(id1, proof1.get(&key1).unwrap(), proof1.into_parts().0); let mut partial_tree = PartialAccountTree::new(); partial_tree.track_account(witness0).unwrap(); diff --git a/crates/miden-objects/src/transaction/inputs/account.rs b/crates/miden-objects/src/transaction/inputs/account.rs index d1c09e831b..4d7dd2faf9 100644 --- a/crates/miden-objects/src/transaction/inputs/account.rs +++ b/crates/miden-objects/src/transaction/inputs/account.rs @@ -1,5 +1,3 @@ -use miden_crypto::merkle::SparseMerklePath; - use crate::Word; use crate::account::{AccountCode, AccountId, PartialAccount, PartialStorage}; use crate::asset::PartialVault; @@ -68,8 +66,6 @@ impl AccountInputs { /// This root should be equal to the account root in the reference block header. pub fn compute_account_root(&self) -> Result { let smt_merkle_path = self.witness.path().clone(); - let smt_merkle_path = SparseMerklePath::try_from(smt_merkle_path) - .expect("Only ever exists for merkle paths that match the SMT depth by construction"); let smt_leaf = self.witness.leaf(); let root = SmtProof::new(smt_merkle_path, smt_leaf)?.compute_root(); @@ -102,7 +98,7 @@ mod tests { use miden_core::Felt; use miden_core::utils::{Deserializable, Serializable}; - use miden_crypto::merkle::MerklePath; + use miden_crypto::merkle::SparseMerklePath; use miden_processor::SMT_DEPTH; use crate::account::{Account, AccountCode, AccountId, AccountStorage, PartialAccount}; @@ -125,7 +121,8 @@ mod tests { for _ in 0..(SMT_DEPTH as usize) { merkle_nodes.push(commitment); } - let merkle_path = MerklePath::new(merkle_nodes); + let merkle_path = SparseMerklePath::from_sized_iter(merkle_nodes) + .expect("The nodes given are of SMT_DEPTH count"); let fpi_inputs = AccountInputs::new( PartialAccount::from(&account), diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 2a092ef8fc..178e0c2506 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -455,7 +455,7 @@ fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { let source_code = r#" use.miden::auth use.miden::tx - const.ABORT_EVENT=event("miden::auth::unauthorized") + const.AUTH_UNAUTHORIZED_EVENT=event("miden::auth::unauthorized") #! Inputs: [AUTH_ARGS, pad(12)] #! Outputs: [pad(16)] export.auth_abort_tx @@ -475,7 +475,7 @@ fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { exec.auth::hash_tx_summary # => [MESSAGE, pad(16)] - emit.ABORT_EVENT + emit.AUTH_UNAUTHORIZED_EVENT end "#; diff --git a/crates/miden-testing/src/mock_host.rs b/crates/miden-testing/src/mock_host.rs index 6dcac84aa4..ba7fd8bbfd 100644 --- a/crates/miden-testing/src/mock_host.rs +++ b/crates/miden-testing/src/mock_host.rs @@ -130,8 +130,8 @@ impl SyncHost for MockHost { TransactionEvent::AccountPushProcedureIndex => { self.on_push_account_procedure_index(process) }, - TransactionEvent::LinkMapSetEvent => LinkMap::handle_set_event(process), - TransactionEvent::LinkMapGetEvent => LinkMap::handle_get_event(process), + TransactionEvent::LinkMapSet => LinkMap::handle_set_event(process), + TransactionEvent::LinkMapGet => LinkMap::handle_get_event(process), _ => Ok(Vec::new()), }?; diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 6982b26476..8312d2184c 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -270,11 +270,11 @@ where self.on_account_vault_after_remove_asset(process).map(|_| TransactionEventHandling::Handled(Vec::new())) }, - TransactionEvent::AccountVaultBeforeGetBalanceEvent => { + TransactionEvent::AccountVaultBeforeGetBalance => { self.on_account_vault_before_get_balance(process) }, - TransactionEvent::AccountVaultBeforeHasNonFungibleAssetEvent => { + TransactionEvent::AccountVaultBeforeHasNonFungibleAsset => { self.on_account_vault_before_has_non_fungible_asset(process) } @@ -373,10 +373,10 @@ where self.tx_progress.end_epilogue(process.clk()); Ok(TransactionEventHandling::Handled(Vec::new())) } - TransactionEvent::LinkMapSetEvent => { + TransactionEvent::LinkMapSet => { return LinkMap::handle_set_event(process).map(TransactionEventHandling::Handled); }, - TransactionEvent::LinkMapGetEvent => { + TransactionEvent::LinkMapGet => { return LinkMap::handle_get_event(process).map(TransactionEventHandling::Handled); }, TransactionEvent::Unauthorized => { From 57ddb798f3336e97eaa0e7d566834b0a09323a73 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 7 Oct 2025 13:13:10 +0200 Subject: [PATCH 075/133] feat: Enable computing the transaction ID from the data in a `TransactionHeader` (#1973) * feat: Make `build_output_notes_commitment` an inherent method * feat: Track `NoteMetadata` for output notes in `TransactionHeader` * feat: Change TX header to track input notes commitments * chore: add changelog * chore: Use `NoteHeader` instead of tuple --- CHANGELOG.md | 1 + .../miden-objects/src/transaction/outputs.rs | 44 ++++----- .../src/transaction/tx_header.rs | 90 ++++++++++++------- 3 files changed, 80 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb68a56418..4d7bce0587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937)) - Added `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935]https://github.com/0xMiden/miden-base/pull/1935). - Added `AssetVault::{num_assets, num_leaves, inner_nodes}` ([#1939]https://github.com/0xMiden/miden-base/pull/1939). +- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). ### Changes diff --git a/crates/miden-objects/src/transaction/outputs.rs b/crates/miden-objects/src/transaction/outputs.rs index e030ae142e..f45e313a54 100644 --- a/crates/miden-objects/src/transaction/outputs.rs +++ b/crates/miden-objects/src/transaction/outputs.rs @@ -103,7 +103,7 @@ impl OutputNotes { } } - let commitment = build_output_notes_commitment(¬es); + let commitment = Self::compute_commitment(notes.iter().map(NoteHeader::from)); Ok(Self { notes, commitment }) } @@ -140,6 +140,27 @@ impl OutputNotes { pub fn iter(&self) -> impl Iterator { self.notes.iter() } + + // HELPERS + // -------------------------------------------------------------------------------------------- + + /// Computes a commitment to output notes. + /// + /// For a non-empty list of notes, this is a sequential hash of (note_id, metadata) tuples for + /// the notes created in a transaction. For an empty list, [EMPTY_WORD] is returned. + pub(crate) fn compute_commitment(notes: impl ExactSizeIterator) -> Word { + if notes.len() == 0 { + return Word::empty(); + } + + let mut elements: Vec = Vec::with_capacity(notes.len() * 8); + for note_header in notes { + elements.extend_from_slice(note_header.id().as_elements()); + elements.extend_from_slice(Word::from(note_header.metadata()).as_elements()); + } + + Hasher::hash_elements(&elements) + } } // SERIALIZATION @@ -307,27 +328,6 @@ impl Deserializable for OutputNote { } } -// HELPER FUNCTIONS -// ================================================================================================ - -/// Build a commitment to output notes. -/// -/// For a non-empty list of notes, this is a sequential hash of (note_id, metadata) tuples for the -/// notes created in a transaction. For an empty list, [EMPTY_WORD] is returned. -fn build_output_notes_commitment(notes: &[OutputNote]) -> Word { - if notes.is_empty() { - return Word::empty(); - } - - let mut elements: Vec = Vec::with_capacity(notes.len() * 8); - for note in notes.iter() { - elements.extend_from_slice(note.id().as_elements()); - elements.extend_from_slice(Word::from(note.metadata()).as_elements()); - } - - Hasher::hash_elements(&elements) -} - // TESTS // ================================================================================================ diff --git a/crates/miden-objects/src/transaction/tx_header.rs b/crates/miden-objects/src/transaction/tx_header.rs index a3ae0e4b09..bdec27be5e 100644 --- a/crates/miden-objects/src/transaction/tx_header.rs +++ b/crates/miden-objects/src/transaction/tx_header.rs @@ -3,12 +3,12 @@ use alloc::vec::Vec; use miden_processor::DeserializationError; use crate::Word; -use crate::note::NoteId; +use crate::note::NoteHeader; use crate::transaction::{ AccountId, InputNoteCommitment, - Nullifier, - OutputNote, + InputNotes, + OutputNotes, ProvenTransaction, TransactionId, }; @@ -27,8 +27,8 @@ pub struct TransactionHeader { account_id: AccountId, initial_state_commitment: Word, final_state_commitment: Word, - input_notes: Vec, - output_notes: Vec, + input_notes: InputNotes, + output_notes: Vec, } impl TransactionHeader { @@ -37,18 +37,30 @@ impl TransactionHeader { /// Constructs a new [`TransactionHeader`] from the provided parameters. /// - /// Note that the nullifiers of the input notes and note IDs of the output notes must be in the - /// same order as they appeared in the transaction. This is ensured when constructing this type - /// from a proven transaction, but cannot be validated during deserialization, hence additional - /// validation is necessary. - pub(crate) fn new( - id: TransactionId, + /// The [`TransactionId`] is computed from the provided parameters. + /// + /// The input notes and output notes must be in the same order as they appeared in the + /// transaction that this header represents, otherwise an incorrect ID will be computed. + /// + /// Note that this cannot validate that the [`AccountId`] is valid with respect to the other + /// data. This must be validated outside of this type. + pub fn new( account_id: AccountId, initial_state_commitment: Word, final_state_commitment: Word, - input_notes: Vec, - output_notes: Vec, + input_notes: InputNotes, + output_notes: Vec, ) -> Self { + let input_notes_commitment = input_notes.commitment(); + let output_notes_commitment = OutputNotes::compute_commitment(output_notes.iter().copied()); + + let id = TransactionId::new( + initial_state_commitment, + final_state_commitment, + input_notes_commitment, + output_notes_commitment, + ); + Self { id, account_id, @@ -59,24 +71,28 @@ impl TransactionHeader { } } - /// Constructs a new [`TransactionHeader`] from the provided parameters for testing purposes. - #[cfg(any(feature = "testing", test))] + /// Constructs a new [`TransactionHeader`] from the provided parameters. + /// + /// # Warning + /// + /// This does not validate the internal consistency of the data. Prefer [`Self::new`] whenever + /// possible. pub fn new_unchecked( id: TransactionId, account_id: AccountId, initial_state_commitment: Word, final_state_commitment: Word, - input_notes: Vec, - output_notes: Vec, + input_notes: InputNotes, + output_notes: Vec, ) -> Self { - Self::new( + Self { id, account_id, initial_state_commitment, final_state_commitment, input_notes, output_notes, - ) + } } // PUBLIC ACCESSORS @@ -104,32 +120,41 @@ impl TransactionHeader { self.final_state_commitment } - /// Returns a reference to the nullifiers of the consumed notes. + /// Returns a reference to the consumed notes of the transaction. + /// + /// The returned input note commitments have the same order as the transaction to which the + /// header belongs. /// /// Note that the note may have been erased at the batch or block level, so it may not be /// present there. - pub fn input_notes(&self) -> &[Nullifier] { + pub fn input_notes(&self) -> &InputNotes { &self.input_notes } - /// Returns a reference to the notes created by the transaction. + /// Returns a reference to the ID and metadata of the output notes created by the transaction. + /// + /// The returned output note data has the same order as the transaction to which the header + /// belongs. /// /// Note that the note may have been erased at the batch or block level, so it may not be /// present there. - pub fn output_notes(&self) -> &[NoteId] { + pub fn output_notes(&self) -> &[NoteHeader] { &self.output_notes } } impl From<&ProvenTransaction> for TransactionHeader { + /// Constructs a [`TransactionHeader`] from a [`ProvenTransaction`]. fn from(tx: &ProvenTransaction) -> Self { - TransactionHeader::new( + // SAFETY: The data in a proven transaction is guaranteed to be internally consistent and so + // we can skip the consistency checks by the `new` constructor. + TransactionHeader::new_unchecked( tx.id(), tx.account_id(), tx.account_update().initial_state_commitment(), tx.account_update().final_state_commitment(), - tx.input_notes().iter().map(InputNoteCommitment::nullifier).collect(), - tx.output_notes().iter().map(OutputNote::id).collect(), + tx.input_notes().clone(), + tx.output_notes().iter().map(NoteHeader::from).collect(), ) } } @@ -139,7 +164,6 @@ impl From<&ProvenTransaction> for TransactionHeader { impl Serializable for TransactionHeader { fn write_into(&self, target: &mut W) { - self.id.write_into(target); self.account_id.write_into(target); self.initial_state_commitment.write_into(target); self.final_state_commitment.write_into(target); @@ -150,20 +174,20 @@ impl Serializable for TransactionHeader { impl Deserializable for TransactionHeader { fn read_from(source: &mut R) -> Result { - let id = ::read_from(source)?; let account_id = ::read_from(source)?; let initial_state_commitment = ::read_from(source)?; let final_state_commitment = ::read_from(source)?; - let input_notes = >::read_from(source)?; - let output_notes = >::read_from(source)?; + let input_notes = >::read_from(source)?; + let output_notes = >::read_from(source)?; - Ok(Self::new( - id, + let tx_header = Self::new( account_id, initial_state_commitment, final_state_commitment, input_notes, output_notes, - )) + ); + + Ok(tx_header) } } From d07f46e5a2e9e95e96c6dd26e62e7071687af005 Mon Sep 17 00:00:00 2001 From: Marti Date: Tue, 7 Oct 2025 15:28:31 +0200 Subject: [PATCH 076/133] chore: remove `execute_blocking` (#1958) * chore: replace execute_blocking with async calls * chore: replace part 2 * chore: replace 3 * Update crates/miden-testing/src/kernel_tests/tx/test_tx.rs * chore: remove custom thread * chore: single tokio runtime for benches * chore: remove async_trait --- Cargo.lock | 1 + bin/bench-note-checker/benches/benchmarks.rs | 8 +- bin/bench-transaction/Cargo.toml | 3 +- bin/bench-transaction/src/main.rs | 12 +- .../src/time_counting_benchmarks/prove.rs | 118 +++++----- crates/miden-testing/Cargo.toml | 5 +- .../block/proposed_block_errors.rs | 131 ++++++----- .../block/proposed_block_success.rs | 59 ++--- .../kernel_tests/block/proven_block_error.rs | 44 ++-- .../block/proven_block_success.rs | 41 ++-- .../src/kernel_tests/block/utils.rs | 44 ++-- .../src/kernel_tests/tx/test_account.rs | 75 ++++--- .../src/kernel_tests/tx/test_account_delta.rs | 55 +++-- .../src/kernel_tests/tx/test_active_note.rs | 10 +- .../src/kernel_tests/tx/test_auth.rs | 18 +- .../src/kernel_tests/tx/test_epilogue.rs | 13 +- .../src/kernel_tests/tx/test_faucet.rs | 35 +-- .../src/kernel_tests/tx/test_fee.rs | 39 ++-- .../src/kernel_tests/tx/test_fpi.rs | 211 +++++++++--------- .../src/kernel_tests/tx/test_input_note.rs | 42 ++-- .../src/kernel_tests/tx/test_lazy_loading.rs | 33 +-- .../src/kernel_tests/tx/test_note.rs | 14 +- .../src/kernel_tests/tx/test_output_note.rs | 25 ++- .../src/kernel_tests/tx/test_prologue.rs | 42 ++-- .../src/kernel_tests/tx/test_tx.rs | 53 +++-- crates/miden-testing/src/mock_chain/chain.rs | 14 +- .../miden-testing/src/tx_context/context.rs | 12 - .../tests/auth/rpo_falcon_acl.rs | 28 ++- crates/miden-testing/tests/scripts/faucet.rs | 32 +-- crates/miden-testing/tests/scripts/fee.rs | 7 +- crates/miden-testing/tests/scripts/p2id.rs | 29 +-- crates/miden-testing/tests/scripts/p2ide.rs | 72 +++--- .../miden-testing/tests/scripts/send_note.rs | 14 +- crates/miden-testing/tests/scripts/swap.rs | 34 +-- 34 files changed, 737 insertions(+), 636 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef2f23aeea..00cd0181fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,6 +259,7 @@ dependencies = [ "rand_chacha", "serde", "serde_json", + "tokio", ] [[package]] diff --git a/bin/bench-note-checker/benches/benchmarks.rs b/bin/bench-note-checker/benches/benchmarks.rs index 801de09028..b09a138f72 100644 --- a/bin/bench-note-checker/benches/benchmarks.rs +++ b/bin/bench-note-checker/benches/benchmarks.rs @@ -22,12 +22,8 @@ fn note_checker_benchmarks(c: &mut Criterion) { setup_mixed_notes_benchmark(MixedNotesConfig { failing_note_count: failing_count }) .expect("failed to set up mixed notes benchmark"); - b.iter(|| { - let runtime = - tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); - - runtime.block_on(async { black_box(run_mixed_notes_check(&setup).await) }) - }); + b.to_async(tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap()) + .iter(|| async { black_box(run_mixed_notes_check(&setup).await) }); }); } diff --git a/bin/bench-transaction/Cargo.toml b/bin/bench-transaction/Cargo.toml index 7c2639af60..a269825112 100644 --- a/bin/bench-transaction/Cargo.toml +++ b/bin/bench-transaction/Cargo.toml @@ -30,6 +30,7 @@ anyhow = { workspace = true } rand_chacha = { default-features = false, version = "0.9" } serde = { features = ["derive"], version = "1.0" } serde_json = { features = ["preserve_order"], package = "serde_json", version = "1.0" } +tokio = { features = ["macros", "rt"], workspace = true } [dev-dependencies] -criterion = { features = ["html_reports"], version = "0.6" } +criterion = { features = ["async_tokio", "html_reports"], version = "0.6" } diff --git a/bin/bench-transaction/src/main.rs b/bin/bench-transaction/src/main.rs index 097fd5419c..c036936eea 100644 --- a/bin/bench-transaction/src/main.rs +++ b/bin/bench-transaction/src/main.rs @@ -16,7 +16,8 @@ mod cycle_counting_benchmarks; use cycle_counting_benchmarks::ExecutionBenchmark; use cycle_counting_benchmarks::utils::write_bench_results_to_json; -fn main() -> Result<()> { +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { // create a template file for benchmark results let path = Path::new("bin/bench-transaction/bench-tx.json"); let mut file = File::create(path).context("failed to create file")?; @@ -27,21 +28,24 @@ fn main() -> Result<()> { ( ExecutionBenchmark::ConsumeSingleP2ID, tx_consume_single_p2id_note()? - .execute_blocking() + .execute() + .await .map(TransactionMeasurements::from)? .into(), ), ( ExecutionBenchmark::ConsumeTwoP2ID, tx_consume_two_p2id_notes()? - .execute_blocking() + .execute() + .await .map(TransactionMeasurements::from)? .into(), ), ( ExecutionBenchmark::CreateSingleP2ID, tx_create_single_p2id_note()? - .execute_blocking() + .execute() + .await .map(TransactionMeasurements::from)? .into(), ), diff --git a/bin/bench-transaction/src/time_counting_benchmarks/prove.rs b/bin/bench-transaction/src/time_counting_benchmarks/prove.rs index 31cad97365..b15c0fc70d 100644 --- a/bin/bench-transaction/src/time_counting_benchmarks/prove.rs +++ b/bin/bench-transaction/src/time_counting_benchmarks/prove.rs @@ -36,33 +36,35 @@ fn core_benchmarks(c: &mut Criterion) { .warm_up_time(Duration::from_millis(1000)); execute_group.bench_function(BENCH_EXECUTE_TX_CONSUME_SINGLE_P2ID, |b| { - b.iter_batched( - || { - // prepare the transaction context - tx_consume_single_p2id_note() - .expect("failed to create a context which consumes single P2ID note") - }, - |tx_context| { - // benchmark the transaction execution - black_box(tx_context.execute_blocking()) - }, - BatchSize::SmallInput, - ); + b.to_async(tokio::runtime::Builder::new_current_thread().build().unwrap()) + .iter_batched( + || { + // prepare the transaction context + tx_consume_single_p2id_note() + .expect("failed to create a context which consumes single P2ID note") + }, + |tx_context| async move { + // benchmark the transaction execution + black_box(tx_context.execute().await) + }, + BatchSize::SmallInput, + ); }); execute_group.bench_function(BENCH_EXECUTE_TX_CONSUME_TWO_P2ID, |b| { - b.iter_batched( - || { - // prepare the transaction context - tx_consume_two_p2id_notes() - .expect("failed to create a context which consumes two P2ID notes") - }, - |tx_context| { - // benchmark the transaction execution - black_box(tx_context.execute_blocking()) - }, - BatchSize::SmallInput, - ); + b.to_async(tokio::runtime::Builder::new_current_thread().build().unwrap()) + .iter_batched( + || { + // prepare the transaction context + tx_consume_two_p2id_notes() + .expect("failed to create a context which consumes two P2ID notes") + }, + |tx_context| async move { + // benchmark the transaction execution + black_box(tx_context.execute().await) + }, + BatchSize::SmallInput, + ); }); execute_group.finish(); @@ -78,41 +80,45 @@ fn core_benchmarks(c: &mut Criterion) { .warm_up_time(Duration::from_millis(1000)); execute_and_prove_group.bench_function(BENCH_EXECUTE_AND_PROVE_TX_CONSUME_SINGLE_P2ID, |b| { - b.iter_batched( - || { - // prepare the transaction context - tx_consume_single_p2id_note() - .expect("failed to create a context which consumes single P2ID note") - }, - |tx_context| { - // benchmark the transaction execution and proving - black_box(prove_transaction( - tx_context - .execute_blocking() - .expect("execution of the single P2ID note consumption tx failed"), - )) - }, - BatchSize::SmallInput, - ); + b.to_async(tokio::runtime::Builder::new_current_thread().build().unwrap()) + .iter_batched( + || { + // prepare the transaction context + tx_consume_single_p2id_note() + .expect("failed to create a context which consumes single P2ID note") + }, + |tx_context| async move { + // benchmark the transaction execution and proving + black_box(prove_transaction( + tx_context + .execute() + .await + .expect("execution of the single P2ID note consumption tx failed"), + )) + }, + BatchSize::SmallInput, + ); }); execute_and_prove_group.bench_function(BENCH_EXECUTE_AND_PROVE_TX_CONSUME_TWO_P2ID, |b| { - b.iter_batched( - || { - // prepare the transaction context - tx_consume_two_p2id_notes() - .expect("failed to create a context which consumes two P2ID notes") - }, - |tx_context| { - // benchmark the transaction execution and proving - black_box(prove_transaction( - tx_context - .execute_blocking() - .expect("execution of the two P2ID note consumption tx failed"), - )) - }, - BatchSize::SmallInput, - ); + b.to_async(tokio::runtime::Builder::new_current_thread().build().unwrap()) + .iter_batched( + || { + // prepare the transaction context + tx_consume_two_p2id_notes() + .expect("failed to create a context which consumes two P2ID notes") + }, + |tx_context| async move { + // benchmark the transaction execution and proving + black_box(prove_transaction( + tx_context + .execute() + .await + .expect("execution of the two P2ID note consumption tx failed"), + )) + }, + BatchSize::SmallInput, + ); }); execute_and_prove_group.finish(); diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index 647776a5cc..01e4fe2fc2 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -32,13 +32,12 @@ itertools = { default-features = false, features = ["use_alloc"], version = "0 rand = { features = ["os_rng", "small_rng"], workspace = true } rand_chacha = { default-features = false, version = "0.9" } thiserror = { workspace = true } -# TODO: We should be able to remove this once we remove `TransactionContext::execute_blocking` -tokio = { features = ["macros", "rt"], workspace = true } -winterfell = { version = "0.13" } +winterfell = { version = "0.13" } [dev-dependencies] anyhow = { features = ["backtrace", "std"], workspace = true } assert_matches = { workspace = true } miden-objects = { features = ["std"], workspace = true } rstest = { workspace = true } +tokio = { features = ["macros", "rt"], workspace = true } winter-rand-utils = { version = "0.13" } diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs index 0c803d31a8..1aca71fe7c 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs @@ -15,8 +15,8 @@ use crate::kernel_tests::block::utils::MockChainBlockExt; use crate::{Auth, MockChain}; /// Tests that too many batches produce an error. -#[test] -fn proposed_block_fails_on_too_many_batches() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_too_many_batches() -> anyhow::Result<()> { let count = MAX_BATCHES_PER_BLOCK + 1; let (chain, batches) = { @@ -39,8 +39,9 @@ fn proposed_block_fails_on_too_many_batches() -> anyhow::Result<()> { let mut batches = Vec::with_capacity(count); for i in 0..count { - let proven_tx = - chain.create_authenticated_notes_proven_tx(accounts[i].id(), [notes[i].id()])?; + let proven_tx = chain + .create_authenticated_notes_proven_tx(accounts[i].id(), [notes[i].id()]) + .await?; batches.push(chain.create_batch(vec![proven_tx])?); } @@ -63,16 +64,17 @@ fn proposed_block_fails_on_too_many_batches() -> anyhow::Result<()> { } /// Tests that duplicate batches produce an error. -#[test] -fn proposed_block_fails_on_duplicate_batches() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_duplicate_batches() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let sender_account = builder.add_existing_mock_account(Auth::IncrNonce)?; let note = builder.add_p2any_note(sender_account.id(), NoteType::Public, [FungibleAsset::mock(42)])?; let chain = builder.build()?; - let proven_tx0 = - chain.create_authenticated_notes_proven_tx(sender_account.id(), [note.id()])?; + let proven_tx0 = chain + .create_authenticated_notes_proven_tx(sender_account.id(), [note.id()]) + .await?; let batch0 = chain.create_batch(vec![proven_tx0])?; let batches = vec![batch0.clone(), batch0.clone()]; @@ -93,8 +95,8 @@ fn proposed_block_fails_on_duplicate_batches() -> anyhow::Result<()> { } /// Tests that an expired batch produces an error. -#[test] -fn proposed_block_fails_on_expired_batches() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_expired_batches() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; @@ -103,8 +105,8 @@ fn proposed_block_fails_on_expired_batches() -> anyhow::Result<()> { chain.prove_next_block()?; let block1_num = chain.block_header(1).block_num(); - let tx0 = chain.create_expiring_proven_tx(account0.id(), block1_num + 5)?; - let tx1 = chain.create_expiring_proven_tx(account1.id(), block1_num + 1)?; + let tx0 = chain.create_expiring_proven_tx(account0.id(), block1_num + 5).await?; + let tx1 = chain.create_expiring_proven_tx(account1.id(), block1_num + 1).await?; let batch0 = chain.create_batch(vec![tx0])?; let batch1 = chain.create_batch(vec![tx1])?; @@ -133,12 +135,12 @@ fn proposed_block_fails_on_expired_batches() -> anyhow::Result<()> { } /// Tests that a timestamp at or before the previous block header produces an error. -#[test] -fn proposed_block_fails_on_timestamp_not_increasing_monotonically() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_timestamp_not_increasing_monotonically() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::IncrNonce)?; let chain = builder.build()?; - let proven_tx0 = chain.create_authenticated_notes_proven_tx(account, [])?; + let proven_tx0 = chain.create_authenticated_notes_proven_tx(account, []).await?; let batch0 = chain.create_batch(vec![proven_tx0])?; let batches = vec![batch0]; @@ -166,12 +168,13 @@ fn proposed_block_fails_on_timestamp_not_increasing_monotonically() -> anyhow::R /// Tests that a partial blockchain that is not at the state of the previous block header produces /// an error. -#[test] -fn proposed_block_fails_on_partial_blockchain_and_prev_block_inconsistency() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_partial_blockchain_and_prev_block_inconsistency() +-> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::IncrNonce)?; let chain = builder.build()?; - let proven_tx0 = chain.create_authenticated_notes_proven_tx(account, [])?; + let proven_tx0 = chain.create_authenticated_notes_proven_tx(account, []).await?; let batch0 = chain.create_batch(vec![proven_tx0])?; let batches = vec![batch0]; @@ -222,14 +225,14 @@ fn proposed_block_fails_on_partial_blockchain_and_prev_block_inconsistency() -> /// Tests that a partial blockchain that does not contain all reference blocks of the batches /// produces an error. -#[test] -fn proposed_block_fails_on_missing_batch_reference_block() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_missing_batch_reference_block() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::IncrNonce)?; let mut chain = builder.build()?; chain.prove_next_block()?; - let proven_tx0 = chain.create_authenticated_notes_proven_tx(account, [])?; + let proven_tx0 = chain.create_authenticated_notes_proven_tx(account, []).await?; // This batch will reference the latest block with number 1. let batch0 = chain.create_batch(vec![proven_tx0.clone()])?; @@ -264,8 +267,8 @@ fn proposed_block_fails_on_missing_batch_reference_block() -> anyhow::Result<()> } /// Tests that duplicate input notes across batches produce an error. -#[test] -fn proposed_block_fails_on_duplicate_input_note() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_duplicate_input_note() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; @@ -280,9 +283,10 @@ fn proposed_block_fails_on_duplicate_input_note() -> anyhow::Result<()> { chain.prove_next_block()?; // Create two different transactions against the same account consuming the same note. - let tx0 = - chain.create_authenticated_notes_proven_tx(account1.id(), [note0.id(), note1.id()])?; - let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note0.id()])?; + let tx0 = chain + .create_authenticated_notes_proven_tx(account1.id(), [note0.id(), note1.id()]) + .await?; + let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note0.id()]).await?; let batch0 = chain.create_batch(vec![tx0])?; let batch1 = chain.create_batch(vec![tx1])?; @@ -298,8 +302,8 @@ fn proposed_block_fails_on_duplicate_input_note() -> anyhow::Result<()> { } /// Tests that duplicate output notes across batches produce an error. -#[test] -fn proposed_block_fails_on_duplicate_output_note() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_duplicate_output_note() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::IncrNonce)?; let output_note = builder.create_p2any_note(account.id(), NoteType::Private, [])?; @@ -316,8 +320,8 @@ fn proposed_block_fails_on_duplicate_output_note() -> anyhow::Result<()> { // We use the same account because the sender of the created output note is set to the account // of the transaction, so it is essential we use the same account to produce a duplicate output // note. - let tx0 = chain.create_authenticated_notes_proven_tx(account.id(), [note0.id()])?; - let tx1 = chain.create_authenticated_notes_proven_tx(account.id(), [note1.id()])?; + let tx0 = chain.create_authenticated_notes_proven_tx(account.id(), [note0.id()]).await?; + let tx1 = chain.create_authenticated_notes_proven_tx(account.id(), [note1.id()]).await?; let batch0 = chain.create_batch(vec![tx0])?; let batch1 = chain.create_batch(vec![tx1])?; @@ -335,8 +339,8 @@ fn proposed_block_fails_on_duplicate_output_note() -> anyhow::Result<()> { /// Tests that a missing note inclusion proof produces an error. /// Also tests that an error is produced if the block that the note inclusion proof references is /// not in the partial blockchain. -#[test] -fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_block() +#[tokio::test] +async fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_block() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; @@ -347,8 +351,9 @@ fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_blo let mut chain = builder.build()?; // This tx will use block1 as the reference block. - let tx0 = - chain.create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(&p2id_note))?; + let tx0 = chain + .create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(&p2id_note)) + .await?; // This batch will use block1 as the reference block. // With this setup, the block inputs need to contain a reference to block2 in order to prove @@ -360,7 +365,8 @@ fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_blo let tx = chain .build_tx_context(account0.id(), &[spawn_note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; chain.add_pending_executed_transaction(&tx)?; let block2 = chain.prove_next_block()?; @@ -425,8 +431,8 @@ fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_blo } /// Tests that a missing note inclusion proof produces an error. -#[test] -fn proposed_block_fails_on_missing_note_inclusion_proof() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_missing_note_inclusion_proof() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; @@ -434,8 +440,9 @@ fn proposed_block_fails_on_missing_note_inclusion_proof() -> anyhow::Result<()> let note0 = builder.create_p2any_note(account0.id(), NoteType::Private, [])?; let chain = builder.build()?; - let tx0 = - chain.create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(¬e0))?; + let tx0 = chain + .create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(¬e0)) + .await?; let batch0 = chain.create_batch(vec![tx0])?; @@ -452,8 +459,8 @@ fn proposed_block_fails_on_missing_note_inclusion_proof() -> anyhow::Result<()> } /// Tests that a missing nullifier witness produces an error. -#[test] -fn proposed_block_fails_on_missing_nullifier_witness() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_missing_nullifier_witness() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::IncrNonce)?; let p2id_note = @@ -462,8 +469,9 @@ fn proposed_block_fails_on_missing_nullifier_witness() -> anyhow::Result<()> { chain.prove_next_block()?; // This tx will use block1 as the reference block. - let tx0 = - chain.create_unauthenticated_notes_proven_tx(account.id(), slice::from_ref(&p2id_note))?; + let tx0 = chain + .create_unauthenticated_notes_proven_tx(account.id(), slice::from_ref(&p2id_note)) + .await?; // This batch will use block1 as the reference block. let batch0 = chain.create_batch(vec![tx0])?; @@ -490,8 +498,8 @@ fn proposed_block_fails_on_missing_nullifier_witness() -> anyhow::Result<()> { } /// Tests that a nullifier witness pointing to a spent nullifier produces an error. -#[test] -fn proposed_block_fails_on_spent_nullifier_witness() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_spent_nullifier_witness() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; @@ -501,12 +509,16 @@ fn proposed_block_fails_on_spent_nullifier_witness() -> anyhow::Result<()> { chain.prove_next_block()?; // Consume the note with account 0 and add the transaction to a block. - let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [p2any_note.id()])?; + let tx0 = chain + .create_authenticated_notes_proven_tx(account0.id(), [p2any_note.id()]) + .await?; chain.add_pending_proven_transaction(tx0); chain.prove_next_block()?; // Consume the (already consumed) note with account 1 and build a batch from it. - let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [p2any_note.id()])?; + let tx1 = chain + .create_authenticated_notes_proven_tx(account1.id(), [p2any_note.id()]) + .await?; let batch1 = chain.create_batch(vec![tx1])?; let batches = vec![batch1]; let block_inputs = chain.get_block_inputs(&batches)?; @@ -524,8 +536,9 @@ fn proposed_block_fails_on_spent_nullifier_witness() -> anyhow::Result<()> { /// Tests that multiple transactions against the same account that start from the same initial state /// commitment but produce different final state commitments produce an error. -#[test] -fn proposed_block_fails_on_conflicting_transactions_updating_same_account() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_conflicting_transactions_updating_same_account() +-> anyhow::Result<()> { let mut builder = MockChain::builder(); let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; let note0 = @@ -539,8 +552,8 @@ fn proposed_block_fails_on_conflicting_transactions_updating_same_account() -> a // Create two different transactions against the same account consuming a different note so they // result in a different final state commitment for the account. - let tx0 = chain.create_authenticated_notes_proven_tx(account1.id(), [note0.id()])?; - let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()])?; + let tx0 = chain.create_authenticated_notes_proven_tx(account1.id(), [note0.id()]).await?; + let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()]).await?; let batch0 = chain.create_batch(vec![tx0])?; let batch1 = chain.create_batch(vec![tx1])?; @@ -564,12 +577,12 @@ fn proposed_block_fails_on_conflicting_transactions_updating_same_account() -> a } /// Tests that a missing account witness produces an error. -#[test] -fn proposed_block_fails_on_missing_account_witness() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_missing_account_witness() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::IncrNonce)?; let chain = builder.build()?; - let tx0 = chain.create_authenticated_notes_proven_tx(account.id(), [])?; + let tx0 = chain.create_authenticated_notes_proven_tx(account.id(), []).await?; let batch0 = chain.create_batch(vec![tx0])?; @@ -591,8 +604,8 @@ fn proposed_block_fails_on_missing_account_witness() -> anyhow::Result<()> { /// Tests that, given three transactions 0 -> 1 -> 2 which are executed against the same account and /// build on top of each other produce an error when tx 1 is missing from the block. -#[test] -fn proposed_block_fails_on_inconsistent_account_state_transition() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_fails_on_inconsistent_account_state_transition() -> anyhow::Result<()> { let asset = FungibleAsset::mock(200); let mut builder = MockChain::builder(); @@ -603,15 +616,15 @@ fn proposed_block_fails_on_inconsistent_account_state_transition() -> anyhow::Re let chain = builder.build()?; // Create three transactions on the same account that build on top of each other. - let executed_tx0 = chain.create_authenticated_notes_tx(account.clone(), [note0.id()])?; + let executed_tx0 = chain.create_authenticated_notes_tx(account.clone(), [note0.id()]).await?; account.apply_delta(executed_tx0.account_delta())?; // Builds a tx on top of the account state from tx0. - let executed_tx1 = chain.create_authenticated_notes_tx(account.clone(), [note1.id()])?; + let executed_tx1 = chain.create_authenticated_notes_tx(account.clone(), [note1.id()]).await?; account.apply_delta(executed_tx1.account_delta())?; // Builds a tx on top of the account state from tx1. - let executed_tx2 = chain.create_authenticated_notes_tx(account.clone(), [note2.id()])?; + let executed_tx2 = chain.create_authenticated_notes_tx(account.clone(), [note2.id()]).await?; // We will only include tx0 and tx2 and leave out tx1, which will trigger the error condition // that there is no transition from tx0 -> tx2. diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs index 6f7b2f70ac..e9ab238fe9 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs @@ -21,8 +21,8 @@ use super::utils::MockChainBlockExt; use crate::{AccountState, Auth, MockChain, TxContextInput}; /// Tests that we can build empty blocks. -#[test] -fn proposed_block_succeeds_with_empty_batches() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_succeeds_with_empty_batches() -> anyhow::Result<()> { let mut chain = MockChain::builder().build()?; chain.prove_next_block()?; @@ -45,8 +45,8 @@ fn proposed_block_succeeds_with_empty_batches() -> anyhow::Result<()> { /// Tests that a proposed block from two batches with one transaction each can be successfully /// built. -#[test] -fn proposed_block_basic_success() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_basic_success() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; @@ -56,8 +56,10 @@ fn proposed_block_basic_success() -> anyhow::Result<()> { builder.add_p2any_note(account1.id(), NoteType::Public, [FungibleAsset::mock(42)])?; let chain = builder.build()?; - let proven_tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()])?; - let proven_tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()])?; + let proven_tx0 = + chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()]).await?; + let proven_tx1 = + chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()]).await?; let batch0 = chain.create_batch(vec![proven_tx0.clone()])?; let batch1 = chain.create_batch(vec![proven_tx1.clone()])?; @@ -110,8 +112,8 @@ fn proposed_block_basic_success() -> anyhow::Result<()> { } /// Tests that account updates are correctly aggregated into a block-level account update. -#[test] -fn proposed_block_aggregates_account_state_transition() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_aggregates_account_state_transition() -> anyhow::Result<()> { let asset = FungibleAsset::mock(100); let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER)?; @@ -126,13 +128,13 @@ fn proposed_block_aggregates_account_state_transition() -> anyhow::Result<()> { chain.prove_next_block()?; // Create three transactions on the same account that build on top of each other. - let executed_tx0 = chain.create_authenticated_notes_tx(account1.id(), [note0.id()])?; + let executed_tx0 = chain.create_authenticated_notes_tx(account1.id(), [note0.id()]).await?; account1.apply_delta(executed_tx0.account_delta())?; - let executed_tx1 = chain.create_authenticated_notes_tx(account1.clone(), [note1.id()])?; + let executed_tx1 = chain.create_authenticated_notes_tx(account1.clone(), [note1.id()]).await?; account1.apply_delta(executed_tx1.account_delta())?; - let executed_tx2 = chain.create_authenticated_notes_tx(account1.clone(), [note2.id()])?; + let executed_tx2 = chain.create_authenticated_notes_tx(account1.clone(), [note2.id()]).await?; let [tx0, tx1, tx2] = [executed_tx0, executed_tx1, executed_tx2] .into_iter() @@ -176,8 +178,8 @@ fn proposed_block_aggregates_account_state_transition() -> anyhow::Result<()> { } /// Tests that unauthenticated notes can be authenticated when inclusion proofs are provided. -#[test] -fn proposed_block_authenticating_unauthenticated_notes() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_authenticating_unauthenticated_notes() -> anyhow::Result<()> { let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER)?; let mut builder = MockChain::builder(); @@ -188,10 +190,12 @@ fn proposed_block_authenticating_unauthenticated_notes() -> anyhow::Result<()> { let chain = builder.build()?; // These txs will use block1 as the reference block. - let tx0 = - chain.create_unauthenticated_notes_proven_tx(account0.id(), slice::from_ref(¬e0))?; - let tx1 = - chain.create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(¬e1))?; + let tx0 = chain + .create_unauthenticated_notes_proven_tx(account0.id(), slice::from_ref(¬e0)) + .await?; + let tx1 = chain + .create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(¬e1)) + .await?; // These batches will use block1 as the reference block. let batch0 = chain.create_batch(vec![tx0.clone()])?; @@ -224,8 +228,8 @@ fn proposed_block_authenticating_unauthenticated_notes() -> anyhow::Result<()> { } /// Tests that a batch that expires at the block being proposed is still accepted. -#[test] -fn proposed_block_with_batch_at_expiration_limit() -> anyhow::Result<()> { +#[tokio::test] +async fn proposed_block_with_batch_at_expiration_limit() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; @@ -234,8 +238,8 @@ fn proposed_block_with_batch_at_expiration_limit() -> anyhow::Result<()> { chain.prove_next_block()?; let block1_num = chain.block_header(1).block_num(); - let tx0 = chain.create_expiring_proven_tx(account0.id(), block1_num + 5)?; - let tx1 = chain.create_expiring_proven_tx(account1.id(), block1_num + 2)?; + let tx0 = chain.create_expiring_proven_tx(account0.id(), block1_num + 5).await?; + let tx1 = chain.create_expiring_proven_tx(account1.id(), block1_num + 2).await?; let batch0 = chain.create_batch(vec![tx0])?; let batch1 = chain.create_batch(vec![tx1])?; @@ -258,8 +262,8 @@ fn proposed_block_with_batch_at_expiration_limit() -> anyhow::Result<()> { /// Tests that a NOOP transaction with state commitments X -> X against account A can appear /// in one batch while another batch contains a state-updating transaction with state commitments X /// -> Y against the same account A. Both batches are in the same block. -#[test] -fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow::Result<()> { +#[tokio::test] +async fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow::Result<()> { let account_builder = Account::builder(rand::rng().random()) .storage_mode(AccountStorageMode::Public) .with_component(MockAccountComponent::with_empty_slots()); @@ -279,9 +283,10 @@ fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow: builder.add_output_note(OutputNote::Full(noop_note1.clone())); let mut chain = builder.build()?; - let noop_tx = generate_conditional_tx(&mut chain, account0.id(), noop_note0, false); + let noop_tx = generate_conditional_tx(&mut chain, account0.id(), noop_note0, false).await; account0.apply_delta(noop_tx.account_delta())?; - let state_updating_tx = generate_conditional_tx(&mut chain, account0.clone(), noop_note1, true); + let state_updating_tx = + generate_conditional_tx(&mut chain, account0.clone(), noop_note1, true).await; // sanity check: NOOP transaction's init and final commitment should be the same. assert_eq!(noop_tx.initial_account().commitment(), noop_tx.final_account().commitment()); @@ -318,7 +323,7 @@ fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> anyhow: /// - if `modify_storage` is false, it does nothing (NOOP). /// /// To make this transaction (always) non-empty, it consumes one "noop note", which does nothing. -fn generate_conditional_tx( +async fn generate_conditional_tx( chain: &mut MockChain, input: impl Into, noop_note: Note, @@ -338,5 +343,5 @@ fn generate_conditional_tx( .auth_args(auth_args.into()) .build() .unwrap(); - tx_context.execute_blocking().unwrap() + tx_context.execute().await.unwrap() } diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs index 89833ae77b..f50937fdbb 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_error.rs @@ -28,7 +28,7 @@ struct WitnessTestSetup { /// Setup for a test which returns two inputs for the same block. The valid inputs match the /// commitments of the latest block and the stale inputs match the commitments of the latest block /// minus 1. -fn witness_test_setup() -> anyhow::Result { +async fn witness_test_setup() -> anyhow::Result { let mut builder = MockChain::builder(); let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; @@ -44,9 +44,9 @@ fn witness_test_setup() -> anyhow::Result { let mut chain = builder.build()?; - let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()])?; - let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()])?; - let tx2 = chain.create_authenticated_notes_proven_tx(account2.id(), [note2.id()])?; + let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()]).await?; + let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()]).await?; + let tx2 = chain.create_authenticated_notes_proven_tx(account2.id(), [note2.id()]).await?; let batch1 = chain.create_batch(vec![tx1, tx2])?; let batches = vec![batch1]; @@ -75,8 +75,8 @@ fn witness_test_setup() -> anyhow::Result { /// Tests that a proven block cannot be built if witnesses from a stale account tree are used /// (i.e. an account tree whose root is not in the previous block header). -#[test] -fn proven_block_fails_on_stale_account_witnesses() -> anyhow::Result<()> { +#[tokio::test] +async fn proven_block_fails_on_stale_account_witnesses() -> anyhow::Result<()> { // Setup test with stale and valid block inputs. // -------------------------------------------------------------------------------------------- @@ -84,7 +84,7 @@ fn proven_block_fails_on_stale_account_witnesses() -> anyhow::Result<()> { stale_block_inputs, valid_block_inputs, batches, - } = witness_test_setup()?; + } = witness_test_setup().await?; // Account tree root mismatch. // -------------------------------------------------------------------------------------------- @@ -112,8 +112,8 @@ fn proven_block_fails_on_stale_account_witnesses() -> anyhow::Result<()> { /// Tests that a proven block cannot be built if witnesses from a stale nullifier tree are used /// (i.e. a nullifier tree whose root is not in the previous block header). -#[test] -fn proven_block_fails_on_stale_nullifier_witnesses() -> anyhow::Result<()> { +#[tokio::test] +async fn proven_block_fails_on_stale_nullifier_witnesses() -> anyhow::Result<()> { // Setup test with stale and valid block inputs. // -------------------------------------------------------------------------------------------- @@ -121,7 +121,7 @@ fn proven_block_fails_on_stale_nullifier_witnesses() -> anyhow::Result<()> { stale_block_inputs, valid_block_inputs, batches, - } = witness_test_setup()?; + } = witness_test_setup().await?; // Nullifier tree root mismatch. // -------------------------------------------------------------------------------------------- @@ -149,8 +149,8 @@ fn proven_block_fails_on_stale_nullifier_witnesses() -> anyhow::Result<()> { /// Tests that a proven block cannot be built if both witnesses from a stale account tree and from /// the current account tree are used which results in different account tree roots. -#[test] -fn proven_block_fails_on_account_tree_root_mismatch() -> anyhow::Result<()> { +#[tokio::test] +async fn proven_block_fails_on_account_tree_root_mismatch() -> anyhow::Result<()> { // Setup test with stale and valid block inputs. // -------------------------------------------------------------------------------------------- @@ -158,7 +158,7 @@ fn proven_block_fails_on_account_tree_root_mismatch() -> anyhow::Result<()> { mut stale_block_inputs, valid_block_inputs, batches, - } = witness_test_setup()?; + } = witness_test_setup().await?; // Stale and current account witnesses used together. // -------------------------------------------------------------------------------------------- @@ -195,8 +195,8 @@ fn proven_block_fails_on_account_tree_root_mismatch() -> anyhow::Result<()> { /// Tests that a proven block cannot be built if both witnesses from a stale nullifier tree and from /// the current nullifier tree are used which results in different nullifier tree roots. -#[test] -fn proven_block_fails_on_nullifier_tree_root_mismatch() -> anyhow::Result<()> { +#[tokio::test] +async fn proven_block_fails_on_nullifier_tree_root_mismatch() -> anyhow::Result<()> { // Setup test with stale and valid block inputs. // -------------------------------------------------------------------------------------------- @@ -204,7 +204,7 @@ fn proven_block_fails_on_nullifier_tree_root_mismatch() -> anyhow::Result<()> { mut stale_block_inputs, valid_block_inputs, batches, - } = witness_test_setup()?; + } = witness_test_setup().await?; // Stale and current nullifier witnesses used together. // -------------------------------------------------------------------------------------------- @@ -240,8 +240,9 @@ fn proven_block_fails_on_nullifier_tree_root_mismatch() -> anyhow::Result<()> { /// Tests that creating an account when an existing account with the same account ID prefix exists, /// results in an error. -#[test] -fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> anyhow::Result<()> { +#[tokio::test] +async fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() +-> anyhow::Result<()> { // Construct a new account. // -------------------------------------------------------------------------------------------- @@ -290,7 +291,7 @@ fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> a let tx_inputs = mock_chain.get_transaction_inputs(&account, &[], &[])?; let tx_context = TransactionContextBuilder::new(account).tx_inputs(tx_inputs).build()?; - let tx = tx_context.execute_blocking().context("failed to execute account creating tx")?; + let tx = tx_context.execute().await.context("failed to execute account creating tx")?; let tx = LocalTransactionProver::default().prove_dummy(tx)?; let batch = mock_chain.create_batch(vec![tx])?; @@ -334,8 +335,9 @@ fn proven_block_fails_on_creating_account_with_existing_account_id_prefix() -> a } /// Tests that creating two accounts in the same block whose ID prefixes match, results in an error. -#[test] -fn proven_block_fails_on_creating_account_with_duplicate_account_id_prefix() -> anyhow::Result<()> { +#[tokio::test] +async fn proven_block_fails_on_creating_account_with_duplicate_account_id_prefix() +-> anyhow::Result<()> { // Construct a new account. // -------------------------------------------------------------------------------------------- let mock_chain = MockChain::new(); diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs index f29db75c62..896039c285 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs @@ -23,8 +23,8 @@ use crate::{Auth, MockChain}; /// Tests the outputs of a proven block with transactions that consume notes, create output notes /// and modify the account's state. -#[test] -fn proven_block_success() -> anyhow::Result<()> { +#[tokio::test] +async fn proven_block_success() -> anyhow::Result<()> { // Setup test with notes that produce output notes, in order to test the block note tree root // computation. // -------------------------------------------------------------------------------------------- @@ -54,10 +54,18 @@ fn proven_block_success() -> anyhow::Result<()> { let mut chain = builder.build()?; chain.prove_next_block()?; - let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [input_note0.id()])?; - let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [input_note1.id()])?; - let tx2 = chain.create_authenticated_notes_proven_tx(account2.id(), [input_note2.id()])?; - let tx3 = chain.create_authenticated_notes_proven_tx(account3.id(), [input_note3.id()])?; + let tx0 = chain + .create_authenticated_notes_proven_tx(account0.id(), [input_note0.id()]) + .await?; + let tx1 = chain + .create_authenticated_notes_proven_tx(account1.id(), [input_note1.id()]) + .await?; + let tx2 = chain + .create_authenticated_notes_proven_tx(account2.id(), [input_note2.id()]) + .await?; + let tx3 = chain + .create_authenticated_notes_proven_tx(account3.id(), [input_note3.id()]) + .await?; let batch0 = chain.create_batch(vec![tx0.clone(), tx1.clone()])?; let batch1 = chain.create_batch(vec![tx2.clone(), tx3.clone()])?; @@ -196,8 +204,8 @@ fn proven_block_success() -> anyhow::Result<()> { /// /// We also test that the batch note tree containing the output generating transactions is a subtree /// of the subtree of the overall block note tree computed from the block's output notes. -#[test] -fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { +#[tokio::test] +async fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; @@ -221,11 +229,12 @@ fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { let note3 = builder.add_spawn_note([&output_note3])?; let chain = builder.build()?; - let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()])?; + let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()]).await?; let tx1 = chain - .create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(&output_note0))?; - let tx2 = chain.create_authenticated_notes_proven_tx(account2.id(), [note2.id()])?; - let tx3 = chain.create_authenticated_notes_proven_tx(account3.id(), [note3.id()])?; + .create_unauthenticated_notes_proven_tx(account1.id(), slice::from_ref(&output_note0)) + .await?; + let tx2 = chain.create_authenticated_notes_proven_tx(account2.id(), [note2.id()]).await?; + let tx3 = chain.create_authenticated_notes_proven_tx(account3.id(), [note3.id()]).await?; assert_eq!(tx0.input_notes().num_notes(), 1); assert_eq!(tx0.output_notes().num_notes(), 1); @@ -333,8 +342,8 @@ fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { } /// Tests that we can build empty blocks. -#[test] -fn proven_block_succeeds_with_empty_batches() -> anyhow::Result<()> { +#[tokio::test] +async fn proven_block_succeeds_with_empty_batches() -> anyhow::Result<()> { // Setup a chain with a non-empty nullifier tree by consuming some notes. // -------------------------------------------------------------------------------------------- @@ -347,8 +356,8 @@ fn proven_block_succeeds_with_empty_batches() -> anyhow::Result<()> { builder.add_p2any_note(account1.id(), NoteType::Public, [FungibleAsset::mock(100)])?; let mut chain = builder.build()?; - let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()])?; - let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()])?; + let tx0 = chain.create_authenticated_notes_proven_tx(account0.id(), [note0.id()]).await?; + let tx1 = chain.create_authenticated_notes_proven_tx(account1.id(), [note1.id()]).await?; chain.add_pending_proven_transaction(tx0); chain.add_pending_proven_transaction(tx1); diff --git a/crates/miden-testing/src/kernel_tests/block/utils.rs b/crates/miden-testing/src/kernel_tests/block/utils.rs index 66e59db91f..20e407616b 100644 --- a/crates/miden-testing/src/kernel_tests/block/utils.rs +++ b/crates/miden-testing/src/kernel_tests/block/utils.rs @@ -15,27 +15,27 @@ use crate::{MockChain, TxContextInput}; /// Provides convenience methods for testing. pub trait MockChainBlockExt { - fn create_authenticated_notes_tx( + async fn create_authenticated_notes_tx( &self, - input: impl Into, - notes: impl IntoIterator, + input: impl Into + Send, + notes: impl IntoIterator + Send, ) -> anyhow::Result; - fn create_authenticated_notes_proven_tx( + async fn create_authenticated_notes_proven_tx( &self, - input: impl Into, - notes: impl IntoIterator, + input: impl Into + Send, + notes: impl IntoIterator + Send, ) -> anyhow::Result; - fn create_unauthenticated_notes_proven_tx( + async fn create_unauthenticated_notes_proven_tx( &self, account_id: AccountId, notes: &[Note], ) -> anyhow::Result; - fn create_expiring_proven_tx( + async fn create_expiring_proven_tx( &self, - input: impl Into, + input: impl Into + Send, expiration_block: BlockNumber, ) -> anyhow::Result; @@ -43,38 +43,38 @@ pub trait MockChainBlockExt { } impl MockChainBlockExt for MockChain { - fn create_authenticated_notes_tx( + async fn create_authenticated_notes_tx( &self, - input: impl Into, - notes: impl IntoIterator, + input: impl Into + Send, + notes: impl IntoIterator + Send, ) -> anyhow::Result { let notes = notes.into_iter().collect::>(); let tx_context = self.build_tx_context(input, ¬es, &[])?.build()?; - tx_context.execute_blocking().map_err(From::from) + tx_context.execute().await.map_err(From::from) } - fn create_authenticated_notes_proven_tx( + async fn create_authenticated_notes_proven_tx( &self, - input: impl Into, - notes: impl IntoIterator, + input: impl Into + Send, + notes: impl IntoIterator + Send, ) -> anyhow::Result { - let executed_tx = self.create_authenticated_notes_tx(input, notes)?; + let executed_tx = self.create_authenticated_notes_tx(input, notes).await?; LocalTransactionProver::default().prove_dummy(executed_tx).map_err(From::from) } - fn create_unauthenticated_notes_proven_tx( + async fn create_unauthenticated_notes_proven_tx( &self, account_id: AccountId, notes: &[Note], ) -> anyhow::Result { let tx_context = self.build_tx_context(account_id, &[], notes)?.build()?; - let executed_tx = tx_context.execute_blocking()?; + let executed_tx = tx_context.execute().await?; LocalTransactionProver::default().prove_dummy(executed_tx).map_err(From::from) } - fn create_expiring_proven_tx( + async fn create_expiring_proven_tx( &self, - input: impl Into, + input: impl Into + Send, expiration_block: BlockNumber, ) -> anyhow::Result { let expiration_delta = expiration_block @@ -85,7 +85,7 @@ impl MockChainBlockExt for MockChain { .build_tx_context(input, &[], &[])? .tx_script(update_expiration_tx_script(expiration_delta.as_u32() as u16)) .build()?; - let executed_tx = tx_context.execute_blocking()?; + let executed_tx = tx_context.execute().await?; LocalTransactionProver::default().prove_dummy(executed_tx).map_err(From::from) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 3fa1cca576..0cbe7bb05b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -60,8 +60,8 @@ use crate::{ // ACCOUNT COMMITMENT TESTS // ================================================================================================ -#[test] -pub fn compute_current_commitment() -> miette::Result<()> { +#[tokio::test] +pub async fn compute_current_commitment() -> miette::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); // Precompute a commitment to a changed account so we can assert it during tx script execution. @@ -140,7 +140,8 @@ pub fn compute_current_commitment() -> miette::Result<()> { .map_err(|err| miette::miette!("{err}"))?; tx_context - .execute_blocking() + .execute() + .await .into_diagnostic() .wrap_err("failed to execute code")?; @@ -595,8 +596,8 @@ fn test_set_map_item() -> miette::Result<()> { Ok(()) } -#[test] -fn test_account_component_storage_offset() -> miette::Result<()> { +#[tokio::test] +async fn test_account_component_storage_offset() -> miette::Result<()> { // setup assembler let assembler = TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())); @@ -735,7 +736,7 @@ fn test_account_component_storage_offset() -> miette::Result<()> { .unwrap(); // execute code in context - let tx = tx_context.execute_blocking().into_diagnostic()?; + let tx = tx_context.execute().await.into_diagnostic()?; account.apply_delta(tx.account_delta()).unwrap(); // assert that elements have been set at the correct locations in storage @@ -747,8 +748,8 @@ fn test_account_component_storage_offset() -> miette::Result<()> { } /// Tests that we can successfully create regular and faucet accounts with empty storage. -#[test] -fn create_account_with_empty_storage_slots() -> anyhow::Result<()> { +#[tokio::test] +async fn create_account_with_empty_storage_slots() -> anyhow::Result<()> { for account_type in [AccountType::FungibleFaucet, AccountType::RegularAccountUpdatableCode] { let account = AccountBuilder::new([5; 32]) .account_type(account_type) @@ -757,13 +758,13 @@ fn create_account_with_empty_storage_slots() -> anyhow::Result<()> { .build() .context("failed to build account")?; - TransactionContextBuilder::new(account).build()?.execute_blocking()?; + TransactionContextBuilder::new(account).build()?.execute().await?; } Ok(()) } -fn create_procedure_metadata_test_account( +async fn create_procedure_metadata_test_account( account_type: AccountType, storage_offset: u8, storage_size: u8, @@ -804,7 +805,7 @@ fn create_procedure_metadata_test_account( let tx_inputs = mock_chain.get_transaction_inputs(&account, &[], &[])?; let tx_context = TransactionContextBuilder::new(account).tx_inputs(tx_inputs).build()?; - let result = tx_context.execute_blocking().map_err(|err| { + let result = tx_context.execute().await.map_err(|err| { let TransactionExecutorError::TransactionProgramExecutionFailed(exec_err) = err else { panic!("should have received an execution error"); }; @@ -816,10 +817,12 @@ fn create_procedure_metadata_test_account( } /// Tests that creating an account whose procedure accesses the reserved faucet storage slot fails. -#[test] -fn creating_faucet_account_with_procedure_accessing_reserved_slot_fails() -> anyhow::Result<()> { +#[tokio::test] +async fn creating_faucet_account_with_procedure_accessing_reserved_slot_fails() -> anyhow::Result<()> +{ // Set offset to 0 for a faucet which should be disallowed. let execution_res = create_procedure_metadata_test_account(AccountType::FungibleFaucet, 0, 1) + .await .context("failed to create test account")?; assert_execution_error!(execution_res, ERR_FAUCET_INVALID_STORAGE_OFFSET); @@ -828,17 +831,20 @@ fn creating_faucet_account_with_procedure_accessing_reserved_slot_fails() -> any } /// Tests that creating a faucet whose procedure offset+size is out of bounds fails. -#[test] -fn creating_faucet_with_procedure_offset_plus_size_out_of_bounds_fails() -> anyhow::Result<()> { +#[tokio::test] +async fn creating_faucet_with_procedure_offset_plus_size_out_of_bounds_fails() -> anyhow::Result<()> +{ // Set offset to lowest allowed value 1 and size to 1 while number of slots is 1 which should // result in an out of bounds error. let execution_res = create_procedure_metadata_test_account(AccountType::FungibleFaucet, 1, 1) + .await .context("failed to create test account")?; assert_execution_error!(execution_res, ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS); // Set offset to 2 while number of slots is 1 which should result in an out of bounds error. let execution_res = create_procedure_metadata_test_account(AccountType::FungibleFaucet, 2, 1) + .await .context("failed to create test account")?; assert_execution_error!(execution_res, ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS); @@ -847,11 +853,13 @@ fn creating_faucet_with_procedure_offset_plus_size_out_of_bounds_fails() -> anyh } /// Tests that creating an account whose procedure offset+size is out of bounds fails. -#[test] -fn creating_account_with_procedure_offset_plus_size_out_of_bounds_fails() -> anyhow::Result<()> { +#[tokio::test] +async fn creating_account_with_procedure_offset_plus_size_out_of_bounds_fails() -> anyhow::Result<()> +{ // Set size to 2 while number of slots is 1 which should result in an out of bounds error. let execution_res = create_procedure_metadata_test_account(AccountType::RegularAccountImmutableCode, 0, 2) + .await .context("failed to create test account")?; assert_execution_error!(execution_res, ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS); @@ -859,6 +867,7 @@ fn creating_account_with_procedure_offset_plus_size_out_of_bounds_fails() -> any // Set offset to 2 while number of slots is 1 which should result in an out of bounds error. let execution_res = create_procedure_metadata_test_account(AccountType::RegularAccountImmutableCode, 2, 1) + .await .context("failed to create test account")?; assert_execution_error!(execution_res, ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS); @@ -973,8 +982,8 @@ fn test_compute_storage_commitment() -> anyhow::Result<()> { /// keys in storage maps, this test ensures that the partial storage map is correctly converted into /// a full storage map. If we end up representing new public accounts as account deltas, this test /// can likely go away. -#[test] -fn proven_tx_storage_map_matches_executed_tx_for_new_account() -> anyhow::Result<()> { +#[tokio::test] +async fn proven_tx_storage_map_matches_executed_tx_for_new_account() -> anyhow::Result<()> { // Build a public account so the proven transaction includes the account update. let mock_slots = AccountStorage::mock_storage_slots(); let mut account = AccountBuilder::new([1; 32]) @@ -1018,7 +1027,8 @@ fn proven_tx_storage_map_matches_executed_tx_for_new_account() -> anyhow::Result .tx_script(tx_script) .with_source_manager(source_manager) .build()? - .execute_blocking()?; + .execute() + .await?; let map_delta = tx.account_delta().storage().maps().get(&map_index).unwrap(); assert_eq!( @@ -1166,8 +1176,8 @@ fn test_authenticate_and_track_procedure() -> miette::Result<()> { // PROCEDURE INTROSPECTION TESTS // ================================================================================================ -#[test] -fn test_was_procedure_called() -> miette::Result<()> { +#[tokio::test] +async fn test_was_procedure_called() -> miette::Result<()> { // Create a standard account using the mock component let mock_component = MockAccountComponent::with_slots(AccountStorage::mock_storage_slots()); let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) @@ -1221,7 +1231,8 @@ fn test_was_procedure_called() -> miette::Result<()> { let tx_context = TransactionContextBuilder::new(account).tx_script(tx_script).build().unwrap(); tx_context - .execute_blocking() + .execute() + .await .into_diagnostic() .wrap_err("Failed to execute transaction")?; @@ -1233,8 +1244,8 @@ fn test_was_procedure_called() -> miette::Result<()> { /// /// The call chain and dependency graph in this test is: /// `tx script -> account code -> external library` -#[test] -fn transaction_executor_account_code_using_custom_library() -> miette::Result<()> { +#[tokio::test] +async fn transaction_executor_account_code_using_custom_library() -> miette::Result<()> { const EXTERNAL_LIBRARY_CODE: &str = r#" use.miden::account @@ -1296,7 +1307,7 @@ fn transaction_executor_account_code_using_custom_library() -> miette::Result<() .build() .unwrap(); - let executed_tx = tx_context.execute_blocking().into_diagnostic()?; + let executed_tx = tx_context.execute().await.into_diagnostic()?; // Account's initial nonce of 1 should have been incremented by 1. assert_eq!(executed_tx.account_delta().nonce_delta(), Felt::new(1)); @@ -1310,8 +1321,8 @@ fn transaction_executor_account_code_using_custom_library() -> miette::Result<() } /// Tests that incrementing the account nonce twice fails. -#[test] -fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { +#[tokio::test] +async fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { let source_code = " use.miden::account @@ -1330,7 +1341,7 @@ fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { .build() .context("failed to build account")?; - let result = TransactionContextBuilder::new(account).build()?.execute_blocking(); + let result = TransactionContextBuilder::new(account).build()?.execute().await; assert_transaction_executor_error!(result, ERR_ACCOUNT_NONCE_CAN_ONLY_BE_INCREMENTED_ONCE); @@ -1458,8 +1469,8 @@ fn test_get_map_item_init() -> miette::Result<()> { } /// Tests that incrementing the account nonce fails if it would overflow the field. -#[test] -fn incrementing_nonce_overflow_fails() -> anyhow::Result<()> { +#[tokio::test] +async fn incrementing_nonce_overflow_fails() -> anyhow::Result<()> { let mut account = AccountBuilder::new([42; 32]) .with_auth_component(Auth::IncrNonce) .with_component(MockAccountComponent::with_empty_slots()) @@ -1469,7 +1480,7 @@ fn incrementing_nonce_overflow_fails() -> anyhow::Result<()> { // modulus - 2. account.increment_nonce(Felt::new(Felt::MODULUS - 2))?; - let result = TransactionContextBuilder::new(account).build()?.execute_blocking(); + let result = TransactionContextBuilder::new(account).build()?.execute().await; assert_transaction_executor_error!(result, ERR_ACCOUNT_NONCE_AT_MAX); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index db058ddf1c..02c857d3e7 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -52,8 +52,8 @@ use crate::{Auth, MockChain, TransactionContextBuilder}; /// /// In order to make the account delta empty but the transaction still legal, we consume a note /// without assets. -#[test] -fn empty_account_delta_commitment_is_empty_word() -> anyhow::Result<()> { +#[tokio::test] +async fn empty_account_delta_commitment_is_empty_word() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::Noop)?; let p2any_note = @@ -64,7 +64,8 @@ fn empty_account_delta_commitment_is_empty_word() -> anyhow::Result<()> { .build_tx_context(account.id(), &[p2any_note.id()], &[]) .expect("failed to build tx context") .build()? - .execute_blocking() + .execute() + .await .context("failed to execute transaction")?; assert_eq!(executed_tx.account_delta().nonce_delta(), ZERO); @@ -75,15 +76,16 @@ fn empty_account_delta_commitment_is_empty_word() -> anyhow::Result<()> { } /// Tests that a noop transaction with [`Auth::IncrNonce`] results in a nonce delta of 1. -#[test] -fn delta_nonce() -> anyhow::Result<()> { +#[tokio::test] +async fn delta_nonce() -> anyhow::Result<()> { let TestSetup { mock_chain, account_id, .. } = setup_test([], [], [])?; let executed_tx = mock_chain .build_tx_context(account_id, &[], &[]) .expect("failed to build tx context") .build()? - .execute_blocking() + .execute() + .await .context("failed to execute transaction")?; assert_eq!(executed_tx.account_delta().nonce_delta(), Felt::new(1)); @@ -97,8 +99,8 @@ fn delta_nonce() -> anyhow::Result<()> { /// - Slot 1: EMPTY_WORD -> [3,4,5,6] -> Delta: [3,4,5,6] /// - Slot 2: [1,3,5,7] -> [1,3,5,7] -> Delta: None /// - Slot 3: [1,3,5,7] -> [2,3,4,5] -> [1,3,5,7] -> Delta: None -#[test] -fn storage_delta_for_value_slots() -> anyhow::Result<()> { +#[tokio::test] +async fn storage_delta_for_value_slots() -> anyhow::Result<()> { let slot_0_init_value = Word::from([2, 4, 6, 8u32]); let slot_0_tmp_value = Word::from([3, 4, 5, 6u32]); let slot_0_final_value = EMPTY_WORD; @@ -171,7 +173,8 @@ fn storage_delta_for_value_slots() -> anyhow::Result<()> { .expect("failed to build tx context") .tx_script(tx_script) .build()? - .execute_blocking() + .execute() + .await .context("failed to execute transaction")?; let storage_values_delta = executed_tx @@ -198,8 +201,8 @@ fn storage_delta_for_value_slots() -> anyhow::Result<()> { /// - Slot 2: key5: [1,2,3,4] -> [2,3,4,5] -> [1,2,3,4] -> Delta: None /// - key5 and key4 are the same scenario, but in different slots. In particular, slot 2's delta /// map will be empty after normalization and so it shouldn't be present in the delta at all. -#[test] -fn storage_delta_for_map_slots() -> anyhow::Result<()> { +#[tokio::test] +async fn storage_delta_for_map_slots() -> anyhow::Result<()> { // Test with random keys to make sure the ordering in the MASM and Rust implementations // matches. let key0 = rand_value::(); @@ -307,7 +310,8 @@ fn storage_delta_for_map_slots() -> anyhow::Result<()> { .build_tx_context(account_id, &[], &[])? .tx_script(tx_script) .build()? - .execute_blocking() + .execute() + .await .context("failed to execute transaction")?; let maps_delta = executed_tx.account_delta().storage().maps(); @@ -337,8 +341,8 @@ fn storage_delta_for_map_slots() -> anyhow::Result<()> { /// - Asset2 is increased by 200 and decreased by 100 -> Delta: 100. /// - Asset3 is decreased by [`FungibleAsset::MAX_AMOUNT`] -> Delta: -MAX_AMOUNT. /// - Asset4 is increased by [`FungibleAsset::MAX_AMOUNT`] -> Delta: MAX_AMOUNT. -#[test] -fn fungible_asset_delta() -> anyhow::Result<()> { +#[tokio::test] +async fn fungible_asset_delta() -> anyhow::Result<()> { // Test with random IDs to make sure the ordering in the MASM and Rust implementations // matches. let faucet0: AccountId = AccountIdBuilder::new() @@ -401,7 +405,8 @@ fn fungible_asset_delta() -> anyhow::Result<()> { .build_tx_context(account_id, ¬es.iter().map(Note::id).collect::>(), &[])? .tx_script(tx_script) .build()? - .execute_blocking() + .execute() + .await .context("failed to execute transaction")?; let mut added_assets = executed_tx @@ -443,8 +448,8 @@ fn fungible_asset_delta() -> anyhow::Result<()> { /// - Asset1 is removed from the vault -> Delta: Remove. /// - Asset2 is added and removed -> Delta: No Change. /// - Asset3 is removed and added -> Delta: No Change. -#[test] -fn non_fungible_asset_delta() -> anyhow::Result<()> { +#[tokio::test] +async fn non_fungible_asset_delta() -> anyhow::Result<()> { let mut rng = rand::rng(); // Test with random IDs to make sure the ordering in the MASM and Rust implementations // matches. @@ -494,7 +499,8 @@ fn non_fungible_asset_delta() -> anyhow::Result<()> { .build_tx_context(account_id, ¬es.iter().map(Note::id).collect::>(), &[])? .tx_script(tx_script) .build()? - .execute_blocking() + .execute() + .await .context("failed to execute transaction")?; let mut added_assets = executed_tx @@ -521,8 +527,8 @@ fn non_fungible_asset_delta() -> anyhow::Result<()> { /// Tests that adding and removing assets and updating value and map storage slots results in the /// correct delta. -#[test] -fn asset_and_storage_delta() -> anyhow::Result<()> { +#[tokio::test] +async fn asset_and_storage_delta() -> anyhow::Result<()> { let account_assets = AssetVault::mock().assets().collect::>(); let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) @@ -687,7 +693,7 @@ fn asset_and_storage_delta() -> anyhow::Result<()> { // expected delta // -------------------------------------------------------------------------------------------- // execute the transaction and get the witness - let executed_transaction = tx_context.execute_blocking()?; + let executed_transaction = tx_context.execute().await?; // nonce delta // -------------------------------------------------------------------------------------------- @@ -748,8 +754,8 @@ fn asset_and_storage_delta() -> anyhow::Result<()> { /// Tests that adding a fungible asset with amount zero to the account vault works and does not /// result in an account delta entry. -#[test] -fn adding_amount_zero_fungible_asset_to_account_vault_works() -> anyhow::Result<()> { +#[tokio::test] +async fn adding_amount_zero_fungible_asset_to_account_vault_works() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::IncrNonce)?; let input_note = builder.add_p2id_note( @@ -763,7 +769,8 @@ fn adding_amount_zero_fungible_asset_to_account_vault_works() -> anyhow::Result< let tx = chain .build_tx_context(account, &[input_note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; assert!(tx.account_delta().vault().is_empty()); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 7fb28abf7b..234c5c819d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -33,8 +33,8 @@ use crate::{ assert_transaction_executor_error, }; -#[test] -fn test_active_note_get_sender_fails_from_tx_script() -> anyhow::Result<()> { +#[tokio::test] +async fn test_active_note_get_sender_fails_from_tx_script() -> anyhow::Result<()> { // Creates a mockchain with an account and a note let mut builder = MockChain::builder(); let account = builder.add_existing_wallet(Auth::BasicAuth)?; @@ -64,7 +64,7 @@ fn test_active_note_get_sender_fails_from_tx_script() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - let result = tx_context.execute_blocking(); + let result = tx_context.execute().await; assert_transaction_executor_error!( result, ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED @@ -73,8 +73,8 @@ fn test_active_note_get_sender_fails_from_tx_script() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_active_note_get_metadata() -> anyhow::Result<()> { +#[tokio::test] +async fn test_active_note_get_metadata() -> anyhow::Result<()> { let tx_context = { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs index 8c31802f10..1be61e13fa 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs @@ -18,8 +18,8 @@ pub const ERR_WRONG_ARGS: MasmError = MasmError::from_static_str(ERR_WRONG_ARGS_ /// This test creates an account with a conditional auth component that expects specific /// auth arguments [97, 98, 99] to not error out. When the correct arguments are provided, /// the nonce is incremented (because of `incr_nonce_flag`). -#[test] -fn test_auth_procedure_args() -> anyhow::Result<()> { +#[tokio::test] +async fn test_auth_procedure_args() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ConditionalAuthComponent); @@ -32,7 +32,7 @@ fn test_auth_procedure_args() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::new(account).auth_args(auth_args.into()).build()?; - tx_context.execute_blocking().context("failed to execute transaction")?; + tx_context.execute().await.context("failed to execute transaction")?; Ok(()) } @@ -42,8 +42,8 @@ fn test_auth_procedure_args() -> anyhow::Result<()> { /// This test creates an account with a conditional auth component that expects specific /// auth arguments [97, 98, 99, incr_nonce_flag]. When incorrect arguments are provided /// (in this case [101, 102, 103]), the transaction should fail with an appropriate error message. -#[test] -fn test_auth_procedure_args_wrong_inputs() -> anyhow::Result<()> { +#[tokio::test] +async fn test_auth_procedure_args_wrong_inputs() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ConditionalAuthComponent); @@ -57,7 +57,7 @@ fn test_auth_procedure_args_wrong_inputs() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::new(account).auth_args(auth_args.into()).build()?; - let execution_result = tx_context.execute_blocking(); + let execution_result = tx_context.execute().await; assert_transaction_executor_error!(execution_result, ERR_WRONG_ARGS); @@ -65,8 +65,8 @@ fn test_auth_procedure_args_wrong_inputs() -> anyhow::Result<()> { } /// Tests that attempting to call the auth procedure manually from user code fails. -#[test] -fn test_auth_procedure_called_from_wrong_context() -> anyhow::Result<()> { +#[tokio::test] +async fn test_auth_procedure_called_from_wrong_context() -> anyhow::Result<()> { let (auth_component, _) = Auth::IncrNonce.build_component(); let account = AccountBuilder::new([42; 32]) @@ -87,7 +87,7 @@ fn test_auth_procedure_called_from_wrong_context() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::new(account).tx_script(tx_script).build()?; - let execution_result = tx_context.execute_blocking(); + let execution_result = tx_context.execute().await; assert_transaction_executor_error!( execution_result, diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 27a073f8af..8d52fd26c3 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -487,8 +487,8 @@ fn test_epilogue_increment_nonce_success() -> anyhow::Result<()> { } /// Tests that changing the account state without incrementing the nonce results in an error. -#[test] -fn epilogue_fails_on_account_state_change_without_nonce_increment() -> anyhow::Result<()> { +#[tokio::test] +async fn epilogue_fails_on_account_state_change_without_nonce_increment() -> anyhow::Result<()> { let code = " use.mock::account @@ -508,7 +508,8 @@ fn epilogue_fails_on_account_state_change_without_nonce_increment() -> anyhow::R let result = TransactionContextBuilder::with_noop_auth_account() .tx_script(tx_script) .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!( result, @@ -518,11 +519,11 @@ fn epilogue_fails_on_account_state_change_without_nonce_increment() -> anyhow::R Ok(()) } -#[test] -fn test_epilogue_execute_empty_transaction() -> anyhow::Result<()> { +#[tokio::test] +async fn test_epilogue_execute_empty_transaction() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_noop_auth_account().build()?; - let result = tx_context.execute_blocking(); + let result = tx_context.execute().await; assert_transaction_executor_error!(result, ERR_EPILOGUE_EXECUTED_TRANSACTION_IS_EMPTY); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index da0f1bc1a2..51cdd6dd54 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -101,8 +101,8 @@ fn test_mint_fungible_asset_succeeds() -> anyhow::Result<()> { } /// Tests that minting a fungible asset on a non-faucet account fails. -#[test] -fn mint_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { +#[tokio::test] +async fn mint_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let account = setup_non_faucet_account()?; let code = format!( @@ -121,7 +121,8 @@ fn mint_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let result = TransactionContextBuilder::new(account) .tx_script(tx_script) .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(result, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); Ok(()) @@ -155,8 +156,8 @@ fn test_mint_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_mint_fungible_asset_fails_saturate_max_amount() -> anyhow::Result<()> { +#[tokio::test] +async fn test_mint_fungible_asset_fails_saturate_max_amount() -> anyhow::Result<()> { let code = format!( " use.mock::faucet @@ -176,7 +177,8 @@ fn test_mint_fungible_asset_fails_saturate_max_amount() -> anyhow::Result<()> { ) .tx_script(tx_script) .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!( result, @@ -271,8 +273,8 @@ fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow::Result } /// Tests that minting a non-fungible asset on a non-faucet account fails. -#[test] -fn mint_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { +#[tokio::test] +async fn mint_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let account = setup_non_faucet_account()?; let code = format!( @@ -291,7 +293,8 @@ fn mint_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let result = TransactionContextBuilder::new(account) .tx_script(tx_script) .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(result, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); Ok(()) @@ -395,8 +398,8 @@ fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { } /// Tests that burning a fungible asset on a non-faucet account fails. -#[test] -fn burn_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { +#[tokio::test] +async fn burn_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let account = setup_non_faucet_account()?; let code = format!( @@ -415,7 +418,8 @@ fn burn_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let result = TransactionContextBuilder::new(account) .tx_script(tx_script) .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(result, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); Ok(()) @@ -586,8 +590,8 @@ fn test_burn_non_fungible_asset_fails_does_not_exist() -> anyhow::Result<()> { } /// Tests that burning a non-fungible asset on a non-faucet account fails. -#[test] -fn burn_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { +#[tokio::test] +async fn burn_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let account = setup_non_faucet_account()?; let code = format!( @@ -606,7 +610,8 @@ fn burn_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> { let result = TransactionContextBuilder::new(account) .tx_script(tx_script) .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(result, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); Ok(()) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fee.rs b/crates/miden-testing/src/kernel_tests/tx/test_fee.rs index e8197fe566..0cc326fe60 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fee.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fee.rs @@ -16,8 +16,8 @@ use crate::{Auth, MockChain}; // ================================================================================================ /// Tests that a simple wallet account can be created with non-zero fees. -#[test] -fn create_account_with_fees() -> anyhow::Result<()> { +#[tokio::test] +async fn create_account_with_fees() -> anyhow::Result<()> { let note_amount = 10_000; let mut builder = MockChain::builder().verification_base_fee(50); @@ -28,7 +28,8 @@ fn create_account_with_fees() -> anyhow::Result<()> { let tx = chain .build_tx_context(account, &[fee_note.id()], &[])? .build()? - .execute_blocking() + .execute() + .await .context("failed to execute account-creating transaction")?; let expected_fee = tx.compute_fee(); @@ -52,8 +53,8 @@ fn create_account_with_fees() -> anyhow::Result<()> { /// Tests that the transaction executor host aborts the transaction if the balance of the native /// asset in the account does not cover the computed fee. -#[test] -fn tx_host_aborts_if_account_balance_does_not_cover_fee() -> anyhow::Result<()> { +#[tokio::test] +async fn tx_host_aborts_if_account_balance_does_not_cover_fee() -> anyhow::Result<()> { let account_amount = 100; let note_amount = 100; let native_asset_id = AccountId::try_from(ACCOUNT_ID_NATIVE_ASSET_FAUCET)?; @@ -69,7 +70,8 @@ fn tx_host_aborts_if_account_balance_does_not_cover_fee() -> anyhow::Result<()> let err = chain .build_tx_context(account, &[fee_note.id()], &[])? .build()? - .execute_blocking() + .execute() + .await .unwrap_err(); assert_matches!( @@ -87,11 +89,11 @@ fn tx_host_aborts_if_account_balance_does_not_cover_fee() -> anyhow::Result<()> /// /// TODO: Once smt::set supports multiple leaves, this case should be tested explicitly here. #[rstest::rstest] -#[case::create_account_no_storage(create_account_no_storage_no_fees()?)] -#[case::mutate_account_with_storage(mutate_account_with_storage()?)] -#[case::create_output_notes(create_output_notes()?)] -#[test] -fn num_tx_cycles_after_compute_fee_are_less_than_estimated( +#[case::create_account_no_storage(create_account_no_storage_no_fees().await?)] +#[case::mutate_account_with_storage(mutate_account_with_storage().await?)] +#[case::create_output_notes(create_output_notes().await?)] +#[tokio::test] +async fn num_tx_cycles_after_compute_fee_are_less_than_estimated( #[case] tx: ExecutedTransaction, ) -> anyhow::Result<()> { // These constants should always be updated together with the equivalent constants in @@ -109,19 +111,20 @@ fn num_tx_cycles_after_compute_fee_are_less_than_estimated( } /// Returns a transaction that creates an account without storage and 0 fees. -fn create_account_no_storage_no_fees() -> anyhow::Result { +async fn create_account_no_storage_no_fees() -> anyhow::Result { let mut builder = MockChain::builder(); let account = builder.create_new_wallet(Auth::IncrNonce)?; builder .build()? .build_tx_context(account, &[], &[])? .build()? - .execute_blocking() + .execute() + .await .map_err(From::from) } /// Returns a transaction that mutates an account with storage and consumes a note. -fn mutate_account_with_storage() -> anyhow::Result { +async fn mutate_account_with_storage() -> anyhow::Result { let native_asset_id = AccountId::try_from(ACCOUNT_ID_NATIVE_ASSET_FAUCET)?; let native_asset = FungibleAsset::new(native_asset_id, 10_000)?; let mut builder = @@ -144,12 +147,13 @@ fn mutate_account_with_storage() -> anyhow::Result { .build()? .build_tx_context(account, &[p2id_note.id()], &[])? .build()? - .execute_blocking() + .execute() + .await .map_err(From::from) } /// Returns a transaction that consumes two notes and creates two notes. -fn create_output_notes() -> anyhow::Result { +async fn create_output_notes() -> anyhow::Result { let native_asset_id = AccountId::try_from(ACCOUNT_ID_NATIVE_ASSET_FAUCET)?; let native_asset = FungibleAsset::new(native_asset_id, 10_000)?; let mut builder = @@ -182,6 +186,7 @@ fn create_output_notes() -> anyhow::Result { OutputNote::Full(output_note1), ]) .build()? - .execute_blocking() + .execute() + .await .map_err(From::from) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index 5d3be684e5..94634d29e3 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -514,8 +514,8 @@ fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { /// It checks the foreign account code loading, providing the mast forest to the executor, /// construction of the account procedure maps and execution the foreign procedure in order to /// obtain the data from the foreign account's storage slot. -#[test] -fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { +#[tokio::test] +async fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { // Prepare the test data let storage_slots = vec![AccountStorage::mock_item_0().slot, AccountStorage::mock_item_2().slot]; @@ -644,15 +644,16 @@ fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { .tx_script(tx_script) .with_source_manager(source_manager) .build()? - .execute_blocking()?; + .execute() + .await?; Ok(()) } /// Test that a foreign account can get the balance of a fungible asset and check the presence of a /// non-fungible asset. -#[test] -fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Result<()> { +#[tokio::test] +async fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Result<()> { let fungible_faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1)?; let non_fungible_faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET)?; @@ -764,7 +765,8 @@ fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Result<()> .tx_script(tx_script) .with_source_manager(source_manager) .build()? - .execute_blocking()?; + .execute() + .await?; Ok(()) } @@ -779,8 +781,8 @@ fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Result<()> /// /// The call chain in this test looks like so: /// `Native -> First FA -> Second FA -> First FA` -#[test] -fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { +#[tokio::test] +async fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { // ------ SECOND FOREIGN ACCOUNT --------------------------------------------------------------- let storage_slots = vec![AccountStorage::mock_item_0().slot]; let second_foreign_account_code_source = r#" @@ -985,7 +987,8 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { .tx_script(tx_script) .with_source_manager(source_manager) .build()? - .execute_blocking()?; + .execute() + .await?; // TODO: Remove later and add a integration test using FPI. LocalTransactionProver::default().prove(executed_transaction.into())?; @@ -997,15 +1000,11 @@ fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { /// /// Attempt to create a 64th foreign account first triggers the assert during the account data /// loading, but we have an additional assert during the account stack push just in case. -#[test] -fn test_nested_fpi_stack_overflow() { - // use a custom thread to increase its stack capacity - std::thread::Builder::new() - .stack_size(8 * 1_048_576) - .spawn(|| { - let mut foreign_accounts = Vec::new(); - - let last_foreign_account_code_source = " +#[tokio::test] +async fn test_nested_fpi_stack_overflow() -> anyhow::Result<()> { + let mut foreign_accounts = Vec::new(); + + let last_foreign_account_code_source = " use.miden::account export.get_item_foreign @@ -1026,27 +1025,27 @@ fn test_nested_fpi_stack_overflow() { end "; - let storage_slots = vec![AccountStorage::mock_item_0().slot]; - let last_foreign_account_component = AccountComponent::compile( - last_foreign_account_code_source, - TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), - storage_slots, - ) - .unwrap() - .with_supports_all_types(); + let storage_slots = vec![AccountStorage::mock_item_0().slot]; + let last_foreign_account_component = AccountComponent::compile( + last_foreign_account_code_source, + TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), + storage_slots, + ) + .unwrap() + .with_supports_all_types(); - let last_foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) - .with_auth_component(Auth::IncrNonce) - .with_component(last_foreign_account_component) - .build_existing() - .unwrap(); + let last_foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(last_foreign_account_component) + .build_existing() + .unwrap(); - foreign_accounts.push(last_foreign_account); + foreign_accounts.push(last_foreign_account); - for foreign_account_index in 0..63 { - let next_account = foreign_accounts.last().unwrap(); + for foreign_account_index in 0..63 { + let next_account = foreign_accounts.last().unwrap(); - let foreign_account_code_source = format!( + let foreign_account_code_source = format!( " use.miden::tx use.std::sys @@ -1074,46 +1073,50 @@ fn test_nested_fpi_stack_overflow() { next_foreign_prefix = next_account.id().prefix().as_felt(), ); - let foreign_account_component = AccountComponent::compile( - foreign_account_code_source, - TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), - vec![], - ) - .unwrap() - .with_supports_all_types(); - - let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) - .with_auth_component(Auth::IncrNonce) - .with_component(foreign_account_component) - .build_existing() - .unwrap(); - - foreign_accounts.push(foreign_account) - } - - // ------ NATIVE ACCOUNT --------------------------------------------------------------- - let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) - .with_auth_component(Auth::IncrNonce) - .with_component( - MockAccountComponent::with_empty_slots(), - ) - .storage_mode(AccountStorageMode::Public) - .build_existing() - .unwrap(); - - let mut mock_chain = MockChainBuilder::with_accounts( - [vec![native_account.clone()], foreign_accounts.clone()].concat(), - ).unwrap().build().unwrap(); - - mock_chain.prove_next_block().unwrap(); - - let foreign_accounts: Vec = foreign_accounts - .iter() - .map(|acc| mock_chain.get_foreign_account_inputs(acc.id()) - .expect("failed to get foreign account inputs")) - .collect(); - - let code = format!( + let foreign_account_component = AccountComponent::compile( + foreign_account_code_source, + TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), + vec![], + ) + .unwrap() + .with_supports_all_types(); + + let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(foreign_account_component) + .build_existing() + .unwrap(); + + foreign_accounts.push(foreign_account) + } + + // ------ NATIVE ACCOUNT --------------------------------------------------------------- + let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(MockAccountComponent::with_empty_slots()) + .storage_mode(AccountStorageMode::Public) + .build_existing() + .unwrap(); + + let mut mock_chain = MockChainBuilder::with_accounts( + [vec![native_account.clone()], foreign_accounts.clone()].concat(), + ) + .unwrap() + .build() + .unwrap(); + + mock_chain.prove_next_block().unwrap(); + + let foreign_accounts: Vec = foreign_accounts + .iter() + .map(|acc| { + mock_chain + .get_foreign_account_inputs(acc.id()) + .expect("failed to get foreign account inputs") + }) + .collect(); + + let code = format!( " use.std::sys @@ -1143,28 +1146,26 @@ fn test_nested_fpi_stack_overflow() { foreign_suffix = foreign_accounts.last().unwrap().id().suffix(), ); - let tx_script = ScriptBuilder::default().compile_tx_script(code).unwrap(); + let tx_script = ScriptBuilder::default().compile_tx_script(code).unwrap(); - let tx_context = mock_chain - .build_tx_context(native_account.id(), &[], &[]) - .expect("failed to build tx context") - .foreign_accounts(foreign_accounts) - .enable_lazy_loading() - .tx_script(tx_script) - .build().unwrap(); + let tx_context = mock_chain + .build_tx_context(native_account.id(), &[], &[]) + .expect("failed to build tx context") + .foreign_accounts(foreign_accounts) + .enable_lazy_loading() + .tx_script(tx_script) + .build() + .unwrap(); - let result = tx_context.execute_blocking(); + let result = tx_context.execute().await; - assert_transaction_executor_error!(result, ERR_FOREIGN_ACCOUNT_MAX_NUMBER_EXCEEDED); - }) - .expect("thread panic external") - .join() - .expect("thread panic internal"); + assert_transaction_executor_error!(result, ERR_FOREIGN_ACCOUNT_MAX_NUMBER_EXCEEDED); + Ok(()) } /// Test that code will panic in attempt to call a procedure from the native account. -#[test] -fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { +#[tokio::test] +async fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { // ------ FIRST FOREIGN ACCOUNT --------------------------------------------------------------- let foreign_account_code_source = " use.miden::tx @@ -1267,7 +1268,8 @@ fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { .extend_advice_inputs(advice_inputs) .tx_script(tx_script) .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(result, ERR_FOREIGN_ACCOUNT_CONTEXT_AGAINST_NATIVE_ACCOUNT); Ok(()) @@ -1275,8 +1277,8 @@ fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { /// Test that providing an account whose commitment does not match the one in the account tree /// results in an error. -#[test] -fn test_fpi_stale_account() -> anyhow::Result<()> { +#[tokio::test] +async fn test_fpi_stale_account() -> anyhow::Result<()> { // Prepare the test data let foreign_account_code_source = " use.miden::account @@ -1387,8 +1389,8 @@ fn test_fpi_stale_account() -> anyhow::Result<()> { /// This test checks that our `miden::get_id` and `miden::get_native_id` procedures return IDs of /// the current and native account respectively while being called from the foreign account. -#[test] -fn test_fpi_get_account_id() -> anyhow::Result<()> { +#[tokio::test] +async fn test_fpi_get_account_id() -> anyhow::Result<()> { let foreign_account_code_source = " use.miden::account @@ -1493,7 +1495,8 @@ fn test_fpi_get_account_id() -> anyhow::Result<()> { .enable_lazy_loading() .tx_script(tx_script) .build()? - .execute_blocking()?; + .execute() + .await?; Ok(()) } @@ -1501,8 +1504,8 @@ fn test_fpi_get_account_id() -> anyhow::Result<()> { /// This test checks that our `miden::get_nonce` and `miden::get_native_nonce` procedures return /// nonce values of the current and native account respectively while being called from the foreign /// account. -#[test] -fn test_fpi_get_account_nonce() -> anyhow::Result<()> { +#[tokio::test] +async fn test_fpi_get_account_nonce() -> anyhow::Result<()> { let foreign_account_code_source = " use.miden::account @@ -1604,7 +1607,8 @@ fn test_fpi_get_account_nonce() -> anyhow::Result<()> { .enable_lazy_loading() .tx_script(tx_script) .build()? - .execute_blocking()?; + .execute() + .await?; Ok(()) } @@ -1680,8 +1684,8 @@ fn foreign_account_data_memory_assertions(foreign_account: &Account, process: &P } /// Test that get_item_init and get_map_item_init work correctly with foreign accounts. -#[test] -fn test_get_item_init_and_get_map_item_init_with_foreign_account() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_item_init_and_get_map_item_init_with_foreign_account() -> anyhow::Result<()> { // Create a native account let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) @@ -1773,7 +1777,8 @@ fn test_get_item_init_and_get_map_item_init_with_foreign_account() -> anyhow::Re .foreign_accounts(vec![foreign_account_inputs]) .tx_script(tx_script) .build()? - .execute_blocking()?; + .execute() + .await?; Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index eece48380f..25595dd181 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -10,8 +10,8 @@ use crate::TxContextInput; /// Check that the assets number and assets commitment obtained from the /// `input_note::get_assets_info` procedure is correct for each note with zero, one and two /// different assets. -#[test] -fn test_get_asset_info() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_asset_info() -> anyhow::Result<()> { let TestSetup { mock_chain, account, @@ -85,15 +85,15 @@ fn test_get_asset_info() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } /// Check that recipient and metadata of a note with one asset obtained from the /// `input_note::get_recipient` and `input_note::get_metadata` procedures are correct. -#[test] -fn test_get_recipient_and_metadata() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { let TestSetup { mock_chain, account, @@ -139,15 +139,15 @@ fn test_get_recipient_and_metadata() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } /// Check that a sender of a note with one asset obtained from the `input_note::get_sender` /// procedure is correct. -#[test] -fn test_get_sender() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_sender() -> anyhow::Result<()> { let TestSetup { mock_chain, account, @@ -188,15 +188,15 @@ fn test_get_sender() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } /// Check that the assets number and assets data obtained from the `input_note::get_assets` /// procedure is correct for each note with zero, one and two different assets. -#[test] -fn test_get_assets() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_assets() -> anyhow::Result<()> { let TestSetup { mock_chain, account, @@ -283,15 +283,15 @@ fn test_get_assets() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } /// Check that the number of the inputs and their commitment of a note with one asset /// obtained from the `input_note::get_inputs_info` procedure is correct. -#[test] -fn test_get_inputs_info() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_inputs_info() -> anyhow::Result<()> { let TestSetup { mock_chain, account, @@ -333,15 +333,15 @@ fn test_get_inputs_info() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } /// Check that the script root of a note with one asset obtained from the /// `input_note::get_script_root` procedure is correct. -#[test] -fn test_get_script_root() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_script_root() -> anyhow::Result<()> { let TestSetup { mock_chain, account, @@ -376,15 +376,15 @@ fn test_get_script_root() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } /// Check that the serial number of a note with one asset obtained from the /// `input_note::get_serial_number` procedure is correct. -#[test] -fn test_get_serial_number() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_serial_number() -> anyhow::Result<()> { let TestSetup { mock_chain, account, @@ -419,7 +419,7 @@ fn test_get_serial_number() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index bf3e617dcd..a3813ebe63 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -22,8 +22,8 @@ use crate::{Auth, MockChain, TransactionContextBuilder}; /// Tests that adding two different assets to the account vault succeeds when lazy loading is /// enabled. -#[test] -fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { let faucet_id1: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); let faucet_id2: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into().unwrap(); @@ -63,7 +63,7 @@ fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { .with_source_manager(source_manager) .build()?; let account = tx_context.account().clone(); - let tx = tx_context.execute_blocking()?; + let tx = tx_context.execute().await?; let mut account_vault = account.vault().clone(); account_vault.add_asset(fungible_asset1.into())?; @@ -76,8 +76,8 @@ fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { /// Tests that removing two different assets from the account vault succeeds when lazy loading is /// enabled. -#[test] -fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { let faucet_id1: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); let faucet_id2: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into().unwrap(); @@ -129,7 +129,7 @@ fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { .with_source_manager(source_manager) .build()?; let account = tx_context.account().clone(); - let tx = tx_context.execute_blocking()?; + let tx = tx_context.execute().await?; let mut account_vault = account.vault().clone(); account_vault.remove_asset(fungible_asset1.into())?; @@ -145,8 +145,8 @@ fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> { /// /// The non-empty vault is important for the test because the advice provider's merkle store has all /// merkle paths for an empty vault by default, and so there would be nothing to load. -#[test] -fn loading_fee_asset_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn loading_fee_asset_succeeds() -> anyhow::Result<()> { let mut builder = MockChain::builder().native_asset_id(ACCOUNT_ID_NATIVE_ASSET_FAUCET.try_into()?); let account = builder.add_existing_mock_account_with_assets( @@ -161,7 +161,8 @@ fn loading_fee_asset_succeeds() -> anyhow::Result<()> { .build_tx_context(account, &[], &[])? .enable_lazy_loading() .build()? - .execute_blocking()?; + .execute() + .await?; Ok(()) } @@ -171,8 +172,8 @@ fn loading_fee_asset_succeeds() -> anyhow::Result<()> { /// Tests that updating or inserting a map item into a storage map succeeds when lazy loading is /// enabled. -#[test] -fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { // Fetch a random existing key from the map. let mock_map = AccountStorage::mock_map(); let existing_key = *mock_map.entries().next().unwrap().0; @@ -222,7 +223,8 @@ fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { .enable_lazy_loading() .with_source_manager(source_manager) .build()? - .execute_blocking()?; + .execute() + .await?; let map_delta = tx.account_delta().storage().maps().get(&map_index).unwrap(); assert_eq!(map_delta.entries().get(&LexicographicWord::new(existing_key)).unwrap(), &value0); @@ -235,8 +237,8 @@ fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { } /// Tests that getting a map item from a storage map succeeds when lazy loading is enabled. -#[test] -fn getting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn getting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { // Fetch a random existing key from the map. let mock_map = AccountStorage::mock_map(); let (existing_key, existing_value) = mock_map.entries().next().unwrap(); @@ -284,7 +286,8 @@ fn getting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { .enable_lazy_loading() .with_source_manager(source_manager) .build()? - .execute_blocking()?; + .execute() + .await?; Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index f5ba4751de..11d4451165 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -437,8 +437,8 @@ fn test_build_metadata() -> miette::Result<()> { } /// This serves as a test that setting a custom timestamp on mock chain blocks works. -#[test] -pub fn test_timelock() -> anyhow::Result<()> { +#[tokio::test] +pub async fn test_timelock() -> anyhow::Result<()> { const TIMESTAMP_ERROR: MasmError = MasmError::from_static_str("123"); let code = format!( @@ -495,7 +495,7 @@ pub fn test_timelock() -> anyhow::Result<()> { .with_source_manager(source_manager.clone()) .tx_inputs(tx_inputs.clone()) .build()?; - let result = tx_context.execute_blocking(); + let result = tx_context.execute().await; assert_transaction_executor_error!(result, TIMESTAMP_ERROR); // Consume note where lock timestamp matches the block timestamp. @@ -506,7 +506,7 @@ pub fn test_timelock() -> anyhow::Result<()> { let tx_inputs = mock_chain.get_transaction_inputs(&account, &[timelock_note.id()], &[])?; let tx_context = TransactionContextBuilder::new(account).tx_inputs(tx_inputs).build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } @@ -516,8 +516,8 @@ pub fn test_timelock() -> anyhow::Result<()> { /// /// Previously this setup was leading to the values collision in the advice map, see the /// [issue #1267](https://github.com/0xMiden/miden-base/issues/1267) for more details. -#[test] -fn test_public_key_as_note_input() -> anyhow::Result<()> { +#[tokio::test] +async fn test_public_key_as_note_input() -> anyhow::Result<()> { let mut rng = ChaCha20Rng::from_seed(Default::default()); let sec_key = SecretKey::with_rng(&mut rng); // this value will be used both as public key in the RPO component of the target account and as @@ -560,6 +560,6 @@ fn test_public_key_as_note_input() -> anyhow::Result<()> { .authenticator(authenticator) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index e6dee6be6d..03ec1ae3c0 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -602,8 +602,8 @@ fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { } /// Tests that creating a note with a fungible asset with amount zero works. -#[test] -fn creating_note_with_fungible_asset_amount_zero_works() -> anyhow::Result<()> { +#[tokio::test] +async fn creating_note_with_fungible_asset_amount_zero_works() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::IncrNonce)?; let output_note = builder.add_p2id_note( @@ -618,7 +618,8 @@ fn creating_note_with_fungible_asset_amount_zero_works() -> anyhow::Result<()> { chain .build_tx_context(account, &[input_note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; Ok(()) } @@ -718,8 +719,8 @@ fn test_build_recipient_hash() -> anyhow::Result<()> { /// - Right after the previous check to make sure it returns the same commitment from the cached /// data. /// - After adding the second `asset_1` to the note. -#[test] -fn test_get_asset_info() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_asset_info() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let fungible_asset_0 = Asset::Fungible( @@ -854,15 +855,15 @@ fn test_get_asset_info() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } /// Check that recipient and metadata of a note with one asset obtained from the /// `output_note::get_recipient` procedure is correct. -#[test] -fn test_get_recipient_and_metadata() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = @@ -926,15 +927,15 @@ fn test_get_recipient_and_metadata() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } /// Check that the assets number and assets data obtained from the `output_note::get_assets` /// procedure is correct for each note with zero, one and two different assets. -#[test] -fn test_get_assets() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_assets() -> anyhow::Result<()> { let TestSetup { mock_chain, account, @@ -1032,7 +1033,7 @@ fn test_get_assets() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index af0e5d12ef..249f46f31f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -548,8 +548,8 @@ fn input_notes_memory_assertions( /// Tests that a simple account can be created in a complete transaction execution (not using /// [`TransactionContext::execute_code`]). -#[test] -fn create_simple_account() -> anyhow::Result<()> { +#[tokio::test] +async fn create_simple_account() -> anyhow::Result<()> { let account = AccountBuilder::new([6; 32]) .storage_mode(AccountStorageMode::Public) .with_auth_component(Auth::IncrNonce) @@ -558,7 +558,8 @@ fn create_simple_account() -> anyhow::Result<()> { let tx = TransactionContextBuilder::new(account) .build()? - .execute_blocking() + .execute() + .await .context("failed to execute account-creating transaction")?; assert_eq!(tx.account_delta().nonce_delta(), Felt::new(1)); @@ -574,13 +575,13 @@ fn create_simple_account() -> anyhow::Result<()> { /// Test helper which executes the prologue to check if the creation of the given `account` with its /// `seed` is valid in the context of the given `mock_chain`. -pub fn create_account_test( +pub async fn create_account_test( account: Account, ) -> Result { - TransactionContextBuilder::new(account).build().unwrap().execute_blocking() + TransactionContextBuilder::new(account).build().unwrap().execute().await } -pub fn create_multiple_accounts_test(storage_mode: AccountStorageMode) -> anyhow::Result<()> { +pub async fn create_multiple_accounts_test(storage_mode: AccountStorageMode) -> anyhow::Result<()> { let mut accounts = Vec::new(); for account_type in [ @@ -604,7 +605,7 @@ pub fn create_multiple_accounts_test(storage_mode: AccountStorageMode) -> anyhow for account in accounts { let account_type = account.account_type(); - create_account_test(account).context(format!( + create_account_test(account).await.context(format!( "create_multiple_accounts_test test failed for account type {account_type}" ))?; } @@ -613,13 +614,13 @@ pub fn create_multiple_accounts_test(storage_mode: AccountStorageMode) -> anyhow } /// Tests that a valid account of each storage mode can be created successfully. -#[test] -pub fn create_accounts_with_all_storage_modes() -> anyhow::Result<()> { - create_multiple_accounts_test(AccountStorageMode::Private)?; +#[tokio::test] +pub async fn create_accounts_with_all_storage_modes() -> anyhow::Result<()> { + create_multiple_accounts_test(AccountStorageMode::Private).await?; - create_multiple_accounts_test(AccountStorageMode::Public)?; + create_multiple_accounts_test(AccountStorageMode::Public).await?; - create_multiple_accounts_test(AccountStorageMode::Network) + create_multiple_accounts_test(AccountStorageMode::Network).await } /// Takes an account with a placeholder ID and returns the same account but with its ID replaced @@ -652,8 +653,8 @@ fn compute_valid_account_id(account: Account) -> Account { /// Tests that creating a fungible faucet account with a non-empty initial balance in its reserved /// slot fails. -#[test] -pub fn create_account_fungible_faucet_invalid_initial_balance() -> anyhow::Result<()> { +#[tokio::test] +pub async fn create_account_fungible_faucet_invalid_initial_balance() -> anyhow::Result<()> { let account = AccountBuilder::new([1; 32]) .account_type(AccountType::FungibleFaucet) .with_auth_component(NoopAuthComponent) @@ -672,7 +673,7 @@ pub fn create_account_fungible_faucet_invalid_initial_balance() -> anyhow::Resul let account = Account::new(id, vault, storage, code, ONE, None)?; let account = compute_valid_account_id(account); - let result = create_account_test(account); + let result = create_account_test(account).await; assert_transaction_executor_error!( result, @@ -684,8 +685,9 @@ pub fn create_account_fungible_faucet_invalid_initial_balance() -> anyhow::Resul /// Tests that creating a non fungible faucet account with a non-empty storage map in its reserved /// slot fails. -#[test] -pub fn create_account_non_fungible_faucet_invalid_initial_reserved_slot() -> anyhow::Result<()> { +#[tokio::test] +pub async fn create_account_non_fungible_faucet_invalid_initial_reserved_slot() -> anyhow::Result<()> +{ // Create a storage map with a mock asset to make it non-empty. let asset = NonFungibleAsset::mock(&[1, 2, 3, 4]); let non_fungible_storage_map = @@ -705,7 +707,7 @@ pub fn create_account_non_fungible_faucet_invalid_initial_reserved_slot() -> any let account = Account::new(id, vault, storage, code, ONE, None)?; let account = compute_valid_account_id(account); - let result = create_account_test(account); + let result = create_account_test(account).await; assert_transaction_executor_error!( result, @@ -716,8 +718,8 @@ pub fn create_account_non_fungible_faucet_invalid_initial_reserved_slot() -> any } /// Tests that supplying an invalid seed causes account creation to fail. -#[test] -pub fn create_account_invalid_seed() -> anyhow::Result<()> { +#[tokio::test] +pub async fn create_account_invalid_seed() -> anyhow::Result<()> { let mut mock_chain = MockChain::new(); mock_chain.prove_next_block()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 178e0c2506..5dae4d8e40 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -64,8 +64,8 @@ use crate::utils::{create_public_p2any_note, create_spawn_note}; use crate::{Auth, MockChain, TransactionContextBuilder}; /// Tests that executing a transaction with a foreign account whose inputs are stale fails. -#[test] -fn transaction_with_stale_foreign_account_inputs_fails() -> anyhow::Result<()> { +#[tokio::test] +async fn transaction_with_stale_foreign_account_inputs_fails() -> anyhow::Result<()> { // Create a chain with an account let mut builder = MockChain::builder(); let native_account = builder.add_existing_wallet(Auth::IncrNonce)?; @@ -80,10 +80,7 @@ fn transaction_with_stale_foreign_account_inputs_fails() -> anyhow::Result<()> { .expect("failed to get foreign account inputs"); // Create a new unrelated account to modify the account tree. - let tx = mock_chain - .build_tx_context(new_account, &[], &[])? - .build()? - .execute_blocking()?; + let tx = mock_chain.build_tx_context(new_account, &[], &[])?.build()?.execute().await?; mock_chain.add_pending_executed_transaction(&tx)?; mock_chain.prove_next_block()?; @@ -93,7 +90,8 @@ fn transaction_with_stale_foreign_account_inputs_fails() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[])? .foreign_accounts(vec![inputs]) .build()? - .execute_blocking(); + .execute() + .await; assert_matches::assert_matches!( transaction, @@ -166,8 +164,8 @@ async fn consuming_note_created_in_future_block_fails() -> anyhow::Result<()> { // BLOCK TESTS // ================================================================================================ -#[test] -fn test_block_procedures() -> anyhow::Result<()> { +#[tokio::test] +async fn test_block_procedures() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let code = " @@ -210,8 +208,8 @@ fn test_block_procedures() -> anyhow::Result<()> { Ok(()) } -#[test] -fn executed_transaction_output_notes() -> anyhow::Result<()> { +#[tokio::test] +async fn executed_transaction_output_notes() -> anyhow::Result<()> { let executor_account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, IncrNonceAuthComponent); let account_id = executor_account.id(); @@ -397,7 +395,7 @@ fn executed_transaction_output_notes() -> anyhow::Result<()> { ]) .build()?; - let executed_transaction = tx_context.execute_blocking()?; + let executed_transaction = tx_context.execute().await?; // output notes // -------------------------------------------------------------------------------------------- @@ -450,8 +448,8 @@ fn executed_transaction_output_notes() -> anyhow::Result<()> { /// Tests that a transaction consuming and creating one note can emit an abort event in its auth /// component to result in a [`TransactionExecutorError::Unauthorized`] error. -#[test] -fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { +#[tokio::test] +async fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { let source_code = r#" use.miden::auth use.miden::tx @@ -513,7 +511,7 @@ fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { let input_notes = tx_context.input_notes().clone(); let output_notes = OutputNotes::new(vec![OutputNote::Partial(output_note.into())])?; - let error = tx_context.execute_blocking().unwrap_err(); + let error = tx_context.execute().await.unwrap_err(); assert_matches!(error, TransactionExecutorError::Unauthorized(tx_summary) => { assert!(tx_summary.account_delta().vault().is_empty()); @@ -531,8 +529,8 @@ fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { /// Tests that a transaction consuming and creating one note with basic authentication correctly /// signs the transaction summary. -#[test] -fn tx_summary_commitment_is_signed_by_falcon_auth() -> anyhow::Result<()> { +#[tokio::test] +async fn tx_summary_commitment_is_signed_by_falcon_auth() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::BasicAuth)?; let mut rng = RpoRandomCoin::new(Word::empty()); @@ -550,7 +548,8 @@ fn tx_summary_commitment_is_signed_by_falcon_auth() -> anyhow::Result<()> { let tx = chain .build_tx_context(account.id(), &[spawn_note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; let summary = TransactionSummary::new( tx.account_delta().clone(), @@ -641,8 +640,8 @@ async fn execute_tx_view_script() -> anyhow::Result<()> { // ================================================================================================ /// Tests transaction script inputs. -#[test] -fn test_tx_script_inputs() -> anyhow::Result<()> { +#[tokio::test] +async fn test_tx_script_inputs() -> anyhow::Result<()> { let tx_script_input_key = Word::from([9999, 8888, 9999, 8888u32]); let tx_script_input_value = Word::from([9, 8, 7, 6u32]); let tx_script_src = format!( @@ -669,14 +668,14 @@ fn test_tx_script_inputs() -> anyhow::Result<()> { .extend_advice_map([(tx_script_input_key, tx_script_input_value.to_vec())]) .build()?; - tx_context.execute_blocking().context("failed to execute transaction")?; + tx_context.execute().await.context("failed to execute transaction")?; Ok(()) } /// Tests transaction script arguments. -#[test] -fn test_tx_script_args() -> anyhow::Result<()> { +#[tokio::test] +async fn test_tx_script_args() -> anyhow::Result<()> { let tx_script_args = Word::from([1, 2, 3, 4u32]); let tx_script_src = r#" @@ -715,15 +714,15 @@ fn test_tx_script_args() -> anyhow::Result<()> { .tx_script_args(tx_script_args) .build()?; - tx_context.execute_blocking()?; + tx_context.execute().await?; Ok(()) } // Tests that advice map from the account code and transaction script gets correctly passed as // part of the transaction advice inputs -#[test] -fn inputs_created_correctly() -> anyhow::Result<()> { +#[tokio::test] +async fn inputs_created_correctly() -> anyhow::Result<()> { let account_code_script = r#" adv_map.A([6,7,8,9])=[10,11,12,13] @@ -787,7 +786,7 @@ fn inputs_created_correctly() -> anyhow::Result<()> { Felt::new(1u64), ); let tx_context = crate::TransactionContextBuilder::new(account).tx_script(tx_script).build()?; - _ = tx_context.execute_blocking()?; + _ = tx_context.execute().await?; Ok(()) } diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 4fbdfbe480..d6dbe5e7c9 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -1151,8 +1151,8 @@ mod tests { Ok(()) } - #[test] - fn private_account_state_update() -> anyhow::Result<()> { + #[tokio::test] + async fn private_account_state_update() -> anyhow::Result<()> { let faucet_id = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?; let account_builder = AccountBuilder::new([4; 32]) .storage_mode(AccountStorageMode::Private) @@ -1181,7 +1181,8 @@ mod tests { let tx = mock_chain .build_tx_context(TxContextInput::Account(account), &[], &[note_1])? .build()? - .execute_blocking()?; + .execute() + .await?; mock_chain.add_pending_executed_transaction(&tx)?; mock_chain.prove_next_block()?; @@ -1195,8 +1196,8 @@ mod tests { Ok(()) } - #[test] - fn mock_chain_serialization() { + #[tokio::test] + async fn mock_chain_serialization() { let mut builder = MockChain::builder(); let mut notes = vec![]; @@ -1232,7 +1233,8 @@ mod tests { .unwrap() .build() .unwrap() - .execute_blocking() + .execute() + .await .unwrap(); chain.add_pending_executed_transaction(&tx).unwrap(); chain.prove_next_block().unwrap(); diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index 3b820c1c67..eee3098410 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -144,18 +144,6 @@ impl TransactionContext { tx_executor.execute_transaction(account_id, block_num, notes, tx_args).await } - /// Executes the transaction through a [TransactionExecutor] - /// - /// TODO: This is a temporary workaround to avoid having to update each test to use tokio::test. - /// Eventually we should get rid of this method and use tokio::test + execute, but for the POC - /// stage this is easier. - pub fn execute_blocking(self) -> Result { - tokio::runtime::Builder::new_current_thread() - .build() - .unwrap() - .block_on(self.execute()) - } - pub fn account(&self) -> &Account { &self.account } diff --git a/crates/miden-testing/tests/auth/rpo_falcon_acl.rs b/crates/miden-testing/tests/auth/rpo_falcon_acl.rs index 117b1c0d8f..2b8d51fe19 100644 --- a/crates/miden-testing/tests/auth/rpo_falcon_acl.rs +++ b/crates/miden-testing/tests/auth/rpo_falcon_acl.rs @@ -75,8 +75,8 @@ fn setup_rpo_falcon_acl_test( Ok((account, mock_chain, note)) } -#[test] -fn test_rpo_falcon_acl() -> anyhow::Result<()> { +#[tokio::test] +async fn test_rpo_falcon_acl() -> anyhow::Result<()> { let (account, mock_chain, note) = setup_rpo_falcon_acl_test(false, true)?; // We need to get the authenticator separately for this test @@ -135,7 +135,8 @@ fn test_rpo_falcon_acl() -> anyhow::Result<()> { .build()?; tx_context_with_auth_1 - .execute_blocking() + .execute() + .await .expect("trigger 1 with auth should succeed"); // Test 2: Transaction WITH authenticator calling trigger procedure 2 (should succeed) @@ -146,7 +147,8 @@ fn test_rpo_falcon_acl() -> anyhow::Result<()> { .build()?; tx_context_with_auth_2 - .execute_blocking() + .execute() + .await .expect("trigger 2 with auth should succeed"); // Test 3: Transaction WITHOUT authenticator calling trigger procedure (should fail) @@ -156,7 +158,7 @@ fn test_rpo_falcon_acl() -> anyhow::Result<()> { .tx_script(tx_script_trigger_1) .build()?; - let executed_tx_no_auth = tx_context_no_auth.execute_blocking(); + let executed_tx_no_auth = tx_context_no_auth.execute().await; assert_matches!(executed_tx_no_auth, Err(TransactionExecutorError::MissingAuthenticator)); @@ -168,7 +170,8 @@ fn test_rpo_falcon_acl() -> anyhow::Result<()> { .build()?; let executed = tx_context_no_trigger - .execute_blocking() + .execute() + .await .expect("no trigger, no auth should succeed"); assert_eq!( executed.account_delta().nonce_delta(), @@ -179,8 +182,8 @@ fn test_rpo_falcon_acl() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_rpo_falcon_acl_with_allow_unauthorized_output_notes() -> anyhow::Result<()> { +#[tokio::test] +async fn test_rpo_falcon_acl_with_allow_unauthorized_output_notes() -> anyhow::Result<()> { let (account, mock_chain, note) = setup_rpo_falcon_acl_test(true, true)?; // Verify the storage layout includes both authorization flags @@ -204,7 +207,8 @@ fn test_rpo_falcon_acl_with_allow_unauthorized_output_notes() -> anyhow::Result< .build()?; let executed = tx_context_no_trigger - .execute_blocking() + .execute() + .await .expect("no trigger, no auth should succeed"); assert_eq!( executed.account_delta().nonce_delta(), @@ -215,8 +219,8 @@ fn test_rpo_falcon_acl_with_allow_unauthorized_output_notes() -> anyhow::Result< Ok(()) } -#[test] -fn test_rpo_falcon_acl_with_disallow_unauthorized_input_notes() -> anyhow::Result<()> { +#[tokio::test] +async fn test_rpo_falcon_acl_with_disallow_unauthorized_input_notes() -> anyhow::Result<()> { let (account, mock_chain, note) = setup_rpo_falcon_acl_test(true, false)?; // Verify the storage layout includes both flags @@ -239,7 +243,7 @@ fn test_rpo_falcon_acl_with_disallow_unauthorized_input_notes() -> anyhow::Resul .tx_script(tx_script_no_trigger) .build()?; - let executed_tx_no_auth = tx_context_no_auth.execute_blocking(); + let executed_tx_no_auth = tx_context_no_auth.execute().await; // This should fail with MissingAuthenticator error because input notes are being consumed // and allow_unauthorized_input_notes is false diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index c142482448..d54c48e408 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -58,7 +58,7 @@ pub fn create_mint_script_code(params: &FaucetTestParams) -> String { } /// Executes a minting transaction with the given faucet and parameters -pub fn execute_mint_transaction( +pub async fn execute_mint_transaction( mock_chain: &mut MockChain, faucet: Account, params: &FaucetTestParams, @@ -67,7 +67,7 @@ pub fn execute_mint_transaction( let tx_script = ScriptBuilder::default().compile_tx_script(tx_script_code)?; let tx_context = mock_chain.build_tx_context(faucet, &[], &[])?.tx_script(tx_script).build()?; - Ok(tx_context.execute_blocking()?) + Ok(tx_context.execute().await?) } /// Verifies minted output note matches expectations @@ -101,8 +101,8 @@ pub fn verify_minted_output_note( // ================================================================================================ /// Tests that minting assets on an existing faucet succeeds. -#[test] -fn minting_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn minting_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let faucet = builder.add_existing_faucet(Auth::BasicAuth, "TST", 200, None)?; let mut mock_chain = builder.build()?; @@ -121,14 +121,15 @@ fn minting_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result<()> { .validate(params.note_type) .expect("note tag should support private notes"); - let executed_transaction = execute_mint_transaction(&mut mock_chain, faucet.clone(), ¶ms)?; + let executed_transaction = + execute_mint_transaction(&mut mock_chain, faucet.clone(), ¶ms).await?; verify_minted_output_note(&executed_transaction, &faucet, ¶ms)?; Ok(()) } -#[test] -fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() -> anyhow::Result<()> { +#[tokio::test] +async fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() -> anyhow::Result<()> { // CONSTRUCT AND EXECUTE TX (Failure) // -------------------------------------------------------------------------------------------- let mut builder = MockChain::builder(); @@ -170,7 +171,8 @@ fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() -> anyhow::Res .build_tx_context(faucet.id(), &[], &[])? .tx_script(tx_script) .build()? - .execute_blocking(); + .execute() + .await; // Execute the transaction and get the witness assert_transaction_executor_error!( @@ -184,8 +186,8 @@ fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() -> anyhow::Res // ================================================================================================ /// Tests that minting assets on a new faucet succeeds. -#[test] -fn minting_fungible_asset_on_new_faucet_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn minting_fungible_asset_on_new_faucet_succeeds() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let faucet = builder.create_new_faucet(Auth::BasicAuth, "TST", 200)?; let mut mock_chain = builder.build()?; @@ -204,7 +206,8 @@ fn minting_fungible_asset_on_new_faucet_succeeds() -> anyhow::Result<()> { .validate(params.note_type) .expect("note tag should support private notes"); - let executed_transaction = execute_mint_transaction(&mut mock_chain, faucet.clone(), ¶ms)?; + let executed_transaction = + execute_mint_transaction(&mut mock_chain, faucet.clone(), ¶ms).await?; verify_minted_output_note(&executed_transaction, &faucet, ¶ms)?; Ok(()) @@ -214,8 +217,8 @@ fn minting_fungible_asset_on_new_faucet_succeeds() -> anyhow::Result<()> { // ================================================================================================ /// Tests that burning a fungible asset on an existing faucet succeeds and proves the transaction. -#[test] -fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let faucet = builder.add_existing_faucet(Auth::BasicAuth, "TST", 200, Some(100))?; @@ -262,7 +265,8 @@ fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result< let executed_transaction = mock_chain .build_tx_context(faucet.id(), &[note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; // Prove, serialize/deserialize and verify the transaction prove_and_verify_transaction(executed_transaction.clone())?; diff --git a/crates/miden-testing/tests/scripts/fee.rs b/crates/miden-testing/tests/scripts/fee.rs index 4182de86ba..70622e4819 100644 --- a/crates/miden-testing/tests/scripts/fee.rs +++ b/crates/miden-testing/tests/scripts/fee.rs @@ -14,8 +14,8 @@ use crate::prove_and_verify_transaction; /// This is an interesting test case because the prover needs to apply the fee asset to the account /// delta in order to prove the correct delta commitment. Once we have other tests with fees, this /// test may become obsolete. -#[test] -fn prove_account_creation_with_fees() -> anyhow::Result<()> { +#[tokio::test] +async fn prove_account_creation_with_fees() -> anyhow::Result<()> { let amount = 10_000; let mut builder = MockChain::builder().verification_base_fee(50); let account = builder.create_new_wallet(Auth::IncrNonce)?; @@ -25,7 +25,8 @@ fn prove_account_creation_with_fees() -> anyhow::Result<()> { let tx = chain .build_tx_context(account, &[fee_note.id()], &[])? .build()? - .execute_blocking() + .execute() + .await .context("failed to execute account-creating transaction")?; let expected_fee = tx.compute_fee(); diff --git a/crates/miden-testing/tests/scripts/p2id.rs b/crates/miden-testing/tests/scripts/p2id.rs index e08ee2bb61..dda0578692 100644 --- a/crates/miden-testing/tests/scripts/p2id.rs +++ b/crates/miden-testing/tests/scripts/p2id.rs @@ -20,8 +20,8 @@ use crate::prove_and_verify_transaction; /// We test the Pay to script with 2 assets to test the loop inside the script. /// So we create a note containing two assets that can only be consumed by the target account. -#[test] -fn p2id_script_multiple_assets() -> anyhow::Result<()> { +#[tokio::test] +async fn p2id_script_multiple_assets() -> anyhow::Result<()> { // Create assets let fungible_asset_1: Asset = FungibleAsset::mock(123); let fungible_asset_2: Asset = @@ -50,7 +50,8 @@ fn p2id_script_multiple_assets() -> anyhow::Result<()> { let executed_transaction = mock_chain .build_tx_context(target_account.id(), &[note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; // vault delta let target_account_after: Account = Account::new_existing( @@ -74,7 +75,8 @@ fn p2id_script_multiple_assets() -> anyhow::Result<()> { let executed_transaction_2 = mock_chain .build_tx_context(malicious_account.id(), &[], &[note])? .build()? - .execute_blocking(); + .execute() + .await; // Check that we got the expected result - TransactionExecutorError assert_transaction_executor_error!(executed_transaction_2, ERR_P2ID_TARGET_ACCT_MISMATCH); @@ -82,8 +84,8 @@ fn p2id_script_multiple_assets() -> anyhow::Result<()> { } /// Consumes an existing note with a new account -#[test] -fn prove_consume_note_with_new_account() -> anyhow::Result<()> { +#[tokio::test] +async fn prove_consume_note_with_new_account() -> anyhow::Result<()> { // Create assets let fungible_asset: Asset = FungibleAsset::mock(123); @@ -110,7 +112,8 @@ fn prove_consume_note_with_new_account() -> anyhow::Result<()> { let executed_transaction = mock_chain .build_tx_context(target_account.clone(), &[note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; // Apply delta to the target account to verify it is no longer new let target_account_after: Account = Account::new_existing( @@ -131,8 +134,8 @@ fn prove_consume_note_with_new_account() -> anyhow::Result<()> { /// Consumes two existing notes (with an asset from a faucet for a combined total of 123 tokens) /// with a basic account -#[test] -fn prove_consume_multiple_notes() -> anyhow::Result<()> { +#[tokio::test] +async fn prove_consume_multiple_notes() -> anyhow::Result<()> { let fungible_asset_1: Asset = FungibleAsset::mock(100); let fungible_asset_2: Asset = FungibleAsset::mock(23); @@ -157,7 +160,7 @@ fn prove_consume_multiple_notes() -> anyhow::Result<()> { .build_tx_context(account.id(), &[note_1.id(), note_2.id()], &[])? .build()?; - let executed_transaction = tx_context.execute_blocking()?; + let executed_transaction = tx_context.execute().await?; account.apply_delta(executed_transaction.account_delta())?; let resulting_asset = account.vault().assets().next().unwrap(); @@ -171,8 +174,8 @@ fn prove_consume_multiple_notes() -> anyhow::Result<()> { } /// Consumes two existing notes and creates two other notes in the same transaction -#[test] -fn test_create_consume_multiple_notes() -> anyhow::Result<()> { +#[tokio::test] +async fn test_create_consume_multiple_notes() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let mut account = @@ -267,7 +270,7 @@ fn test_create_consume_multiple_notes() -> anyhow::Result<()> { .tx_script(tx_script) .build()?; - let executed_transaction = tx_context.execute_blocking()?; + let executed_transaction = tx_context.execute().await?; assert_eq!(executed_transaction.output_notes().num_notes(), 2); diff --git a/crates/miden-testing/tests/scripts/p2ide.rs b/crates/miden-testing/tests/scripts/p2ide.rs index 5d6d6ca16c..d149ced65f 100644 --- a/crates/miden-testing/tests/scripts/p2ide.rs +++ b/crates/miden-testing/tests/scripts/p2ide.rs @@ -15,8 +15,8 @@ use miden_objects::note::{Note, NoteType}; use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; /// Test that the P2IDE note works like a regular P2ID note -#[test] -fn p2ide_script_success_without_reclaim_or_timelock() -> anyhow::Result<()> { +#[tokio::test] +async fn p2ide_script_success_without_reclaim_or_timelock() -> anyhow::Result<()> { let reclaim_height = None; // if 0, means it is not reclaimable let timelock_height = None; // if 0 means it is not timelocked @@ -33,7 +33,8 @@ fn p2ide_script_success_without_reclaim_or_timelock() -> anyhow::Result<()> { let executed_transaction_1 = mock_chain .build_tx_context(malicious_account.id(), &[], slice::from_ref(&p2ide_note))? .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(executed_transaction_1, ERR_P2IDE_RECLAIM_DISABLED); @@ -41,7 +42,8 @@ fn p2ide_script_success_without_reclaim_or_timelock() -> anyhow::Result<()> { let executed_transaction_2 = mock_chain .build_tx_context(target_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; let target_account_after: Account = Account::new_existing( target_account.id(), @@ -59,8 +61,8 @@ fn p2ide_script_success_without_reclaim_or_timelock() -> anyhow::Result<()> { } /// Test that the P2IDE note can have a timelock that unlocks before the reclaim block height -#[test] -fn p2ide_script_success_timelock_unlock_before_reclaim_height() -> anyhow::Result<()> { +#[tokio::test] +async fn p2ide_script_success_timelock_unlock_before_reclaim_height() -> anyhow::Result<()> { let reclaim_height = Some(BlockNumber::from(5u32)); let timelock_height = None; @@ -78,7 +80,8 @@ fn p2ide_script_success_timelock_unlock_before_reclaim_height() -> anyhow::Resul let executed_transaction_1 = mock_chain .build_tx_context(target_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; let target_account_after: Account = Account::new_existing( target_account.id(), @@ -97,8 +100,8 @@ fn p2ide_script_success_timelock_unlock_before_reclaim_height() -> anyhow::Resul /// Test that the P2IDE note can have a timelock set and reclaim functionality /// disabled. -#[test] -fn p2ide_script_timelocked_reclaim_disabled() -> anyhow::Result<()> { +#[tokio::test] +async fn p2ide_script_timelocked_reclaim_disabled() -> anyhow::Result<()> { let reclaim_height = None; let timelock_height = BlockNumber::from(5u32); let P2ideTestSetup { @@ -121,7 +124,8 @@ fn p2ide_script_timelocked_reclaim_disabled() -> anyhow::Result<()> { &[], )? .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(early_reclaim, ERR_P2IDE_TIMELOCK_HEIGHT_NOT_REACHED); @@ -134,7 +138,8 @@ fn p2ide_script_timelocked_reclaim_disabled() -> anyhow::Result<()> { &[], )? .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(early_spend, ERR_P2IDE_TIMELOCK_HEIGHT_NOT_REACHED); @@ -142,7 +147,8 @@ fn p2ide_script_timelocked_reclaim_disabled() -> anyhow::Result<()> { let early_reclaim = mock_chain .build_tx_context(sender_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(early_reclaim, ERR_P2IDE_RECLAIM_DISABLED); @@ -150,7 +156,8 @@ fn p2ide_script_timelocked_reclaim_disabled() -> anyhow::Result<()> { let final_tx = mock_chain .build_tx_context(target_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; let target_after = Account::new_existing( target_account.id(), @@ -169,8 +176,8 @@ fn p2ide_script_timelocked_reclaim_disabled() -> anyhow::Result<()> { /// before the timelock expires. Creating a P2IDE note with a reclaim block height that is /// less than the timelock block height would be the same as creating a P2IDE note /// where the reclaim block height is equal to the timelock block height -#[test] -fn p2ide_script_reclaim_fails_before_timelock_expiry() -> anyhow::Result<()> { +#[tokio::test] +async fn p2ide_script_reclaim_fails_before_timelock_expiry() -> anyhow::Result<()> { let reclaim_height = BlockNumber::from(1u32); let timelock_height = BlockNumber::from(5u32); @@ -188,7 +195,8 @@ fn p2ide_script_reclaim_fails_before_timelock_expiry() -> anyhow::Result<()> { let executed_transaction_1 = mock_chain .build_tx_context_at(1, sender_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!( executed_transaction_1, @@ -199,7 +207,8 @@ fn p2ide_script_reclaim_fails_before_timelock_expiry() -> anyhow::Result<()> { let executed_transaction_2 = mock_chain .build_tx_context_at(timelock_height, sender_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; let sender_account_after: Account = Account::new_existing( sender_account.id(), @@ -218,8 +227,8 @@ fn p2ide_script_reclaim_fails_before_timelock_expiry() -> anyhow::Result<()> { } /// Test that the P2IDE note can have timelock and reclaim functionality -#[test] -fn p2ide_script_reclaimable_timelockable() -> anyhow::Result<()> { +#[tokio::test] +async fn p2ide_script_reclaimable_timelockable() -> anyhow::Result<()> { let reclaim_height = BlockNumber::from(10u32); let timelock_height = BlockNumber::from(7u32); @@ -237,7 +246,8 @@ fn p2ide_script_reclaimable_timelockable() -> anyhow::Result<()> { let early_reclaim = mock_chain .build_tx_context(sender_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(early_reclaim, ERR_P2IDE_TIMELOCK_HEIGHT_NOT_REACHED); @@ -245,7 +255,8 @@ fn p2ide_script_reclaimable_timelockable() -> anyhow::Result<()> { let early_spend = mock_chain .build_tx_context(target_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(early_spend, ERR_P2IDE_TIMELOCK_HEIGHT_NOT_REACHED); @@ -256,7 +267,8 @@ fn p2ide_script_reclaimable_timelockable() -> anyhow::Result<()> { let early_reclaim = mock_chain .build_tx_context(sender_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(early_reclaim, ERR_P2IDE_RECLAIM_HEIGHT_NOT_REACHED); @@ -267,7 +279,8 @@ fn p2ide_script_reclaimable_timelockable() -> anyhow::Result<()> { let executed_transaction_1 = mock_chain .build_tx_context(malicious_account.id(), &[], slice::from_ref(&p2ide_note))? .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!( executed_transaction_1, @@ -278,7 +291,8 @@ fn p2ide_script_reclaimable_timelockable() -> anyhow::Result<()> { let final_tx = mock_chain .build_tx_context(target_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; let target_after = Account::new_existing( target_account.id(), @@ -294,8 +308,8 @@ fn p2ide_script_reclaimable_timelockable() -> anyhow::Result<()> { } /// Test that the P2IDE note can be reclaimed after timelock -#[test] -fn p2ide_script_reclaim_success_after_timelock() -> anyhow::Result<()> { +#[tokio::test] +async fn p2ide_script_reclaim_success_after_timelock() -> anyhow::Result<()> { let reclaim_height = BlockNumber::from(5); let timelock_height = BlockNumber::from(3); @@ -311,7 +325,8 @@ fn p2ide_script_reclaim_success_after_timelock() -> anyhow::Result<()> { let early_reclaim = mock_chain .build_tx_context(sender_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking(); + .execute() + .await; assert_transaction_executor_error!(early_reclaim, ERR_P2IDE_TIMELOCK_HEIGHT_NOT_REACHED); @@ -322,7 +337,8 @@ fn p2ide_script_reclaim_success_after_timelock() -> anyhow::Result<()> { let final_tx = mock_chain .build_tx_context(sender_account.id(), &[p2ide_note.id()], &[])? .build()? - .execute_blocking()?; + .execute() + .await?; let sender_after = Account::new_existing( sender_account.id(), diff --git a/crates/miden-testing/tests/scripts/send_note.rs b/crates/miden-testing/tests/scripts/send_note.rs index 1a20d206ca..69158e1156 100644 --- a/crates/miden-testing/tests/scripts/send_note.rs +++ b/crates/miden-testing/tests/scripts/send_note.rs @@ -24,8 +24,8 @@ use miden_testing::{Auth, MockChain}; /// has the [`BasicWallet`][wallet] interface. /// /// [wallet]: miden_lib::account::interface::AccountComponentInterface::BasicWallet -#[test] -fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { +#[tokio::test] +async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { let sent_asset = FungibleAsset::mock(10); let mut builder = MockChain::builder(); @@ -64,7 +64,8 @@ fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { .tx_script(send_note_transaction_script) .extend_expected_output_notes(vec![OutputNote::Full(note)]) .build()? - .execute_blocking()?; + .execute() + .await?; // assert that the removed asset is in the delta let mut removed_assets: BTreeMap<_, _> = executed_transaction @@ -87,8 +88,8 @@ fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { /// has the [`BasicFungibleFaucet`][faucet] interface. /// /// [faucet]: miden_lib::account::interface::AccountComponentInterface::BasicFungibleFaucet -#[test] -fn test_send_note_script_basic_fungible_faucet() -> anyhow::Result<()> { +#[tokio::test] +async fn test_send_note_script_basic_fungible_faucet() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let sender_basic_fungible_faucet_account = builder.add_existing_faucet(Auth::BasicAuth, "POL", 200, None)?; @@ -127,6 +128,7 @@ fn test_send_note_script_basic_fungible_faucet() -> anyhow::Result<()> { .tx_script(send_note_transaction_script) .extend_expected_output_notes(vec![OutputNote::Full(note)]) .build()? - .execute_blocking()?; + .execute() + .await?; Ok(()) } diff --git a/crates/miden-testing/tests/scripts/swap.rs b/crates/miden-testing/tests/scripts/swap.rs index 1493ed46e7..f5aa7ebe25 100644 --- a/crates/miden-testing/tests/scripts/swap.rs +++ b/crates/miden-testing/tests/scripts/swap.rs @@ -24,8 +24,8 @@ use miden_testing::{Auth, MockChain}; use crate::prove_and_verify_transaction; /// Creates a SWAP note from the transaction script and proves and verifies the transaction. -#[test] -pub fn prove_send_swap_note() -> anyhow::Result<()> { +#[tokio::test] +pub async fn prove_send_swap_note() -> anyhow::Result<()> { let payback_note_type = NoteType::Private; let SwapTestSetup { mock_chain, @@ -69,7 +69,8 @@ pub fn prove_send_swap_note() -> anyhow::Result<()> { .tx_script(tx_script) .extend_expected_output_notes(vec![OutputNote::Full(swap_note.clone())]) .build()? - .execute_blocking()?; + .execute() + .await?; sender_account .apply_delta(create_swap_note_tx.account_delta()) @@ -98,8 +99,8 @@ pub fn prove_send_swap_note() -> anyhow::Result<()> { /// payback note. The payback note is consumed by the original sender of the SWAP note. /// /// Both transactions are proven and verified. -#[test] -fn consume_swap_note_private_payback_note() -> anyhow::Result<()> { +#[tokio::test] +async fn consume_swap_note_private_payback_note() -> anyhow::Result<()> { let payback_note_type = NoteType::Private; let SwapTestSetup { mock_chain, @@ -118,7 +119,8 @@ fn consume_swap_note_private_payback_note() -> anyhow::Result<()> { .build_tx_context(target_account.id(), &[swap_note.id()], &[]) .context("failed to build tx context")? .build()? - .execute_blocking()?; + .execute() + .await?; target_account .apply_delta(consume_swap_note_tx.account_delta()) @@ -144,7 +146,8 @@ fn consume_swap_note_private_payback_note() -> anyhow::Result<()> { .build_tx_context(sender_account.id(), &[], &[full_payback_note]) .context("failed to build tx context")? .build()? - .execute_blocking()?; + .execute() + .await?; sender_account .apply_delta(consume_payback_tx.account_delta()) @@ -163,8 +166,8 @@ fn consume_swap_note_private_payback_note() -> anyhow::Result<()> { // Creates a swap note with a public payback note, then consumes it to complete the swap // The target account receives the offered asset and creates a public payback note for the sender -#[test] -fn consume_swap_note_public_payback_note() -> anyhow::Result<()> { +#[tokio::test] +async fn consume_swap_note_public_payback_note() -> anyhow::Result<()> { let payback_note_type = NoteType::Public; let SwapTestSetup { mock_chain, @@ -197,7 +200,8 @@ fn consume_swap_note_public_payback_note() -> anyhow::Result<()> { .context("failed to build tx context")? .extend_expected_output_notes(vec![OutputNote::Full(payback_p2id_note)]) .build()? - .execute_blocking()?; + .execute() + .await?; target_account.apply_delta(consume_swap_note_tx.account_delta())?; @@ -221,7 +225,8 @@ fn consume_swap_note_public_payback_note() -> anyhow::Result<()> { .build_tx_context(sender_account.id(), &[], &[full_payback_note]) .context("failed to build tx context")? .build()? - .execute_blocking()?; + .execute() + .await?; sender_account.apply_delta(consume_payback_tx.account_delta())?; @@ -231,8 +236,8 @@ fn consume_swap_note_public_payback_note() -> anyhow::Result<()> { /// Tests that a SWAP note offering asset A and requesting asset B can be matched against a SWAP /// note offering asset B and requesting asset A. -#[test] -fn settle_coincidence_of_wants() -> anyhow::Result<()> { +#[tokio::test] +async fn settle_coincidence_of_wants() -> anyhow::Result<()> { // Create two different assets for the swap let faucet0 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; let faucet1 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1)?; @@ -272,7 +277,8 @@ fn settle_coincidence_of_wants() -> anyhow::Result<()> { .build_tx_context(matcher_account.id(), &[swap_note_1.id(), swap_note_2.id()], &[]) .context("failed to build tx context")? .build()? - .execute_blocking()?; + .execute() + .await?; // VERIFY PAYBACK NOTES WERE CREATED CORRECTLY // -------------------------------------------------------------------------------------------- From 28e0f5a282a06a62d5938e8970a9648eb7116e7b Mon Sep 17 00:00:00 2001 From: Marti Date: Wed, 8 Oct 2025 02:53:01 +0200 Subject: [PATCH 077/133] docs: fix order of stack comment after loading storage (#1974) --- crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm b/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm index fb564f1675..c2c3b8d320 100644 --- a/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm +++ b/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm @@ -38,7 +38,7 @@ export.auth_tx_rpo_falcon512_acl.2 # Get the authentication configuration push.AUTH_CONFIG_SLOT exec.account::get_item - # => [0, num_auth_trigger_procs, allow_unauthorized_output_notes, allow_unauthorized_input_notes, pad(16)] + # => [0, allow_unauthorized_input_notes, allow_unauthorized_output_notes, num_auth_trigger_procs, pad(16)] drop # => [allow_unauthorized_input_notes, allow_unauthorized_output_notes, num_auth_trigger_procs, pad(16)] From 9a053d0c5ca9796f20603cd7083e6c57982e394a Mon Sep 17 00:00:00 2001 From: Marti Date: Wed, 8 Oct 2025 15:55:12 +0200 Subject: [PATCH 078/133] feat: remove circular check for generated files (#1980) --- crates/miden-lib/build.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/miden-lib/build.rs b/crates/miden-lib/build.rs index 060b109ec6..eb78ea4e4b 100644 --- a/crates/miden-lib/build.rs +++ b/crates/miden-lib/build.rs @@ -91,9 +91,6 @@ fn main() -> Result<()> { // set target directory to {OUT_DIR}/assets let target_dir = Path::new(&build_dir).join(ASSETS_DIR); - // re-build if any of the generated files were modified - println!("cargo::rerun-if-changed={}", target_dir.display()); - // compile transaction kernel let mut assembler = compile_tx_kernel(&source_dir.join(ASM_TX_KERNEL_DIR), &target_dir.join("kernels"))?; From 8a50c8f98529f9a78655385e3e78a6de44db9316 Mon Sep 17 00:00:00 2001 From: igamigo Date: Wed, 8 Oct 2025 12:15:14 -0300 Subject: [PATCH 079/133] chore: NoteFile derives (#1976) --- crates/miden-objects/src/note/file.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/miden-objects/src/note/file.rs b/crates/miden-objects/src/note/file.rs index 84b7353c21..48a77902aa 100644 --- a/crates/miden-objects/src/note/file.rs +++ b/crates/miden-objects/src/note/file.rs @@ -20,6 +20,7 @@ const MAGIC: &str = "note"; // ================================================================================================ /// A serialized representation of a note. +#[derive(Clone, Debug, PartialEq, Eq)] pub enum NoteFile { /// The note's details aren't known. NoteId(NoteId), From ef81bacdfa963eafe93cf7e0d769a5eeec6f66d5 Mon Sep 17 00:00:00 2001 From: Serge Radinovich <47865535+sergerad@users.noreply.github.com> Date: Thu, 9 Oct 2025 19:48:40 +1300 Subject: [PATCH 080/133] feat: Refactor transaction input types (#1934) --- CHANGELOG.md | 3 + .../src/time_counting_benchmarks/prove.rs | 2 +- crates/miden-lib/src/transaction/inputs.rs | 61 +++----- crates/miden-lib/src/transaction/mod.rs | 19 +-- .../src/transaction/executed_tx.rs | 50 ++---- .../src/transaction/inputs/mod.rs | 144 ++++++++++++++---- crates/miden-objects/src/transaction/mod.rs | 2 - .../miden-objects/src/transaction/tx_args.rs | 1 - .../src/transaction/tx_witness.rs | 134 ---------------- .../src/kernel_tests/tx/test_fpi.rs | 2 +- crates/miden-testing/src/mock_chain/chain.rs | 6 +- .../miden-testing/src/tx_context/builder.rs | 41 +++-- .../miden-testing/src/tx_context/context.rs | 26 ++-- crates/miden-testing/tests/lib.rs | 2 +- crates/miden-tx/src/executor/mod.rs | 53 +++---- crates/miden-tx/src/executor/notes_checker.rs | 55 ++++--- crates/miden-tx/src/lib.rs | 2 - crates/miden-tx/src/prover/mod.rs | 31 ++-- crates/miden-tx/src/verifier/mod.rs | 2 +- 19 files changed, 254 insertions(+), 382 deletions(-) delete mode 100644 crates/miden-objects/src/transaction/tx_witness.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d7bce0587..5666dc9850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,9 @@ - [BREAKING] Change the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). - [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). - Dynamically lookup all masm `EventId`s from source ([#1954](https://github.com/0xMiden/miden-base/pull/1954)). +- [BREAKING] Rename `TransactionInputs` to `TransactionExecutionInputs` and make a new `TransactionInputs` struct which does not contain `InputNotes` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). +- [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). +- [BREAKING] Refactor `TransactionInputs` and remove `TransactionWitness` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). ## 0.11.5 (2025-10-02) diff --git a/bin/bench-transaction/src/time_counting_benchmarks/prove.rs b/bin/bench-transaction/src/time_counting_benchmarks/prove.rs index b15c0fc70d..857fb57121 100644 --- a/bin/bench-transaction/src/time_counting_benchmarks/prove.rs +++ b/bin/bench-transaction/src/time_counting_benchmarks/prove.rs @@ -127,7 +127,7 @@ fn core_benchmarks(c: &mut Criterion) { fn prove_transaction(executed_transaction: ExecutedTransaction) -> Result<()> { let executed_transaction_id = executed_transaction.id(); let proven_transaction: ProvenTransaction = - LocalTransactionProver::default().prove(executed_transaction.into())?; + LocalTransactionProver::default().prove(executed_transaction)?; assert_eq!(proven_transaction.id(), executed_transaction_id); Ok(()) diff --git a/crates/miden-lib/src/transaction/inputs.rs b/crates/miden-lib/src/transaction/inputs.rs index 1a30c3d15e..a60b80f6c1 100644 --- a/crates/miden-lib/src/transaction/inputs.rs +++ b/crates/miden-lib/src/transaction/inputs.rs @@ -4,13 +4,7 @@ use miden_objects::account::{AccountHeader, AccountId, PartialAccount}; use miden_objects::block::AccountWitness; use miden_objects::crypto::SequentialCommit; use miden_objects::crypto::merkle::InnerNodeInfo; -use miden_objects::transaction::{ - AccountInputs, - InputNote, - PartialBlockchain, - TransactionArgs, - TransactionInputs, -}; +use miden_objects::transaction::{AccountInputs, InputNote, PartialBlockchain, TransactionInputs}; use miden_objects::vm::AdviceInputs; use miden_objects::{EMPTY_WORD, Felt, FieldElement, Word, ZERO}; use miden_processor::AdviceMutation; @@ -30,23 +24,17 @@ impl TransactionAdviceInputs { /// Creates a [`TransactionAdviceInputs`]. /// /// The created advice inputs will be populated with the data required for executing a - /// transaction with the specified inputs and arguments. This includes the initial account, an - /// optional account seed (required for new accounts), and the input note data, including - /// core note data + authentication paths all the way to the root of one of partial - /// blockchain peaks. - pub fn new( - tx_inputs: &TransactionInputs, - tx_args: &TransactionArgs, - ) -> Result { - let mut inputs = TransactionAdviceInputs::default(); + /// transaction with the specified transaction inputs. + pub fn new(tx_inputs: &TransactionInputs) -> Result { + let mut inputs = TransactionAdviceInputs(tx_inputs.advice_inputs().clone()); - inputs.build_stack(tx_inputs, tx_args); + inputs.build_stack(tx_inputs); inputs.add_kernel_commitment(); inputs.add_partial_blockchain(tx_inputs.blockchain()); - inputs.add_input_notes(tx_inputs, tx_args)?; + inputs.add_input_notes(tx_inputs)?; - // Add the script's MAST forest's advice inputs - if let Some(tx_script) = tx_args.tx_script() { + // Add the script's MAST forest's advice inputs. + if let Some(tx_script) = tx_inputs.tx_args().tx_script() { inputs.extend_map( tx_script .mast() @@ -56,23 +44,22 @@ impl TransactionAdviceInputs { ); } - // --- native account injection --------------------------------------- - + // Inject native account. let partial_native_acc = tx_inputs.account(); inputs.add_account(partial_native_acc)?; - // if a seed was provided, extend the map appropriately + // If a seed was provided, extend the map appropriately. if let Some(seed) = tx_inputs.account().seed() { // ACCOUNT_ID |-> ACCOUNT_SEED let account_id_key = Self::account_id_map_key(partial_native_acc.id()); inputs.add_map_entry(account_id_key, seed.to_vec()); } - // --- foreign account injection -------------------------------------- - inputs.add_foreign_accounts(tx_args.foreign_account_inputs())?; + // Inject foreign account inputs. + inputs.add_foreign_accounts(tx_inputs.tx_args().foreign_account_inputs())?; - // any extra user-supplied advice - inputs.extend(tx_args.advice_inputs().clone()); + // Extend with extra user-supplied advice. + inputs.extend(tx_inputs.tx_args().advice_inputs().clone()); Ok(inputs) } @@ -128,13 +115,6 @@ impl TransactionAdviceInputs { Ok(()) } - /// Returns the advice map key where: - /// - the seed for native accounts is stored. - /// - the account header for foreign accounts is stored. - pub fn account_id_map_key(id: AccountId) -> Word { - Word::from([id.suffix(), id.prefix().as_felt(), ZERO, ZERO]) - } - /// Extend the advice stack with the transaction inputs. /// /// The following data is pushed to the advice stack: @@ -161,7 +141,7 @@ impl TransactionAdviceInputs { /// TX_SCRIPT_ARGS, /// AUTH_ARGS, /// ] - fn build_stack(&mut self, tx_inputs: &TransactionInputs, tx_args: &TransactionArgs) { + fn build_stack(&mut self, tx_inputs: &TransactionInputs) { let header = tx_inputs.block_header(); // --- block header data (keep in sync with kernel's process_block_data) -- @@ -201,6 +181,7 @@ impl TransactionAdviceInputs { // --- number of notes, script root and args -------------------------- self.extend_stack([Felt::from(tx_inputs.input_notes().num_notes())]); + let tx_args = tx_inputs.tx_args(); self.extend_stack(tx_args.tx_script().map_or(Word::empty(), |script| script.root())); self.extend_stack(tx_args.tx_script_args()); @@ -340,7 +321,6 @@ impl TransactionAdviceInputs { fn add_input_notes( &mut self, tx_inputs: &TransactionInputs, - tx_args: &TransactionArgs, ) -> Result<(), TransactionAdviceMapMismatch> { if tx_inputs.input_notes().is_empty() { return Ok(()); @@ -351,7 +331,7 @@ impl TransactionAdviceInputs { let note = input_note.note(); let assets = note.assets(); let recipient = note.recipient(); - let note_arg = tx_args.get_note_args(note.id()).unwrap_or(&EMPTY_WORD); + let note_arg = tx_inputs.tx_args().get_note_args(note.id()).unwrap_or(&EMPTY_WORD); // recipient inputs / assets commitments self.add_map_entry( @@ -437,6 +417,13 @@ impl TransactionAdviceInputs { fn extend_merkle_store(&mut self, iter: impl Iterator) { self.0.store.extend(iter); } + + /// Returns the advice map key where: + /// - the seed for native accounts is stored. + /// - the account header for foreign accounts is stored. + fn account_id_map_key(id: AccountId) -> Word { + Word::from([id.suffix(), id.prefix().as_felt(), ZERO, ZERO]) + } } // CONVERSIONS diff --git a/crates/miden-lib/src/transaction/mod.rs b/crates/miden-lib/src/transaction/mod.rs index efe6bb19c4..0d47e08bd7 100644 --- a/crates/miden-lib/src/transaction/mod.rs +++ b/crates/miden-lib/src/transaction/mod.rs @@ -10,13 +10,7 @@ use miden_objects::assembly::{Assembler, DefaultSourceManager, KernelLibrary}; use miden_objects::asset::FungibleAsset; use miden_objects::block::BlockNumber; use miden_objects::crypto::SequentialCommit; -use miden_objects::transaction::{ - OutputNote, - OutputNotes, - TransactionArgs, - TransactionInputs, - TransactionOutputs, -}; +use miden_objects::transaction::{OutputNote, OutputNotes, TransactionInputs, TransactionOutputs}; use miden_objects::utils::serde::Deserializable; use miden_objects::utils::sync::LazyLock; use miden_objects::vm::{AdviceInputs, Program, ProgramInfo, StackInputs, StackOutputs}; @@ -125,14 +119,10 @@ impl TransactionKernel { ProgramInfo::new(program_hash, kernel) } - /// Transforms the provided [TransactionInputs] and [TransactionArgs] into stack and advice + /// Transforms the provided [`TransactionInputs`] into stack and advice /// inputs needed to execute a transaction kernel for a specific transaction. - /// - /// If `init_advice_inputs` is provided, they will be included in the returned advice inputs. pub fn prepare_inputs( tx_inputs: &TransactionInputs, - tx_args: &TransactionArgs, - init_advice_inputs: Option, ) -> Result<(StackInputs, TransactionAdviceInputs), TransactionAdviceMapMismatch> { let account = tx_inputs.account(); @@ -144,10 +134,7 @@ impl TransactionKernel { tx_inputs.block_header().block_num(), ); - let mut tx_advice_inputs = TransactionAdviceInputs::new(tx_inputs, tx_args)?; - if let Some(init_advice_inputs) = init_advice_inputs { - tx_advice_inputs.extend(init_advice_inputs); - } + let tx_advice_inputs = TransactionAdviceInputs::new(tx_inputs)?; Ok((stack_inputs, tx_advice_inputs)) } diff --git a/crates/miden-objects/src/transaction/executed_tx.rs b/crates/miden-objects/src/transaction/executed_tx.rs index d1eaf5cfc9..65084eb1fc 100644 --- a/crates/miden-objects/src/transaction/executed_tx.rs +++ b/crates/miden-objects/src/transaction/executed_tx.rs @@ -12,13 +12,12 @@ use super::{ OutputNotes, TransactionArgs, TransactionId, - TransactionInputs, TransactionOutputs, - TransactionWitness, }; -use crate::account::{AccountCode, PartialAccount}; +use crate::account::PartialAccount; use crate::asset::FungibleAsset; use crate::block::BlockNumber; +use crate::transaction::TransactionInputs; use crate::utils::serde::{ ByteReader, ByteWriter, @@ -46,9 +45,6 @@ pub struct ExecutedTransaction { tx_inputs: TransactionInputs, tx_outputs: TransactionOutputs, account_delta: AccountDelta, - tx_args: TransactionArgs, - foreign_account_code: Vec, - advice_witness: AdviceInputs, tx_measurements: TransactionMeasurements, } @@ -64,9 +60,6 @@ impl ExecutedTransaction { tx_inputs: TransactionInputs, tx_outputs: TransactionOutputs, account_delta: AccountDelta, - tx_args: TransactionArgs, - foreign_account_code: Vec, - advice_witness: AdviceInputs, tx_measurements: TransactionMeasurements, ) -> Self { // make sure account IDs are consistent across transaction inputs and outputs @@ -86,9 +79,6 @@ impl ExecutedTransaction { tx_inputs, tx_outputs, account_delta, - tx_args, - foreign_account_code, - advice_witness, tx_measurements, } } @@ -138,7 +128,7 @@ impl ExecutedTransaction { /// Returns a reference to the transaction arguments. pub fn tx_args(&self) -> &TransactionArgs { - &self.tx_args + self.tx_inputs.tx_args() } /// Returns the block header for the block against which the transaction was executed. @@ -159,7 +149,7 @@ impl ExecutedTransaction { /// Returns all the data requested by the VM from the advice provider while executing the /// transaction program. pub fn advice_witness(&self) -> &AdviceInputs { - &self.advice_witness + self.tx_inputs.advice_inputs() } /// Returns a reference to the transaction measurements which are the cycle counts for @@ -174,22 +164,14 @@ impl ExecutedTransaction { /// Returns individual components of this transaction. pub fn into_parts( self, - ) -> (AccountDelta, TransactionOutputs, TransactionWitness, TransactionMeasurements) { - let tx_witness = TransactionWitness { - tx_inputs: self.tx_inputs, - tx_args: self.tx_args, - foreign_account_code: self.foreign_account_code, - advice_witness: self.advice_witness, - }; - - (self.account_delta, self.tx_outputs, tx_witness, self.tx_measurements) + ) -> (TransactionInputs, TransactionOutputs, AccountDelta, TransactionMeasurements) { + (self.tx_inputs, self.tx_outputs, self.account_delta, self.tx_measurements) } } -impl From for TransactionWitness { +impl From for TransactionInputs { fn from(tx: ExecutedTransaction) -> Self { - let (_, _, tx_witness, _) = tx.into_parts(); - tx_witness + tx.tx_inputs } } @@ -205,9 +187,6 @@ impl Serializable for ExecutedTransaction { self.tx_inputs.write_into(target); self.tx_outputs.write_into(target); self.account_delta.write_into(target); - self.tx_args.write_into(target); - self.foreign_account_code.write_into(target); - self.advice_witness.write_into(target); self.tx_measurements.write_into(target); } } @@ -217,20 +196,9 @@ impl Deserializable for ExecutedTransaction { let tx_inputs = TransactionInputs::read_from(source)?; let tx_outputs = TransactionOutputs::read_from(source)?; let account_delta = AccountDelta::read_from(source)?; - let tx_args = TransactionArgs::read_from(source)?; - let foreign_account_code = >::read_from(source)?; - let advice_witness = AdviceInputs::read_from(source)?; let tx_measurements = TransactionMeasurements::read_from(source)?; - Ok(Self::new( - tx_inputs, - tx_outputs, - account_delta, - tx_args, - foreign_account_code, - advice_witness, - tx_measurements, - )) + Ok(Self::new(tx_inputs, tx_outputs, account_delta, tx_measurements)) } } diff --git a/crates/miden-objects/src/transaction/inputs/mod.rs b/crates/miden-objects/src/transaction/inputs/mod.rs index 8ea70ef7cf..fe36c29a62 100644 --- a/crates/miden-objects/src/transaction/inputs/mod.rs +++ b/crates/miden-objects/src/transaction/inputs/mod.rs @@ -1,22 +1,20 @@ +use alloc::vec::Vec; use core::fmt::Debug; +use miden_core::utils::{Deserializable, Serializable}; + use super::PartialBlockchain; use crate::TransactionInputError; -use crate::account::PartialAccount; -use crate::block::BlockHeader; +use crate::account::{AccountCode, PartialAccount}; +use crate::block::{BlockHeader, BlockNumber}; use crate::note::{Note, NoteInclusionProof}; -use crate::utils::serde::{ - ByteReader, - ByteWriter, - Deserializable, - DeserializationError, - Serializable, -}; +use crate::transaction::{TransactionArgs, TransactionScript}; mod account; pub use account::AccountInputs; mod notes; +use miden_processor::AdviceInputs; pub use notes::{InputNote, InputNotes, ToInputNoteCommitments}; // TRANSACTION INPUTS @@ -29,6 +27,9 @@ pub struct TransactionInputs { block_header: BlockHeader, blockchain: PartialBlockchain, input_notes: InputNotes, + tx_args: TransactionArgs, + advice_inputs: AdviceInputs, + foreign_account_code: Vec, } impl TransactionInputs { @@ -40,17 +41,15 @@ impl TransactionInputs { /// # Errors /// /// Returns an error if: - /// - The partial blockchain's length is not the number of the reference block. - /// - The partial blockchain's commitment does not match the reference block's chain commitment. - /// - The partial blockchain does not proof inclusion of an authenticated input note. + /// - The partial blockchain does not track the block headers required to prove inclusion of any + /// authenticated input note. pub fn new( - partial_account: impl Into, + account: PartialAccount, block_header: BlockHeader, blockchain: PartialBlockchain, input_notes: InputNotes, ) -> Result { // Check that the partial blockchain and block header are consistent. - let block_num = block_header.block_num(); if blockchain.chain_length() != block_header.block_num() { return Err(TransactionInputError::InconsistentChainLength { expected: block_header.block_num(), @@ -63,12 +62,11 @@ impl TransactionInputs { actual: blockchain.peaks().hash_peaks(), }); } - // Validate the authentication paths of the input notes. for note in input_notes.iter() { if let InputNote::Authenticated { note, proof } = note { let note_block_num = proof.location().block_num(); - let block_header = if note_block_num == block_num { + let block_header = if note_block_num == block_header.block_num() { &block_header } else { blockchain.get_block(note_block_num).ok_or( @@ -80,22 +78,51 @@ impl TransactionInputs { } Ok(Self { - account: partial_account.into(), + account, block_header, blockchain, input_notes, + tx_args: TransactionArgs::default(), + advice_inputs: AdviceInputs::default(), + foreign_account_code: Vec::new(), }) } - /// Updates the input notes for the transaction. - /// - /// # Warning - /// - /// This method does not validate the notes against the data already in this - /// [`TransactionInputs`]. It should only be called with notes that have been validated by - /// the constructor. - pub fn set_input_notes_unchecked(&mut self, new_notes: InputNotes) { - self.input_notes = new_notes; + /// Replaces the transaction inputs and assigns the given foreign account code. + pub fn with_foreign_account_code(mut self, foreign_account_code: Vec) -> Self { + self.foreign_account_code = foreign_account_code; + self + } + + /// Replaces the transaction inputs and assigns the given transaction arguments. + pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self { + self.tx_args = tx_args; + self + } + + /// Replaces the transaction inputs and assigns the given advice inputs. + pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self { + self.advice_inputs = advice_inputs; + self + } + + // MUTATORS + // -------------------------------------------------------------------------------------------- + + /// Replaces the input notes for the transaction. + pub fn set_input_notes(&mut self, new_notes: Vec) { + self.input_notes = new_notes.into(); + } + + /// Replaces the advice inputs for the transaction. + pub fn set_advice_inputs(&mut self, new_advice_inputs: AdviceInputs) { + self.advice_inputs = new_advice_inputs; + } + + /// Updates the transaction arguments of the inputs. + #[cfg(feature = "testing")] + pub fn set_tx_args(&mut self, tx_args: TransactionArgs) { + self.tx_args = tx_args; } // PUBLIC ACCESSORS @@ -122,35 +149,86 @@ impl TransactionInputs { &self.input_notes } + /// Returns the block number referenced by the inputs. + pub fn ref_block(&self) -> BlockNumber { + self.block_header.block_num() + } + + /// Returns the transaction script to be executed. + pub fn tx_script(&self) -> Option<&TransactionScript> { + self.tx_args.tx_script() + } + + /// Returns the foreign account code to be executed. + pub fn foreign_account_code(&self) -> &[AccountCode] { + &self.foreign_account_code + } + + /// Returns the foreign account inputs to be consumed in the transaction. + pub fn foreign_account_inputs(&self) -> &[AccountInputs] { + self.tx_args.foreign_account_inputs() + } + + /// Returns the advice inputs to be consumed in the transaction. + pub fn advice_inputs(&self) -> &AdviceInputs { + &self.advice_inputs + } + + /// Returns the transaction arguments to be consumed in the transaction. + pub fn tx_args(&self) -> &TransactionArgs { + &self.tx_args + } + // CONVERSIONS // -------------------------------------------------------------------------------------------- /// Consumes these transaction inputs and returns their underlying components. pub fn into_parts( self, - ) -> (PartialAccount, BlockHeader, PartialBlockchain, InputNotes) { - (self.account, self.block_header, self.blockchain, self.input_notes) + ) -> ( + PartialAccount, + BlockHeader, + PartialBlockchain, + InputNotes, + TransactionArgs, + ) { + (self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args) } } impl Serializable for TransactionInputs { - fn write_into(&self, target: &mut W) { + fn write_into(&self, target: &mut W) { self.account.write_into(target); self.block_header.write_into(target); self.blockchain.write_into(target); self.input_notes.write_into(target); + self.tx_args.write_into(target); + self.advice_inputs.write_into(target); + self.foreign_account_code.write_into(target); } } impl Deserializable for TransactionInputs { - fn read_from(source: &mut R) -> Result { - let partial_account = PartialAccount::read_from(source)?; + fn read_from( + source: &mut R, + ) -> Result { + let account = PartialAccount::read_from(source)?; let block_header = BlockHeader::read_from(source)?; let blockchain = PartialBlockchain::read_from(source)?; let input_notes = InputNotes::read_from(source)?; + let tx_args = TransactionArgs::read_from(source)?; + let advice_inputs = AdviceInputs::read_from(source)?; + let foreign_account_code = Vec::::read_from(source)?; - Self::new(partial_account, block_header, blockchain, input_notes) - .map_err(|err| DeserializationError::InvalidValue(format!("{err}"))) + Ok(TransactionInputs { + account, + block_header, + blockchain, + input_notes, + tx_args, + advice_inputs, + foreign_account_code, + }) } } diff --git a/crates/miden-objects/src/transaction/mod.rs b/crates/miden-objects/src/transaction/mod.rs index da9531d128..b11954de1a 100644 --- a/crates/miden-objects/src/transaction/mod.rs +++ b/crates/miden-objects/src/transaction/mod.rs @@ -14,7 +14,6 @@ mod transaction_id; mod tx_args; mod tx_header; mod tx_summary; -mod tx_witness; pub use executed_tx::{ExecutedTransaction, TransactionMeasurements}; pub use inputs::{AccountInputs, InputNote, InputNotes, ToInputNoteCommitments, TransactionInputs}; @@ -31,4 +30,3 @@ pub use transaction_id::TransactionId; pub use tx_args::{TransactionArgs, TransactionScript}; pub use tx_header::TransactionHeader; pub use tx_summary::TransactionSummary; -pub use tx_witness::TransactionWitness; diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index cd5fa54918..7242eddf8b 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -234,7 +234,6 @@ impl TransactionArgs { } /// Extends the advice inputs in self with the provided ones. - #[cfg(feature = "testing")] pub fn extend_advice_inputs(&mut self, advice_inputs: AdviceInputs) { self.advice_inputs.extend(advice_inputs); } diff --git a/crates/miden-objects/src/transaction/tx_witness.rs b/crates/miden-objects/src/transaction/tx_witness.rs deleted file mode 100644 index 938656a253..0000000000 --- a/crates/miden-objects/src/transaction/tx_witness.rs +++ /dev/null @@ -1,134 +0,0 @@ -use alloc::vec::Vec; - -use super::{AdviceInputs, TransactionArgs, TransactionInputs}; -use crate::account::AccountCode; -use crate::utils::serde::{ByteReader, Deserializable, DeserializationError, Serializable}; - -// TRANSACTION WITNESS -// ================================================================================================ - -/// Transaction witness contains all the data required to execute and prove a Miden blockchain -/// transaction. -/// -/// The main purpose of the transaction witness is to enable stateless re-execution and proving -/// of transactions. -/// -/// A transaction witness consists of: -/// - Transaction inputs which contain information about the initial state of the account, input -/// notes, block header etc. -/// - Optional transaction arguments which may contain a transaction script, note arguments, -/// transaction script arguments and any additional advice data to initialize the advice provider -/// with prior to transaction execution. -/// - Account code needed for invoking procedures on foreign accounts. -/// - Advice witness which contains all data that is in the advice provider by the end of the -/// transaction execution. -/// -/// TODO: currently, the advice witness contains redundant and irrelevant data (e.g., tx inputs -/// and tx outputs; account codes and a subset of that data in advice inputs). -/// We should optimize it to contain only the minimum data required for executing/proving the -/// transaction. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TransactionWitness { - pub tx_inputs: TransactionInputs, - pub tx_args: TransactionArgs, - pub foreign_account_code: Vec, - pub advice_witness: AdviceInputs, -} - -// SERIALIZATION -// ================================================================================================ - -impl Serializable for TransactionWitness { - fn write_into(&self, target: &mut W) { - self.tx_inputs.write_into(target); - self.tx_args.write_into(target); - self.foreign_account_code.write_into(target); - self.advice_witness.write_into(target); - } -} - -impl Deserializable for TransactionWitness { - fn read_from(source: &mut R) -> Result { - let tx_inputs = TransactionInputs::read_from(source)?; - let tx_args = TransactionArgs::read_from(source)?; - let foreign_account_code = >::read_from(source)?; - let advice_witness = AdviceInputs::read_from(source)?; - - Ok(Self { - tx_inputs, - tx_args, - foreign_account_code, - advice_witness, - }) - } -} - -#[cfg(test)] -mod tests { - use anyhow::Context; - use miden_crypto::Word; - - use crate::account::{AccountBuilder, AccountComponent, StorageSlot}; - use crate::assembly::Assembler; - use crate::asset::FungibleAsset; - use crate::block::{BlockHeader, BlockNumber}; - use crate::testing::noop_auth_component::NoopAuthComponent; - use crate::transaction::{ - InputNotes, - PartialBlockchain, - TransactionArgs, - TransactionInputs, - TransactionWitness, - }; - use crate::vm::AdviceInputs; - - #[test] - fn transaction_witness_serialization_roundtrip() -> anyhow::Result<()> { - use crate::utils::serde::{Deserializable, Serializable}; - - let component = AccountComponent::compile( - "export.foo add.1 end", - Assembler::default(), - vec![StorageSlot::Value(Word::empty())], - )? - .with_supports_all_types(); - let asset = FungibleAsset::mock(200); - let account = AccountBuilder::new([1; 32]) - .with_auth_component(NoopAuthComponent) - .with_component(component) - .with_assets([asset]) - .build_existing()?; - - let partial_blockchain = PartialBlockchain::default(); - let block_header = BlockHeader::mock( - BlockNumber::GENESIS, - Some(partial_blockchain.peaks().hash_peaks()), - None, - &[], - Word::empty(), - ); - - let tx_inputs = TransactionInputs::new( - &account, - block_header.clone(), - partial_blockchain.clone(), - InputNotes::default(), - ) - .unwrap(); - - let witness = TransactionWitness { - tx_inputs, - tx_args: TransactionArgs::default(), - foreign_account_code: vec![account.code().clone()], - advice_witness: AdviceInputs::default(), - }; - - let bytes = witness.to_bytes(); - let deserialized = TransactionWitness::read_from_bytes(&bytes) - .context("failed to deserialize tx witness")?; - - assert_eq!(witness, deserialized); - - Ok(()) - } -} diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index 94634d29e3..3c9a3484fd 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -991,7 +991,7 @@ async fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { .await?; // TODO: Remove later and add a integration test using FPI. - LocalTransactionProver::default().prove(executed_transaction.into())?; + LocalTransactionProver::default().prove(executed_transaction)?; Ok(()) } diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index d6dbe5e7c9..2f91a6f277 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -606,8 +606,8 @@ impl MockChain { // INPUTS APIS // ---------------------------------------------------------------------------------------- - /// Returns a valid [`TransactionInputs`] for the specified entities, executing against a - /// specific block number. + /// Returns a valid [`TransactionInputs`] for the specified entities, executing against + /// a specific block number. pub fn get_transaction_inputs_at( &self, reference_block: BlockNumber, @@ -668,7 +668,7 @@ impl MockChain { let input_notes = InputNotes::new(input_notes)?; Ok(TransactionInputs::new( - account, + account.into(), ref_block.clone(), partial_blockchain, input_notes, diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 8d1880af57..aff7902ecd 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -85,7 +85,7 @@ pub struct TransactionContextBuilder { tx_script: Option, tx_script_args: Word, note_args: BTreeMap, - transaction_inputs: Option, + tx_inputs: Option, auth_args: Word, signatures: Vec<(PublicKeyCommitment, Word, Signature)>, is_lazy_loading_enabled: bool, @@ -102,7 +102,7 @@ impl TransactionContextBuilder { tx_script_args: EMPTY_WORD, authenticator: None, advice_inputs: Default::default(), - transaction_inputs: None, + tx_inputs: None, note_args: BTreeMap::new(), foreign_account_inputs: BTreeMap::new(), auth_args: EMPTY_WORD, @@ -206,7 +206,7 @@ impl TransactionContextBuilder { tx_inputs.account().into(), "account in context and account provided via tx inputs are not the same account" ); - self.transaction_inputs = Some(tx_inputs); + self.tx_inputs = Some(tx_inputs); self } @@ -264,7 +264,7 @@ impl TransactionContextBuilder { /// If no transaction inputs were provided manually, an ad-hoc MockChain is created in order /// to generate valid block data for the required notes. pub fn build(self) -> anyhow::Result { - let tx_inputs = match self.transaction_inputs { + let tx_inputs = match self.tx_inputs { Some(tx_inputs) => tx_inputs, None => { // If no specific transaction inputs was provided, initialize an ad-hoc mockchain @@ -288,20 +288,6 @@ impl TransactionContextBuilder { }, }; - // If partial loading is enabled, construct an account that doesn't contain all - // merkle paths of assets and storage maps, in order to test lazy loading. - // Otherwise, load the full account. - let tx_inputs = if self.is_lazy_loading_enabled { - let (_account, block_header, partial_blockchain, input_notes) = tx_inputs.into_parts(); - // Note that we use self.account instead of account, because we cannot do the same - // operation on a partial vault. - let account = minimal_partial_account(&self.account)?; - - TransactionInputs::new(account, block_header, partial_blockchain, input_notes)? - } else { - tx_inputs - }; - let foreign_account_inputs = if self.is_lazy_loading_enabled { Vec::new() } else { @@ -310,15 +296,12 @@ impl TransactionContextBuilder { let tx_args = TransactionArgs::new(AdviceMap::default(), foreign_account_inputs) .with_note_args(self.note_args); - let mut tx_args = if let Some(tx_script) = self.tx_script { tx_args.with_tx_script_and_args(tx_script, self.tx_script_args) } else { tx_args }; - tx_args = tx_args.with_auth_args(self.auth_args); - tx_args.extend_advice_inputs(self.advice_inputs.clone()); tx_args.extend_output_note_recipients(self.expected_output_notes.clone()); @@ -326,6 +309,20 @@ impl TransactionContextBuilder { tx_args.add_signature(public_key_commitment, message, signature); } + // If partial loading is enabled, construct an account that doesn't contain all + // merkle paths of assets and storage maps, in order to test lazy loading. + // Otherwise, load the full account. + let tx_inputs = if self.is_lazy_loading_enabled { + let (_, block_header, partial_blockchain, input_notes, _) = tx_inputs.into_parts(); + // Note that we use self.account instead of account, because we cannot do the same + // operation on a partial vault. + let account = minimal_partial_account(&self.account)?; + TransactionInputs::new(account, block_header, partial_blockchain, input_notes)? + .with_tx_args(tx_args) + } else { + tx_inputs.with_tx_args(tx_args) + }; + let mast_store = { let mast_forest_store = TransactionMastStore::new(); mast_forest_store.load_account_code(tx_inputs.account().code()); @@ -341,11 +338,9 @@ impl TransactionContextBuilder { account: self.account, expected_output_notes: self.expected_output_notes, foreign_account_inputs: self.foreign_account_inputs, - tx_args, tx_inputs, mast_store, authenticator: self.authenticator, - advice_inputs: self.advice_inputs, source_manager: self.source_manager, }) } diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index eee3098410..4725474d14 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -21,7 +21,6 @@ use miden_objects::transaction::{ TransactionInputs, }; use miden_processor::{ - AdviceInputs, ExecutionError, FutureMaybeSend, MastForest, @@ -54,10 +53,8 @@ pub struct TransactionContext { pub(super) account: Account, pub(super) expected_output_notes: Vec, pub(super) foreign_account_inputs: BTreeMap, - pub(super) tx_args: TransactionArgs, pub(super) tx_inputs: TransactionInputs, pub(super) mast_store: TransactionMastStore, - pub(super) advice_inputs: AdviceInputs, pub(super) authenticator: Option, pub(super) source_manager: Arc, } @@ -83,12 +80,8 @@ impl TransactionContext { /// /// - If the provided `code` is not a valid program. pub fn execute_code(&self, code: &str) -> Result { - let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs( - &self.tx_inputs, - &self.tx_args, - Some(self.advice_inputs.clone()), - ) - .expect("error initializing transaction inputs"); + let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&self.tx_inputs) + .expect("error initializing transaction inputs"); // Virtual file name should be unique. let virtual_source_file = self.source_manager.load( @@ -109,7 +102,7 @@ impl TransactionContext { mast_store.insert(program.mast_forest().clone()); mast_store.insert(TransactionKernel::library().mast_forest().clone()); mast_store.load_account_code(self.account().code()); - for acc_inputs in self.tx_args.foreign_account_inputs() { + for acc_inputs in self.tx_inputs.tx_args().foreign_account_inputs() { mast_store.load_account_code(acc_inputs.code()); } @@ -118,7 +111,7 @@ impl TransactionContext { MockHost::new( self.tx_inputs().account().code(), mast_store, - self.tx_args.foreign_account_inputs(), + self.tx_inputs().tx_args().foreign_account_inputs(), ) .with_source_manager(self.source_manager()), ) @@ -153,7 +146,7 @@ impl TransactionContext { } pub fn tx_args(&self) -> &TransactionArgs { - &self.tx_args + self.tx_inputs.tx_args() } pub fn input_notes(&self) -> &InputNotes { @@ -161,7 +154,7 @@ impl TransactionContext { } pub fn set_tx_args(&mut self, tx_args: TransactionArgs) { - self.tx_args = tx_args; + self.tx_inputs.set_tx_args(tx_args); } pub fn tx_inputs(&self) -> &TransactionInputs { @@ -188,9 +181,10 @@ impl DataStore for TransactionContext { assert_eq!(account_id, self.account().id()); assert_eq!(account_id, self.tx_inputs.account().id()); - let (partial_account, header, mmr, _) = self.tx_inputs.clone().into_parts(); - - async move { Ok((partial_account, header, mmr)) } + let account = self.tx_inputs.account().clone(); + let block_header = self.tx_inputs.block_header().clone(); + let blockchain = self.tx_inputs.blockchain().clone(); + async move { Ok((account, block_header, blockchain)) } } fn get_foreign_account_inputs( diff --git a/crates/miden-testing/tests/lib.rs b/crates/miden-testing/tests/lib.rs index f2b568159f..c5992e03d5 100644 --- a/crates/miden-testing/tests/lib.rs +++ b/crates/miden-testing/tests/lib.rs @@ -32,7 +32,7 @@ pub fn prove_and_verify_transaction( let proof_options = ProvingOptions::default(); let prover = LocalTransactionProver::new(proof_options); - let proven_transaction = prover.prove(executed_transaction.into()).unwrap(); + let proven_transaction = prover.prove(executed_transaction).unwrap(); assert_eq!(proven_transaction.id(), executed_transaction_id); diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index db79fe95d2..7d31f13066 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -182,11 +182,9 @@ where notes: InputNotes, tx_args: TransactionArgs, ) -> Result { - let tx_inputs = - self.prepare_transaction_inputs(account_id, block_ref, notes, &tx_args).await?; + let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?; - let (mut host, stack_inputs, advice_inputs) = - self.prepare_transaction(&tx_inputs, &tx_args, None).await?; + let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?; let processor = FastProcessor::new_debug(stack_inputs.as_slice(), advice_inputs); let output = processor @@ -204,7 +202,7 @@ where ..Default::default() }; - build_executed_transaction(advice_inputs, tx_args, tx_inputs, stack_outputs, host) + build_executed_transaction(advice_inputs, tx_inputs, stack_outputs, host) } // SCRIPT EXECUTION @@ -226,15 +224,14 @@ where advice_inputs: AdviceInputs, foreign_account_inputs: Vec, ) -> Result<[Felt; 16], TransactionExecutorError> { - let tx_args = TransactionArgs::new(Default::default(), foreign_account_inputs) + let mut tx_args = TransactionArgs::new(Default::default(), foreign_account_inputs) .with_tx_script(tx_script); + tx_args.extend_advice_inputs(advice_inputs); let notes = InputNotes::default(); - let tx_inputs = - self.prepare_transaction_inputs(account_id, block_ref, notes, &tx_args).await?; + let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?; - let (mut host, stack_inputs, advice_inputs) = - self.prepare_transaction(&tx_inputs, &tx_args, Some(advice_inputs)).await?; + let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?; let processor = FastProcessor::new_with_advice_inputs(stack_inputs.as_slice(), advice_inputs); @@ -255,26 +252,27 @@ where // This method has a one-to-many call relationship with the `prepare_transaction` method. This // method needs to be called only once in order to allow many transactions to be prepared based // on the transaction inputs returned by this method. - async fn prepare_transaction_inputs( + async fn prepare_tx_inputs( &self, account_id: AccountId, block_ref: BlockNumber, input_notes: InputNotes, - tx_args: &TransactionArgs, + tx_args: TransactionArgs, ) -> Result { let mut ref_blocks = validate_input_notes(&input_notes, block_ref)?; ref_blocks.insert(block_ref); - let (account, ref_block, mmr) = self + let (account, block_header, blockchain) = self .data_store .get_transaction_inputs(account_id, ref_blocks) .await .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?; - validate_account_inputs(tx_args, &ref_block)?; + validate_account_inputs(&tx_args, &block_header)?; - let tx_inputs = TransactionInputs::new(account, ref_block, mmr, input_notes) - .map_err(TransactionExecutorError::InvalidTransactionInputs)?; + let tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes) + .map_err(TransactionExecutorError::InvalidTransactionInputs)? + .with_tx_args(tx_args); Ok(tx_inputs) } @@ -286,15 +284,12 @@ where async fn prepare_transaction( &self, tx_inputs: &TransactionInputs, - tx_args: &TransactionArgs, - init_advice_inputs: Option, ) -> Result< (TransactionExecutorHost<'store, 'auth, STORE, AUTH>, StackInputs, AdviceInputs), TransactionExecutorError, > { - let (stack_inputs, tx_advice_inputs) = - TransactionKernel::prepare_inputs(tx_inputs, tx_args, init_advice_inputs) - .map_err(TransactionExecutorError::ConflictingAdviceMapEntry)?; + let (stack_inputs, tx_advice_inputs) = TransactionKernel::prepare_inputs(tx_inputs) + .map_err(TransactionExecutorError::ConflictingAdviceMapEntry)?; // This reverses the stack inputs (even though it doesn't look like it does) because the // fast processor expects the reverse order. @@ -306,7 +301,7 @@ where let input_notes = tx_inputs.input_notes(); let script_mast_store = ScriptMastForestStore::new( - tx_args.tx_script(), + tx_inputs.tx_script(), input_notes.iter().map(|n| n.note().script()), ); @@ -339,7 +334,6 @@ where /// Creates a new [ExecutedTransaction] from the provided data. fn build_executed_transaction( mut advice_inputs: AdviceInputs, - tx_args: TransactionArgs, tx_inputs: TransactionInputs, stack_outputs: StackOutputs, host: TransactionExecutorHost, @@ -384,7 +378,7 @@ fn build_executed_transaction Result { // TODO: apply the static analysis before executing the tx - // prepare the transaction inputs - let tx_inputs = self + // Prepare transaction inputs. + let mut tx_inputs = self .0 - .prepare_transaction_inputs( + .prepare_tx_inputs( target_account_id, block_ref, InputNotes::new_unchecked(vec![note]), - &tx_args, + tx_args, ) .await .map_err(NoteCheckerError::TransactionPreparation)?; // try to consume the provided note - match self.try_execute_notes(&tx_inputs, &tx_args).await { + match self.try_execute_notes(&mut tx_inputs).await { // execution succeeded Ok(()) => Ok(NoteConsumptionStatus::Consumable), Err(tx_checker_error) => { @@ -197,7 +198,6 @@ where async fn find_executable_notes_by_elimination( &self, mut tx_inputs: TransactionInputs, - tx_args: TransactionArgs, ) -> Result { let mut candidate_notes = tx_inputs .input_notes() @@ -211,8 +211,8 @@ where // further reduced. loop { // Execute the candidate notes. - tx_inputs.set_input_notes_unchecked(candidate_notes.clone().into()); - match self.try_execute_notes(&tx_inputs, &tx_args).await { + tx_inputs.set_input_notes(candidate_notes.clone()); + match self.try_execute_notes(&mut tx_inputs).await { Ok(()) => { // A full set of successful notes has been found. let successful = candidate_notes; @@ -235,7 +235,6 @@ where candidate_notes, failed_notes, tx_inputs, - &tx_args, ) .await; return Ok(consumption_info); @@ -261,7 +260,6 @@ where mut remaining_notes: Vec, mut failed_notes: Vec, mut tx_inputs: TransactionInputs, - tx_args: &TransactionArgs, ) -> NoteConsumptionInfo { let mut successful_notes = Vec::new(); let mut failed_note_index = BTreeMap::new(); @@ -277,8 +275,8 @@ where for (idx, note) in remaining_notes.iter().enumerate() { successful_notes.push(note.clone()); - tx_inputs.set_input_notes_unchecked(successful_notes.clone().into()); - match self.try_execute_notes(&tx_inputs, tx_args).await { + tx_inputs.set_input_notes(successful_notes.clone()); + match self.try_execute_notes(&mut tx_inputs).await { Ok(()) => { // The successfully added note might have failed earlier. Remove it from the // failed list. @@ -314,18 +312,17 @@ where /// or a specific [`NoteExecutionError`] indicating where and why the execution failed. async fn try_execute_notes( &self, - tx_inputs: &TransactionInputs, - tx_args: &TransactionArgs, + tx_inputs: &mut TransactionInputs, ) -> Result<(), TransactionCheckerError> { if tx_inputs.input_notes().is_empty() { return Ok(()); } - let (mut host, stack_inputs, advice_inputs) = self - .0 - .prepare_transaction(tx_inputs, tx_args, None) - .await - .map_err(TransactionCheckerError::TransactionPreparation)?; + let (mut host, stack_inputs, advice_inputs) = + self.0 + .prepare_transaction(tx_inputs) + .await + .map_err(TransactionCheckerError::TransactionPreparation)?; let processor = FastProcessor::new_with_advice_inputs(stack_inputs.as_slice(), advice_inputs); @@ -335,7 +332,19 @@ where .map_err(map_execution_error); match result { - Ok(_) => Ok(()), + Ok(execution_output) => { + // Set the advice inputs from the successful execution as advice inputs for + // reexecution. This avoids calls to the data store (to load data lazily) that have + // already been done as part of this execution. + let (_, advice_map, merkle_store) = execution_output.advice.into_parts(); + let advice_inputs = AdviceInputs { + map: advice_map, + store: merkle_store, + ..Default::default() + }; + tx_inputs.set_advice_inputs(advice_inputs); + Ok(()) + }, Err(error) => { let notes = host.tx_progress().note_execution(); @@ -403,7 +412,7 @@ pub enum NoteConsumptionStatus { /// The note can be consumed by the account if proper authorization is provided. ConsumableWithAuthorization, /// The note cannot be consumed by the account at the specified conditions (i.e., block - /// height and account state). + /// height and account state). Unconsumable, /// The note cannot be consumed by the specified account under any conditions. Incompatible, diff --git a/crates/miden-tx/src/lib.rs b/crates/miden-tx/src/lib.rs index 942cdd6b17..4fe695d179 100644 --- a/crates/miden-tx/src/lib.rs +++ b/crates/miden-tx/src/lib.rs @@ -6,8 +6,6 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; -pub use miden_objects::transaction::TransactionInputs; - mod executor; pub use executor::{ DataStore, diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index 113683b261..3e0eadb6fa 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -24,8 +24,8 @@ use miden_objects::transaction::{ OutputNote, ProvenTransaction, ProvenTransactionBuilder, + TransactionInputs, TransactionOutputs, - TransactionWitness, }; pub use miden_prover::ProvingOptions; use miden_prover::{ExecutionProof, Word, prove}; @@ -118,35 +118,28 @@ impl LocalTransactionProver { pub fn prove( &self, - tx_witness: TransactionWitness, + tx_inputs: impl Into, ) -> Result { - let TransactionWitness { - tx_inputs, - tx_args, - foreign_account_code, - advice_witness, - } = tx_witness; - - let (stack_inputs, advice_inputs) = - TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_witness)) - .map_err(TransactionProverError::ConflictingAdviceMapEntry)?; + let tx_inputs = tx_inputs.into(); + let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs) + .map_err(TransactionProverError::ConflictingAdviceMapEntry)?; self.mast_store.load_account_code(tx_inputs.account().code()); - for account_code in &foreign_account_code { + for account_code in tx_inputs.foreign_account_code() { self.mast_store.load_account_code(account_code); } let script_mast_store = ScriptMastForestStore::new( - tx_args.tx_script(), + tx_inputs.tx_script(), tx_inputs.input_notes().iter().map(|n| n.note().script()), ); let account_procedure_index_map = AccountProcedureIndexMap::new( - foreign_account_code.iter().chain([tx_inputs.account().code()]), + tx_inputs.foreign_account_code().iter().chain([tx_inputs.account().code()]), ) .map_err(TransactionProverError::CreateAccountProcedureIndexMap)?; - let (partial_account, ref_block, _, input_notes) = tx_inputs.into_parts(); + let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts(); let mut host = TransactionProverHost::new( &partial_account, input_notes, @@ -248,11 +241,11 @@ fn partial_storage_map_to_storage_map( impl LocalTransactionProver { pub fn prove_dummy( &self, - executed_tx: ExecutedTransaction, + executed_transaction: ExecutedTransaction, ) -> Result { - let (account_delta, tx_outputs, tx_witness, _) = executed_tx.into_parts(); + let (tx_inputs, tx_outputs, account_delta, _) = executed_transaction.into_parts(); - let (partial_account, ref_block, _, input_notes) = tx_witness.tx_inputs.into_parts(); + let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts(); self.build_proven_transaction( &input_notes, diff --git a/crates/miden-tx/src/verifier/mod.rs b/crates/miden-tx/src/verifier/mod.rs index f9f51a10b5..396e92dd85 100644 --- a/crates/miden-tx/src/verifier/mod.rs +++ b/crates/miden-tx/src/verifier/mod.rs @@ -25,7 +25,7 @@ impl TransactionVerifier { Self { tx_program_info, proof_security_level } } - /// Verifies the provided [ProvenTransaction] against the transaction kernel. + /// Verifies the provided [`ProvenTransaction`] against the transaction kernel. /// /// # Errors /// Returns an error if: From 2ff393921e3dce9fe7d1888a644fa21b3e8409fd Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Thu, 9 Oct 2025 14:07:58 +0300 Subject: [PATCH 081/133] Add `get_note_script()` to `DataStore` (#1981) --- .../miden-testing/src/tx_context/builder.rs | 11 ++- .../miden-testing/src/tx_context/context.rs | 74 ++++++++++++++++++- crates/miden-tx/src/errors/mod.rs | 2 + crates/miden-tx/src/executor/data_store.rs | 15 ++++ 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index aff7902ecd..950e3083e7 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -23,7 +23,7 @@ use miden_objects::account::{ use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::debuginfo::SourceManagerSync; use miden_objects::asset::PartialVault; -use miden_objects::note::{Note, NoteId}; +use miden_objects::note::{Note, NoteId, NoteScript}; use miden_objects::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; use miden_objects::testing::noop_auth_component::NoopAuthComponent; use miden_objects::transaction::{ @@ -89,6 +89,7 @@ pub struct TransactionContextBuilder { auth_args: Word, signatures: Vec<(PublicKeyCommitment, Word, Signature)>, is_lazy_loading_enabled: bool, + note_scripts: BTreeMap, } impl TransactionContextBuilder { @@ -108,6 +109,7 @@ impl TransactionContextBuilder { auth_args: EMPTY_WORD, signatures: Vec::new(), is_lazy_loading_enabled: false, + note_scripts: BTreeMap::new(), } } @@ -259,6 +261,12 @@ impl TransactionContextBuilder { self } + /// Add a note script to the context for testing. + pub fn add_note_script(mut self, script: NoteScript) -> Self { + self.note_scripts.insert(script.root(), script); + self + } + /// Builds the [TransactionContext]. /// /// If no transaction inputs were provided manually, an ad-hoc MockChain is created in order @@ -342,6 +350,7 @@ impl TransactionContextBuilder { mast_store, authenticator: self.authenticator, source_manager: self.source_manager, + note_scripts: self.note_scripts, }) } } diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index 4725474d14..227f610145 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -10,7 +10,7 @@ use miden_objects::assembly::debuginfo::{SourceLanguage, Uri}; use miden_objects::assembly::{SourceManager, SourceManagerSync}; use miden_objects::asset::AssetWitness; use miden_objects::block::{BlockHeader, BlockNumber}; -use miden_objects::note::Note; +use miden_objects::note::{Note, NoteScript}; use miden_objects::transaction::{ AccountInputs, ExecutedTransaction, @@ -57,6 +57,7 @@ pub struct TransactionContext { pub(super) mast_store: TransactionMastStore, pub(super) authenticator: Option, pub(super) source_manager: Arc, + pub(super) note_scripts: BTreeMap, } impl TransactionContext { @@ -314,6 +315,18 @@ impl DataStore for TransactionContext { } } } + + fn get_note_script( + &self, + script_root: Word, + ) -> impl FutureMaybeSend> { + async move { + self.note_scripts + .get(&script_root) + .cloned() + .ok_or_else(|| DataStoreError::NoteScriptNotFound(script_root)) + } + } } impl MastForestStore for TransactionContext { @@ -321,3 +334,62 @@ impl MastForestStore for TransactionContext { self.mast_store.get(procedure_hash) } } + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_objects::Felt; + use miden_objects::assembly::Assembler; + use miden_objects::note::NoteScript; + + use super::*; + use crate::TransactionContextBuilder; + + #[tokio::test] + async fn test_get_note_scripts() { + // Create two note scripts + let assembler1 = Assembler::default(); + let script1_code = "begin push.1 end"; + let program1 = assembler1 + .assemble_program(script1_code) + .expect("Failed to assemble note script 1"); + let note_script1 = NoteScript::new(program1); + let script_root1 = note_script1.root(); + + let assembler2 = Assembler::default(); + let script2_code = "begin push.2 push.3 add end"; + let program2 = assembler2 + .assemble_program(script2_code) + .expect("Failed to assemble note script 2"); + let note_script2 = NoteScript::new(program2); + let script_root2 = note_script2.root(); + + // Build a transaction context with both note scripts + let tx_context = TransactionContextBuilder::with_existing_mock_account() + .add_note_script(note_script1.clone()) + .add_note_script(note_script2.clone()) + .build() + .expect("Failed to build transaction context"); + + // Assert that fetching both note scripts works + let retrieved_script1 = tx_context + .get_note_script(script_root1) + .await + .expect("Failed to get note script 1"); + assert_eq!(retrieved_script1, note_script1); + + let retrieved_script2 = tx_context + .get_note_script(script_root2) + .await + .expect("Failed to get note script 2"); + assert_eq!(retrieved_script2, note_script2); + + // Fetching a non-existent one fails + let non_existent_root = + Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); + let result = tx_context.get_note_script(non_existent_root).await; + assert!(matches!(result, Err(DataStoreError::NoteScriptNotFound(_)))); + } +} diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index bd65bb3c85..fc7fafbc50 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -352,6 +352,8 @@ pub enum DataStoreError { AccountNotFound(AccountId), #[error("block with number {0} not found in data store")] BlockNotFound(BlockNumber), + #[error("note script with root {0} not found in data store")] + NoteScriptNotFound(Word), /// Custom error variant for implementors of the [`DataStore`](crate::executor::DataStore) /// trait. #[error("{error_msg}")] diff --git a/crates/miden-tx/src/executor/data_store.rs b/crates/miden-tx/src/executor/data_store.rs index 1b8569bc1a..5591abc71c 100644 --- a/crates/miden-tx/src/executor/data_store.rs +++ b/crates/miden-tx/src/executor/data_store.rs @@ -3,6 +3,7 @@ use alloc::collections::BTreeSet; use miden_objects::account::{AccountId, PartialAccount, StorageMapWitness}; use miden_objects::asset::AssetWitness; use miden_objects::block::{BlockHeader, BlockNumber}; +use miden_objects::note::NoteScript; use miden_objects::transaction::{AccountInputs, PartialBlockchain}; use miden_processor::{FutureMaybeSend, MastForestStore, Word}; @@ -67,4 +68,18 @@ pub trait DataStore: MastForestStore { map_root: Word, map_key: Word, ) -> impl FutureMaybeSend>; + + /// Returns a note script with the specified root. + /// + /// This method will try to find a note script with the specified root in the data store, + /// and if not found, return an error. + /// + /// # Errors + /// Returns an error if: + /// - The note script with the specified root could not be found in the data store. + /// - The data store encountered some internal error. + fn get_note_script( + &self, + script_root: Word, + ) -> impl FutureMaybeSend>; } From bffe79535ce0d713dd656a872eae647a7691e0d5 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Thu, 9 Oct 2025 18:25:33 +0300 Subject: [PATCH 082/133] Implement `get_initial_balance` in `miden::account` (#1959) * chore: implementation preparations (create kernel and miden procs, update docs) * feat: impl get_initial_balance in kernel lib * chore: update changelog * chore: minor test improvements * refactor: simplify the get_initial_balance using the initial vault root * refactor: update comments, improve tests, remove dups from changelog * chore: remove unused imports * refactor: rename get_item_init to get_initial_item * refactor: rename get_map_item_init to get_initial_map_item * refactor: be able to return init vault root for both native and foreign accts * refactor: use cdrop in get_account_initial_vault_root_ptr and get_account_initial_storage_slots_ptr procs * refactor: update get_initial_balance proc docs, add fpi test for it --- CHANGELOG.md | 11 +- .../multisig_rpo_falcon_512.masm | 2 +- .../asm/kernels/transaction/api.masm | 47 ++- .../asm/kernels/transaction/lib/account.masm | 42 ++- .../transaction/lib/account_delta.masm | 7 +- .../kernels/transaction/lib/asset_vault.masm | 2 +- .../asm/kernels/transaction/lib/epilogue.masm | 2 - .../asm/kernels/transaction/lib/faucet.masm | 2 - .../asm/kernels/transaction/lib/memory.masm | 83 +++-- .../asm/kernels/transaction/lib/prologue.masm | 3 +- crates/miden-lib/asm/miden/account.masm | 55 +++- crates/miden-lib/asm/miden/active_note.masm | 1 - crates/miden-lib/asm/miden/asset.masm | 1 - .../asm/miden/auth/rpo_falcon512.masm | 4 +- .../asm/miden/kernel_proc_offsets.masm | 95 +++--- crates/miden-lib/asm/miden/output_note.masm | 1 - .../src/testing/mock_account_code.rs | 8 +- .../src/transaction/kernel_procedures.rs | 14 +- .../src/kernel_tests/tx/test_account.rs | 289 +++++++++++++++++- .../src/kernel_tests/tx/test_fpi.rs | 137 ++++++++- .../src/kernel_tests/tx/test_tx.rs | 1 - docs/src/protocol_library.md | 5 +- 22 files changed, 660 insertions(+), 152 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5666dc9850..cf98901eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,6 @@ - Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). - Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). - [BREAKING] Enabled lazy loading of storage map entries during transaction execution ([#1857](https://github.com/0xMiden/miden-base/pull/1857)). -- Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). - Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). - Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). - Enabled lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). @@ -16,16 +15,17 @@ - Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). - [BREAKING] Move account seed into `PartialAccount` ([#1875](https://github.com/0xMiden/miden-base/pull/1875)). - [BREAKING] Enabled lazy loading of assets and storage map items for foreign accounts during transaction execution ([#1888](https://github.com/0xMiden/miden-base/pull/1888)). -- Added `get_item_init` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). -- Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) +- Added `get_initial_item` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). +- Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)). - Added `update_signers_and_threshold` procedure to update owner public keys and threshold config in multisig authentication component ([#1707](https://github.com/0xMiden/miden-base/issues/1707)). -- Implement `SlotName` for named storage slots ([#1932](https://github.com/0xMiden/miden-base/issues/1932)) +- Implement `SlotName` for named storage slots ([#1932](https://github.com/0xMiden/miden-base/issues/1932)). - [BREAKING] Removed `get_falcon_signature` from `miden-tx` crate ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Created a `Signature` wrapper to simplify the preparation of "native" signatures for use in the VM ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933]https://github.com/0xMiden/miden-base/pull/1933). -- Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937)) +- Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937)). - Added `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935]https://github.com/0xMiden/miden-base/pull/1935). - Added `AssetVault::{num_assets, num_leaves, inner_nodes}` ([#1939]https://github.com/0xMiden/miden-base/pull/1939). +- Added `account::get_initial_balance` procedure to `miden` lib ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). - [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). ### Changes @@ -60,6 +60,7 @@ - [BREAKING] Change the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). - [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). - Dynamically lookup all masm `EventId`s from source ([#1954](https://github.com/0xMiden/miden-base/pull/1954)). +- [BREAKING] Rename `get_item_init` and `get_map_item_init` to `get_initial_item` and `get_initial_map_item` respectively ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). - [BREAKING] Rename `TransactionInputs` to `TransactionExecutionInputs` and make a new `TransactionInputs` struct which does not contain `InputNotes` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). - [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). - [BREAKING] Refactor `TransactionInputs` and remove `TransactionWitness` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). diff --git a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm index acd407060b..5221762692 100644 --- a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm @@ -273,7 +273,7 @@ export.auth_tx_rpo_falcon512_multisig push.THRESHOLD_CONFIG_SLOT # => [index, TX_SUMMARY_COMMITMENT] - exec.account::get_item_init + exec.account::get_initial_item # => [0, 0, num_of_approvers, threshold, TX_SUMMARY_COMMITMENT] drop drop diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 7ebd7511cf..55c4d93f63 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -1,7 +1,6 @@ use.$kernel::account use.$kernel::account_delta use.$kernel::account_id -use.$kernel::asset_vault use.$kernel::faucet use.$kernel::input_note use.$kernel::memory @@ -439,7 +438,7 @@ end #! - the index is out of bounds. #! #! Invocation: dynexec -export.account_get_item_init +export.account_get_initial_item # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin # => [storage_offset, storage_size, index, pad(15)] @@ -449,7 +448,7 @@ export.account_get_item_init # => [index_with_offset, pad(15)] # fetch the initial account storage item - exec.account::get_item_init + exec.account::get_initial_item # => [INIT_VALUE, pad(15)] # truncate the stack @@ -472,7 +471,7 @@ end #! - the requested storage slot type is not map. #! #! Invocation: dynexec -export.account_get_map_item_init +export.account_get_initial_map_item # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin # => [storage_offset, storage_size, index, KEY, pad(11)] @@ -482,11 +481,12 @@ export.account_get_map_item_init # => [index_with_offset, KEY, pad(11)] # fetch the initial map item from account storage - exec.account::get_map_item_init + exec.account::get_initial_map_item # => [INIT_VALUE, pad(12)] end -#! Stores NEW_VALUE under the specified KEY within the map contained in the given account storage slot. +#! Stores NEW_VALUE under the specified KEY within the map contained in the given account storage +#! slot. #! #! Inputs: [index, KEY, NEW_VALUE, pad(7)] #! Outputs: [OLD_MAP_ROOT, OLD_MAP_VALUE, pad(8)] @@ -624,7 +624,8 @@ export.account_remove_asset # => [ASSET, pad(12)] end -#! Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault. +#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! account's vault. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix, pad(14)] #! Outputs: [balance, pad(15)] @@ -635,7 +636,7 @@ end #! - balance is the vault balance of the fungible asset. #! #! Panics if: -#! - the asset is not a fungible asset. +#! - the provided faucet ID is not an ID of a fungible faucet. #! #! Invocation: dynexec export.account_get_balance @@ -643,6 +644,26 @@ export.account_get_balance # => [balance, pad(15)] end +#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! account's vault at the beginning of the transaction. +#! +#! Inputs: [faucet_id_prefix, faucet_id_suffix, pad(14)] +#! Outputs: [init_balance, pad(15)] +#! +#! Where: +#! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet id of the fungible +#! asset of interest. +#! - init_balance is the vault balance of the fungible asset at the beginning of the transaction. +#! +#! Panics if: +#! - the provided faucet ID is not an ID of a fungible faucet. +#! +#! Invocation: dynexec +export.account_get_initial_balance + exec.account::get_initial_balance + # => [init_balance, pad(15)] +end + #! Returns a boolean indicating whether the non-fungible asset is present in the current account's vault. #! #! Inputs: [ASSET, pad(12)] @@ -1179,11 +1200,11 @@ end #! Returns the input notes commitment. #! -#! This is computed as a sequential hash of `(NULLIFIER, EMPTY_WORD_OR_NOTE_COMMITMENT)` over all input -#! notes. The data `EMPTY_WORD_OR_NOTE_COMMITMENT` functions as a flag, if the value is set to zero, then -#! the notes are authenticated by the transaction kernel. If the value is non-zero, then note -#! authentication will be delayed to the batch/block kernel. The delayed authentication allows a -#! transaction to consume a public note that is not yet included to a block. +#! This is computed as a sequential hash of `(NULLIFIER, EMPTY_WORD_OR_NOTE_COMMITMENT)` over all +#! input notes. The data `EMPTY_WORD_OR_NOTE_COMMITMENT` functions as a flag, if the value is set to +#! zero, then the notes are authenticated by the transaction kernel. If the value is non-zero, then +#! note authentication will be delayed to the batch/block kernel. The delayed authentication allows +#! a transaction to consume a public note that is not yet included to a block. #! #! Inputs: [pad(16)] #! Outputs: [INPUT_NOTES_COMMITMENT, pad(12)] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 81b0a6e81e..91b4f34d81 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -3,7 +3,7 @@ use.$kernel::account_id use.$kernel::asset_vault use.$kernel::constants use.$kernel::memory -use.std::collections::mmr + use.std::collections::smt use.std::crypto::hashes::rpo use.std::mem @@ -311,7 +311,7 @@ export.memory::get_init_account_commitment->get_initial_commitment #! #! Where: #! - INIT_ACCOUNT_VAULT_ROOT is the initial account vault root. -export.memory::get_init_account_vault_root->get_initial_vault_root +export.memory::get_init_native_account_vault_root->get_initial_vault_root #! Returns the storage commitment of the native account at the beginning of the transaction. #! @@ -486,7 +486,7 @@ end #! Where: #! - index is the index of the item to get. #! - INIT_VALUE is the initial value of the item at the beginning of the transaction. -export.get_item_init +export.get_initial_item # get account initial storage slots section offset exec.memory::get_account_initial_storage_slots_ptr # => [account_initial_storage_slots_ptr, index] @@ -581,13 +581,13 @@ end #! #! Panics if: #! - the requested storage slot type is not map. -export.get_map_item_init +export.get_initial_map_item # duplicate index for later use dup movdn.5 # => [index, KEY, index] # fetch the initial account storage item, which is ROOT of the map - exec.get_item_init swapw + exec.get_initial_item swapw # => [KEY, INIT_ROOT, index] exec.get_map_item_raw @@ -954,7 +954,8 @@ export.remove_asset_from_vault # => [ASSET] end -#! Returns the balance of a fungible asset associated with a faucet_id. +#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! account's vault. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix] #! Outputs: [balance] @@ -965,7 +966,7 @@ end #! - balance is the vault balance of the fungible asset. #! #! Panics if: -#! - the asset is not a fungible asset. +#! - the provided faucet ID is not an ID of a fungible faucet. export.get_balance # get the vault root exec.memory::get_account_vault_root_ptr movdn.2 @@ -980,6 +981,33 @@ export.get_balance # => [balance] end +#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! account's vault at the beginning of the transaction. +#! +#! Inputs: [faucet_id_prefix, faucet_id_suffix] +#! Outputs: [init_balance] +#! +#! Where: +#! - faucet_id_{prefix, suffix} are the prefix and suffix felts of the faucet id of the fungible +#! asset of interest. +#! - init_balance is the vault balance of the fungible asset at the beginning of the transaction. +#! +#! Panics if: +#! - the provided faucet ID is not an ID of a fungible faucet. +export.get_initial_balance + # get the vault root associated with the initial vault root of the native account + exec.memory::get_account_initial_vault_root_ptr movdn.2 + # => [faucet_id_prefix, faucet_id_suffix, init_native_vault_root_ptr] + + # emit event to signal that an asset's balance is requested + emit.ACCOUNT_VAULT_BEFORE_GET_BALANCE_EVENT + # => [faucet_id_prefix, faucet_id_suffix, init_native_vault_root_ptr] + + # get the asset balance + exec.asset_vault::get_balance + # => [init_balance] +end + #! Returns a boolean indicating whether the non-fungible asset is present in the current account's vault. #! #! Inputs: [ASSET] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm index cf71c242bf..3eae11c8bf 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm @@ -5,7 +5,6 @@ use.$kernel::constants use.$kernel::link_map use.$kernel::memory use.std::crypto::hashes::rpo -use.std::math::u64 use.std::word # ERRORS @@ -174,7 +173,7 @@ proc.update_value_slot_delta dup exec.account::get_item # => [CURRENT_VALUE, slot_idx, RATE, RATE, PERM] - dup.4 exec.account::get_item_init + dup.4 exec.account::get_initial_item # => [INIT_VALUE, CURRENT_VALUE, slot_idx, RATE, RATE, PERM] exec.word::test_eq not @@ -787,8 +786,8 @@ end # Since the golidlocks modulus is 2^64 - 2^32 + 1 and therefore odd, we can represent # modulus / 2 + 1 positive and modulus / 2 negative values. Again, 0 is counted on the positive # side and so the largest representable positive value is modulus / 2 and the smallest -# representable negative value is -modulus/2. So, every negative value has a positive counterpart -# (and vice versa). +# representable negative value is -(modulus / 2). So, every negative value has a positive +# counterpart (and vice versa). #! Computes the absolute value of the given delta amount represented as a felt and returns a #! boolean flag indicating whether the value is positive (or unsigned). diff --git a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm index 1afe27af2f..62f0811056 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm @@ -55,7 +55,7 @@ const.INVERSE_FUNGIBLE_BITMASK_U32=0xffffffdf # last byte: 0b1101_1111 #! - balance is the vault balance of the fungible asset. #! #! Panics if: -#! - the asset is not a fungible asset. +#! - the provided faucet ID is not an ID of a fungible faucet. export.get_balance # assert that the faucet id is a fungible faucet dup exec.account_id::is_fungible_faucet diff --git a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm index c05d830cf5..bc98d2cd10 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -4,9 +4,7 @@ use.$kernel::asset_vault use.$kernel::constants use.$kernel::memory use.$kernel::note -use.$kernel::tx -use.std::crypto::hashes::rpo use.std::word # ERRORS diff --git a/crates/miden-lib/asm/kernels/transaction/lib/faucet.masm b/crates/miden-lib/asm/kernels/transaction/lib/faucet.masm index 38fcffa306..7cd5f4e515 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/faucet.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/faucet.masm @@ -1,5 +1,3 @@ -use.std::collections::smt - use.$kernel::account use.$kernel::account_id use.$kernel::asset diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index 5af6ece99a..b599dbad47 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -1,4 +1,3 @@ -use.$kernel::account use.$kernel::constants use.std::mem @@ -294,7 +293,7 @@ export.set_num_output_notes mem_store.NUM_OUTPUT_NOTES_PTR end -#! Returns a pointer to the active input note. +#! Returns the pointer to the active input note. #! #! Inputs: [] #! Outputs: [note_ptr] @@ -316,7 +315,7 @@ export.set_active_input_note_ptr mem_store.ACTIVE_INPUT_NOTE_PTR end -#! Returns a pointer to the memory address at which the input vault root is stored. +#! Returns the pointer to the memory address at which the input vault root is stored. #! #! Inputs: [] #! Outputs: [input_vault_root_ptr] @@ -350,7 +349,7 @@ export.set_input_vault_root mem_storew.INPUT_VAULT_ROOT_PTR end -#! Returns a pointer to the memory address at which the output vault root is stored. +#! Returns the pointer to the memory address at which the output vault root is stored. #! #! Inputs: [] #! Outputs: [output_vault_root_ptr] @@ -488,7 +487,7 @@ end #! #! Where: #! - INIT_NATIVE_ACCOUNT_VAULT_ROOT is the initial vault root of the native account. -export.set_init_account_vault_root +export.set_init_native_account_vault_root mem_storew.INIT_NATIVE_ACCOUNT_VAULT_ROOT_PTR end @@ -499,10 +498,23 @@ end #! #! Where: #! - INIT_NATIVE_ACCOUNT_VAULT_ROOT is the initial vault root of the native account. -export.get_init_account_vault_root +export.get_init_native_account_vault_root padw mem_loadw.INIT_NATIVE_ACCOUNT_VAULT_ROOT_PTR end +#! Returns the memory address of the vault root of the native account at the beginning of the +#! transaction. +#! +#! Inputs: [] +#! Outputs: [native_account_initial_vault_root_ptr] +#! +#! Where: +#! - native_account_initial_vault_root_ptr is the memory pointer to the initial vault root of the +#! native account. +export.get_init_native_account_vault_root_ptr + push.INIT_NATIVE_ACCOUNT_VAULT_ROOT_PTR +end + #! Sets the storage commitment of the native account at the beginning of the transaction. #! #! Inputs: [INIT_NATIVE_ACCOUNT_STORAGE_COMMITMENT] @@ -549,7 +561,7 @@ export.set_nullifier_commitment mem_storew.INPUT_NOTES_COMMITMENT_PTR end -#! Returns a memory address of the transaction script root. +#! Returns the memory address of the transaction script root. #! #! Inputs: [] #! Outputs: [tx_script_root_ptr] @@ -1110,6 +1122,36 @@ export.set_account_vault_root mem_storew end +#! Returns the memory pointer to the initial vault root of the current account. +#! +#! For the native account, this returns the pointer to the initial vault root. +#! For foreign accounts, this returns the regular vault root pointer since foreign accounts +#! are read-only and their initial and current vault state always matches. +#! +#! Inputs: [] +#! Outputs: [account_initial_vault_root_ptr] +#! +#! Where: +#! - account_initial_vault_root_ptr is the memory pointer to the initial vault root. +export.get_account_initial_vault_root_ptr + # For foreign account, use the regular vault root pointer since foreign accounts are read-only + # and initial == current + exec.get_account_vault_root_ptr + # => [account_vault_root_ptr] + + # For native account, use the initial vault root pointer + exec.get_init_native_account_vault_root_ptr + # => [native_account_initial_vault_root_ptr, account_vault_root_ptr] + + # get the flag indicating whether the current account is native + exec.is_native_account + # => [is_native_account, native_account_initial_vault_root_ptr, account_vault_root_ptr] + + # according to the is_native_account flag, return the corresponding pointer + cdrop + # => [account_initial_vault_root_ptr] +end + ### ACCOUNT CODE ################################################# #! Returns the code commitment of the account. @@ -1366,19 +1408,22 @@ end #! Where: #! - account_initial_storage_slots_ptr is the memory pointer to the initial storage slot values. export.get_account_initial_storage_slots_ptr + # For foreign account, use the regular storage slots pointer since foreign accounts are + # read-only and initial == current + exec.get_account_storage_slots_section_ptr + # => [account_storage_slots_ptr] + + # For native account, use the initial storage slots pointer + exec.get_native_account_initial_storage_slots_ptr + # => [native_account_initial_storage_slots_ptr, account_storage_slots_ptr] + + # get the flag indicating whether the current account is native exec.is_native_account - # => [is_native_account] + # => [is_native_account, native_account_initial_storage_slots_ptr, account_storage_slots_ptr] - if.true - # For native account, return the initial storage slots pointer - exec.get_native_account_initial_storage_slots_ptr - # => [account_initial_storage_slots_ptr] - else - # For foreign account, return the regular storage slots pointer since - # foreign accounts are read-only and initial == current - exec.get_account_storage_slots_section_ptr - # => [account_initial_storage_slots_ptr] - end + # according to the is_native_account flag, return the corresponding pointer + cdrop + # => [account_initial_storage_slots_ptr] end ### ACCOUNT DELTA ################################################# @@ -1391,7 +1436,7 @@ end #! Where: #! - account_delta_fungible_asset_ptr is the link map pointer to the fungible asset vault delta. export.get_account_delta_fungible_asset_ptr - push.ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR + push.ACCOUNT_DELTA_FUNGIBLE_ASSET_PTR end #! Returns the link map pointer to the non-fungible asset vault delta. diff --git a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm index b0258de5f8..7ae0e3ecb5 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -7,7 +7,6 @@ use.$kernel::account use.$kernel::account_id use.$kernel::asset_vault use.$kernel::constants -use.$kernel::input_note use.$kernel::memory # CONSTS @@ -449,7 +448,7 @@ proc.process_account_data # invariant checking # this account vault root is also stored as an initial one in the global inputs exec.memory::get_account_vault_root - exec.memory::set_init_account_vault_root + exec.memory::set_init_native_account_vault_root exec.memory::set_input_vault_root dropw # => [ACCOUNT_COMMITMENT] diff --git a/crates/miden-lib/asm/miden/account.masm b/crates/miden-lib/asm/miden/account.masm index 08cf7e6e4b..d2b422df12 100644 --- a/crates/miden-lib/asm/miden/account.masm +++ b/crates/miden-lib/asm/miden/account.masm @@ -264,7 +264,8 @@ export.get_item # => [VALUE] end -#! Gets the initial item from the account storage slot as it was at the beginning of the transaction. +#! Gets the initial item from the account storage slot as it was at the beginning of the +#! transaction. #! #! Inputs: [index] #! Outputs: [INIT_VALUE] @@ -277,11 +278,11 @@ end #! - the index of the requested item is out of bounds. #! #! Invocation: exec -export.get_item_init +export.get_initial_item push.0.0 movup.2 # => [index, 0, 0] - exec.kernel_proc_offsets::account_get_item_init_offset + exec.kernel_proc_offsets::account_get_initial_item_offset # => [offset, index, 0, 0] # pad the stack @@ -390,7 +391,8 @@ export.set_map_item # => [OLD_MAP_ROOT, OLD_MAP_VALUE] end -#! Gets the initial VALUE from the account storage map as it was at the beginning of the transaction. +#! Gets the initial VALUE from the account storage map as it was at the beginning of the +#! transaction. #! #! Inputs: [index, KEY] #! Outputs: [INIT_VALUE] @@ -405,8 +407,8 @@ end #! - the slot item at index is not a map. #! #! Invocation: exec -export.get_map_item_init - exec.kernel_proc_offsets::account_get_map_item_init_offset +export.get_initial_map_item + exec.kernel_proc_offsets::account_get_initial_map_item_offset # => [offset, index, KEY] # pad the stack @@ -504,18 +506,19 @@ export.compute_storage_commitment # => [STORAGE_COMMITMENT] end -#! Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault. +#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! account's vault. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix] #! Outputs: [balance] #! #! Where: -#! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet id of the fungible +#! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet ID of the fungible #! asset of interest. #! - balance is the vault balance of the fungible asset. #! #! Panics if: -#! - the asset is not a fungible asset. +#! - the provided faucet ID is not an ID of a fungible faucet. #! #! Invocation: exec export.get_balance @@ -534,7 +537,39 @@ export.get_balance # => [balance] end -#! Returns a boolean indicating whether the non-fungible asset is present in the current account's vault. +#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! account's vault at the beginning of the transaction. +#! +#! Inputs: [faucet_id_prefix, faucet_id_suffix] +#! Outputs: [init_balance] +#! +#! Where: +#! - faucet_id_{prefix, suffix} are the prefix and suffix felts of the faucet id of the fungible +#! asset of interest. +#! - init_balance is the vault balance of the fungible asset at the beginning of the transaction. +#! +#! Panics if: +#! - the provided faucet ID is not an ID of a fungible faucet. +#! +#! Invocation: exec +export.get_initial_balance + exec.kernel_proc_offsets::account_get_initial_balance_offset + # => [offset, faucet_id_prefix, faucet_id_suffix] + + # pad the stack + push.0 movdn.3 padw swapw padw padw swapdw + # => [offset, faucet_id_prefix, faucet_id_suffix, pad(13)] + + syscall.exec_kernel_proc + # => [init_balance, pad(15)] + + # clean the stack + swapdw dropw dropw swapw dropw movdn.3 drop drop drop + # => [init_balance] +end + +#! Returns a boolean indicating whether the non-fungible asset is present in the current account's +#! vault. #! #! Inputs: [ASSET] #! Outputs: [has_asset] diff --git a/crates/miden-lib/asm/miden/active_note.masm b/crates/miden-lib/asm/miden/active_note.masm index 71ab6dd4fc..cc1ee278a5 100644 --- a/crates/miden-lib/asm/miden/active_note.masm +++ b/crates/miden-lib/asm/miden/active_note.masm @@ -2,7 +2,6 @@ use.std::mem use.miden::kernel_proc_offsets use.miden::note -use.miden::account_id use.miden::contracts::wallets::basic->wallet # ERRORS diff --git a/crates/miden-lib/asm/miden/asset.masm b/crates/miden-lib/asm/miden/asset.masm index ff4bc9742e..6c6d8f8d6b 100644 --- a/crates/miden-lib/asm/miden/asset.masm +++ b/crates/miden-lib/asm/miden/asset.masm @@ -1,4 +1,3 @@ -use.miden::account use.miden::account_id # ERRORS diff --git a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm index 4394cb9d61..b0ff5ecd29 100644 --- a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm +++ b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm @@ -75,7 +75,7 @@ end #! the provided account storage map slot, verifies their signatures against the transaction message, #! and returns the number of successfully verified signatures. #! -#! Note: Calls `account::get_map_item_init` to access the transaction's initial storage state +#! Note: Calls `account::get_initial_map_item` to access the transaction's initial storage state #! rather than the current state. This is crucial when validating transactions that update #! the owner public key mapping - the previous signers must authorize the change to #! the new signers, not the new signers authorizing themselves. @@ -107,7 +107,7 @@ export.verify_signatures.16 # => [owner_key_slot, [0, 0, 0, i-1], i-1, MSG] # Get public key from initial storage state - exec.account::get_map_item_init + exec.account::get_initial_map_item # => [OWNER_PUB_KEY, i-1, MSG] loc_storew.CURRENT_PK_LOC diff --git a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm index 5f1522c2aa..90e630cef0 100644 --- a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm +++ b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm @@ -23,10 +23,10 @@ const.ACCOUNT_GET_CODE_COMMITMENT_OFFSET=7 const.ACCOUNT_GET_INITIAL_STORAGE_COMMITMENT_OFFSET=8 const.ACCOUNT_COMPUTE_STORAGE_COMMITMENT_OFFSET=9 const.ACCOUNT_GET_ITEM_OFFSET=10 -const.ACCOUNT_GET_ITEM_INIT_OFFSET=11 +const.ACCOUNT_GET_INITIAL_ITEM_OFFSET=11 const.ACCOUNT_SET_ITEM_OFFSET=12 const.ACCOUNT_GET_MAP_ITEM_OFFSET=13 -const.ACCOUNT_GET_MAP_ITEM_INIT_OFFSET=14 +const.ACCOUNT_GET_INITIAL_MAP_ITEM_OFFSET=14 const.ACCOUNT_SET_MAP_ITEM_OFFSET=15 # Vault @@ -35,59 +35,60 @@ const.ACCOUNT_GET_VAULT_ROOT_OFFSET=17 const.ACCOUNT_ADD_ASSET_OFFSET=18 const.ACCOUNT_REMOVE_ASSET_OFFSET=19 const.ACCOUNT_GET_BALANCE_OFFSET=20 -const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=21 +const.ACCOUNT_GET_INITIAL_BALANCE_OFFSET=21 +const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=22 # Delta -const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=22 +const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=23 # Procedure introspection -const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=23 +const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=24 ### Faucet ###################################### -const.FAUCET_MINT_ASSET_OFFSET=24 -const.FAUCET_BURN_ASSET_OFFSET=25 -const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=26 -const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=27 +const.FAUCET_MINT_ASSET_OFFSET=25 +const.FAUCET_BURN_ASSET_OFFSET=26 +const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=27 +const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=28 ### Note ######################################## # input notes -const.INPUT_NOTE_GET_METADATA_OFFSET=28 -const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=29 -const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=30 -const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=31 -const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=32 -const.INPUT_NOTE_GET_RECIPIENT_OFFSET=33 +const.INPUT_NOTE_GET_METADATA_OFFSET=29 +const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=30 +const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=31 +const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=32 +const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=33 +const.INPUT_NOTE_GET_RECIPIENT_OFFSET=34 # output notes -const.OUTPUT_NOTE_CREATE_OFFSET=34 -const.OUTPUT_NOTE_GET_METADATA_OFFSET=35 -const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=36 -const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=37 -const.OUTPUT_NOTE_ADD_ASSET_OFFSET=38 +const.OUTPUT_NOTE_CREATE_OFFSET=35 +const.OUTPUT_NOTE_GET_METADATA_OFFSET=36 +const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=37 +const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=38 +const.OUTPUT_NOTE_ADD_ASSET_OFFSET=39 ### Tx ########################################## # input notes -const.TX_GET_NUM_INPUT_NOTES_OFFSET=39 -const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=40 +const.TX_GET_NUM_INPUT_NOTES_OFFSET=40 +const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=41 # output notes -const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=41 -const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=42 +const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=42 +const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=43 # block info -const.TX_GET_BLOCK_COMMITMENT_OFFSET=43 -const.TX_GET_BLOCK_NUMBER_OFFSET=44 -const.TX_GET_BLOCK_TIMESTAMP_OFFSET=45 +const.TX_GET_BLOCK_COMMITMENT_OFFSET=44 +const.TX_GET_BLOCK_NUMBER_OFFSET=45 +const.TX_GET_BLOCK_TIMESTAMP_OFFSET=46 # foreign context -const.TX_START_FOREIGN_CONTEXT_OFFSET=46 -const.TX_END_FOREIGN_CONTEXT_OFFSET=47 +const.TX_START_FOREIGN_CONTEXT_OFFSET=47 +const.TX_END_FOREIGN_CONTEXT_OFFSET=48 # expiration data -const.TX_GET_EXPIRATION_DELTA_OFFSET=48 # accessor -const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=49 # mutator +const.TX_GET_EXPIRATION_DELTA_OFFSET=49 # accessor +const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=50 # mutator # ACCESSORS # ------------------------------------------------------------------------------------------------- @@ -274,28 +275,28 @@ export.account_set_map_item_offset push.ACCOUNT_SET_MAP_ITEM_OFFSET end -#! Returns the offset of the `account_get_item_init` kernel procedure. +#! Returns the offset of the `account_get_initial_item` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `account_get_item_init` kernel procedure required to get the -#! address where this procedure is stored. -export.account_get_item_init_offset - push.ACCOUNT_GET_ITEM_INIT_OFFSET +#! - proc_offset is the offset of the `account_get_initial_item` kernel procedure required to get +#! the address where this procedure is stored. +export.account_get_initial_item_offset + push.ACCOUNT_GET_INITIAL_ITEM_OFFSET end -#! Returns the offset of the `account_get_map_item_init` kernel procedure. +#! Returns the offset of the `account_get_initial_map_item` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `account_get_map_item_init` kernel procedure required to get the -#! address where this procedure is stored. -export.account_get_map_item_init_offset - push.ACCOUNT_GET_MAP_ITEM_INIT_OFFSET +#! - proc_offset is the offset of the `account_get_initial_map_item` kernel procedure required to +#! get the address where this procedure is stored. +export.account_get_initial_map_item_offset + push.ACCOUNT_GET_INITIAL_MAP_ITEM_OFFSET end #! Returns the offset of the `account_get_initial_vault_root` kernel procedure. @@ -358,6 +359,18 @@ export.account_get_balance_offset push.ACCOUNT_GET_BALANCE_OFFSET end +#! Returns the offset of the `account_get_initial_balance` kernel procedure. +#! +#! Inputs: [] +#! Outputs: [proc_offset] +#! +#! Where: +#! - proc_offset is the offset of the `account_get_initial_balance` kernel procedure required to get +#! the address where this procedure is stored. +export.account_get_initial_balance_offset + push.ACCOUNT_GET_INITIAL_BALANCE_OFFSET +end + #! Returns the offset of the `account_has_non_fungible_asset` kernel procedure. #! #! Inputs: [] diff --git a/crates/miden-lib/asm/miden/output_note.masm b/crates/miden-lib/asm/miden/output_note.masm index 63fd14df81..05b92d1083 100644 --- a/crates/miden-lib/asm/miden/output_note.masm +++ b/crates/miden-lib/asm/miden/output_note.masm @@ -1,6 +1,5 @@ use.miden::kernel_proc_offsets use.miden::note -use.std::mem # PROCEDURES # ================================================================================================= diff --git a/crates/miden-lib/src/testing/mock_account_code.rs b/crates/miden-lib/src/testing/mock_account_code.rs index ff78405970..0d82d94f11 100644 --- a/crates/miden-lib/src/testing/mock_account_code.rs +++ b/crates/miden-lib/src/testing/mock_account_code.rs @@ -54,8 +54,8 @@ const MOCK_ACCOUNT_CODE: &str = " # Stack: [index, pad(15)] # Output: [VALUE, pad(12)] - export.get_item_init - exec.account::get_item_init + export.get_initial_item + exec.account::get_initial_item # => [VALUE, pad(15)] # truncate the stack @@ -78,8 +78,8 @@ const MOCK_ACCOUNT_CODE: &str = " # Stack: [index, KEY, pad(11)] # Output: [VALUE, pad(12)] - export.get_map_item_init - exec.account::get_map_item_init + export.get_initial_map_item + exec.account::get_initial_map_item end # Stack: [pad(16)] diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index 2c10d82af5..d8fd0272bb 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -6,7 +6,7 @@ use miden_objects::{Word, word}; // ================================================================================================ /// Hashes of all dynamically executed kernel procedures. -pub const KERNEL_PROCEDURES: [Word; 50] = [ +pub const KERNEL_PROCEDURES: [Word; 51] = [ // account_get_initial_commitment word!("0x920898348bacd6d98a399301eb308478fd32b32eab019a5a6ef7a6b44abb61f6"), // account_compute_current_commitment @@ -29,14 +29,14 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ word!("0xa87008550383e1a88dde5d0adefc68ee3bf477aec07e4700f9101241aa1e868f"), // account_get_item word!("0xe1e6843fb47f24476a12ef8cd19dd5de2dd74b90433051b26720dce5ab223bf0"), - // account_get_item_init - word!("0x256b8513f29310cc8aa8ead06f6de800b7cf342dbfa435a2e11cc32a32d62345"), + // account_get_initial_item + word!("0x5e956c876cd6eaaa15f5800a5232c6b4e3e50e0335a31ba2e4a9e5f2401aece4"), // account_set_item word!("0x84b5206c5a0dccf56568bc0157b8322e8a506332bc212f1ad35bab4fe9f6bfed"), // account_get_map_item word!("0xc310b9a4a08531061839abd3f575a681a6af61c36ca48491d0896d3badee87f1"), - // account_get_map_item_init - word!("0x19a84e1abf9a99c1af352c39d0562bad29003b572cd4eb7a5fdceeac388eaa1b"), + // account_get_initial_map_item + word!("0xa23d7c4e671f60c36c43076fbeb9a3d4112bc18cf808eba28e153690b6833236"), // account_set_map_item word!("0x6ee1674cb94eaf4e23383abbfe918bff742ec13f69150eaf66cc9ea0243f4a7e"), // account_get_initial_vault_root @@ -49,10 +49,12 @@ pub const KERNEL_PROCEDURES: [Word; 50] = [ word!("0x7b965e458a962667a9cdc54a6677fd0c5a573bd7b265ccc33775a8a985aeead5"), // account_get_balance word!("0x52233827fc91ef50a5dc09f74d3176011d244fd80e291ae751b64bdaa2c6cf75"), + // account_get_initial_balance + word!("0x9758302462328d6557153655562e6a419def7992997c0abbcec412cbb4f9351f"), // account_has_non_fungible_asset word!("0xf975c799cffebf8565a8479475fe04c1832ef2a7484c4a2a42bbf7d8a340d649"), // account_compute_delta_commitment - word!("0xb4589587f804af8205f9179ec6b58814d78171a3dc6d78cbf470db512ec25129"), + word!("0x57165e9bc287a6f7b96be5f20a3f6752129afc756f1a6ab6c55959f7e436d0e5"), // account_was_procedure_called word!("0x34f27a609f2f2b4fec454b17182552b0acc52524e507e134257a1f1ed30a57cd"), // faucet_mint_asset diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 0cbe7bb05b..0770fb9a58 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -33,14 +33,17 @@ use miden_objects::account::{ use miden_objects::assembly::diagnostics::{IntoDiagnostic, NamedSource, Report, WrapErr, miette}; use miden_objects::assembly::{DefaultSourceManager, Library}; use miden_objects::asset::{Asset, AssetVault, FungibleAsset}; +use miden_objects::note::NoteType; use miden_objects::testing::account_id::{ ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, + ACCOUNT_ID_SENDER, }; use miden_objects::testing::storage::STORAGE_LEAVES_2; -use miden_objects::transaction::{ExecutedTransaction, TransactionScript}; +use miden_objects::transaction::{ExecutedTransaction, OutputNote, TransactionScript}; use miden_objects::{LexicographicWord, StarkField}; use miden_processor::{EMPTY_WORD, ExecutionError, MastNodeExt, Word}; use miden_tx::{LocalTransactionProver, TransactionExecutorError}; @@ -49,10 +52,12 @@ use rand_chacha::ChaCha20Rng; use super::{Felt, StackInputs, ZERO}; use crate::executor::CodeExecutor; +use crate::utils::create_public_p2any_note; use crate::{ Auth, MockChain, TransactionContextBuilder, + TxContextInput, assert_execution_error, assert_transaction_executor_error, }; @@ -1118,6 +1123,266 @@ fn test_get_vault_root() -> anyhow::Result<()> { Ok(()) } +/// This test checks the correctness of the `miden::account::get_initial_balance` procedure in two +/// cases: +/// - when a note adds the asset which already exists in the account vault. +/// - when a note adds the asset which doesn't exist in the account vault. +/// +/// As part of the test pipeline it also checks the correctness of the +/// `miden::account::get_balance` procedure. +#[tokio::test] +async fn test_get_init_balance_addition() -> anyhow::Result<()> { + // prepare the testing data + // ------------------------------------------ + let mut builder = MockChain::builder(); + + let faucet_existing_asset = + AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).context("id should be valid")?; + let faucet_new_asset = + AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).context("id should be valid")?; + + let fungible_asset_for_account = Asset::Fungible( + FungibleAsset::new(faucet_existing_asset, 10).context("fungible_asset_0 is invalid")?, + ); + let account = builder + .add_existing_wallet_with_assets(crate::Auth::BasicAuth, [fungible_asset_for_account])?; + + let fungible_asset_for_note_existing = Asset::Fungible( + FungibleAsset::new(faucet_existing_asset, 7).context("fungible_asset_0 is invalid")?, + ); + + let fungible_asset_for_note_new = Asset::Fungible( + FungibleAsset::new(faucet_new_asset, 20).context("fungible_asset_1 is invalid")?, + ); + + let p2id_note_existing_asset = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[fungible_asset_for_note_existing], + NoteType::Public, + )?; + let p2id_note_new_asset = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[fungible_asset_for_note_new], + NoteType::Public, + )?; + + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + // case 1: existing asset was added to the account + // ------------------------------------------ + + let initial_balance = account + .vault() + .get_balance(faucet_existing_asset) + .expect("faucet_id should be a fungible faucet ID"); + + let add_existing_source = format!( + r#" + use.miden::account + + begin + # push faucet ID prefix and suffix + push.{suffix}.{prefix} + # => [faucet_id_prefix, faucet_id_suffix] + + # get the current asset balance + dup.1 dup.1 exec.account::get_balance + # => [final_balance, faucet_id_prefix, faucet_id_suffix] + + # assert final balance is correct + push.{final_balance} + assert_eq.err="final balance is incorrect" + # => [faucet_id_prefix, faucet_id_suffix] + + # get the initial asset balance + exec.account::get_initial_balance + # => [init_balance] + + # assert initial balance is correct + push.{initial_balance} + assert_eq.err="initial balance is incorrect" + end + "#, + suffix = faucet_existing_asset.suffix(), + prefix = faucet_existing_asset.prefix().as_felt(), + final_balance = + initial_balance + fungible_asset_for_note_existing.unwrap_fungible().amount(), + ); + + let tx_script = ScriptBuilder::default().compile_tx_script(add_existing_source)?; + + let tx_context = mock_chain + .build_tx_context( + TxContextInput::AccountId(account.id()), + &[], + &[p2id_note_existing_asset], + )? + .tx_script(tx_script) + .build()?; + + tx_context.execute().await?; + + // case 2: new asset was added to the account + // ------------------------------------------ + + let initial_balance = account + .vault() + .get_balance(faucet_new_asset) + .expect("faucet_id should be a fungible faucet ID"); + + let add_new_source = format!( + r#" + use.miden::account + + begin + # push faucet ID prefix and suffix + push.{suffix}.{prefix} + # => [faucet_id_prefix, faucet_id_suffix] + + # get the current asset balance + dup.1 dup.1 exec.account::get_balance + # => [final_balance, faucet_id_prefix, faucet_id_suffix] + + # assert final balance is correct + push.{final_balance} + assert_eq.err="final balance is incorrect" + # => [faucet_id_prefix, faucet_id_suffix] + + # get the initial asset balance + exec.account::get_initial_balance + # => [init_balance] + + # assert initial balance is correct + push.{initial_balance} + assert_eq.err="initial balance is incorrect" + end + "#, + suffix = faucet_new_asset.suffix(), + prefix = faucet_new_asset.prefix().as_felt(), + final_balance = initial_balance + fungible_asset_for_note_new.unwrap_fungible().amount(), + ); + + let tx_script = ScriptBuilder::default().compile_tx_script(add_new_source)?; + + let tx_context = mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[p2id_note_new_asset])? + .tx_script(tx_script) + .build()?; + + tx_context.execute().await?; + + Ok(()) +} + +/// This test checks the correctness of the `miden::account::get_initial_balance` procedure in case +/// when we create a note which removes an asset from the account vault. +/// +/// As part of the test pipeline it also checks the correctness of the +/// `miden::account::get_balance` procedure. +#[tokio::test] +async fn test_get_init_balance_subtraction() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let faucet_existing_asset = + AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).context("id should be valid")?; + + let fungible_asset_for_account = Asset::Fungible( + FungibleAsset::new(faucet_existing_asset, 10).context("fungible_asset_0 is invalid")?, + ); + let account = builder + .add_existing_wallet_with_assets(crate::Auth::BasicAuth, [fungible_asset_for_account])?; + + let fungible_asset_for_note_existing = Asset::Fungible( + FungibleAsset::new(faucet_existing_asset, 7).context("fungible_asset_0 is invalid")?, + ); + + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + let initial_balance = account + .vault() + .get_balance(faucet_existing_asset) + .expect("faucet_id should be a fungible faucet ID"); + + let expected_output_note = + create_public_p2any_note(ACCOUNT_ID_SENDER.try_into()?, [fungible_asset_for_note_existing]); + + let remove_existing_source = format!( + r#" + use.miden::account + use.miden::contracts::wallets::basic->wallet + use.mock::util + + # Inputs: [ASSET, note_idx] + # Outputs: [ASSET, note_idx] + proc.move_asset_to_note + # pad the stack before call + push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw + # => [ASSET, note_idx, pad(11)] + + call.wallet::move_asset_to_note + # => [ASSET, note_idx, pad(11)] + + # remove excess PADs from the stack + swapdw dropw dropw swapw movdn.7 drop drop drop + # => [ASSET, note_idx] + end + + begin + # create random note and move the asset into it + exec.util::create_random_note + # => [note_idx] + + push.{REMOVED_ASSET} + exec.move_asset_to_note dropw drop + # => [] + + # push faucet ID prefix and suffix + push.{suffix}.{prefix} + # => [faucet_id_prefix, faucet_id_suffix] + + # get the current asset balance + dup.1 dup.1 exec.account::get_balance + # => [final_balance, faucet_id_prefix, faucet_id_suffix] + + # assert final balance is correct + push.{final_balance} + assert_eq.err="final balance is incorrect" + # => [faucet_id_prefix, faucet_id_suffix] + + # get the initial asset balance + exec.account::get_initial_balance + # => [init_balance] + + # assert initial balance is correct + push.{initial_balance} + assert_eq.err="initial balance is incorrect" + end + "#, + REMOVED_ASSET = Word::from(fungible_asset_for_note_existing), + suffix = faucet_existing_asset.suffix(), + prefix = faucet_existing_asset.prefix().as_felt(), + final_balance = + initial_balance - fungible_asset_for_note_existing.unwrap_fungible().amount(), + ); + + let tx_script = + ScriptBuilder::with_mock_libraries()?.compile_tx_script(remove_existing_source)?; + + let tx_context = mock_chain + .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[])? + .tx_script(tx_script) + .extend_expected_output_notes(vec![OutputNote::Full(expected_output_note)]) + .build()?; + + tx_context.execute().await?; + + Ok(()) +} + // PROCEDURE AUTHENTICATION TESTS // ================================================================================================ @@ -1352,10 +1617,10 @@ async fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { // ================================================================================================ #[test] -fn test_get_item_init() -> miette::Result<()> { +fn test_get_initial_item() -> miette::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); - // Test that get_item_init returns the initial value before any changes + // Test that get_initial_item returns the initial value before any changes let code = format!( " use.$kernel::account @@ -1367,7 +1632,7 @@ fn test_get_item_init() -> miette::Result<()> { # get initial value of storage slot 0 push.0 - exec.account::get_item_init + exec.account::get_initial_item push.{expected_initial_value} assert_eqw.err=\"initial value should match expected\" @@ -1382,9 +1647,9 @@ fn test_get_item_init() -> miette::Result<()> { push.9.10.11.12 assert_eqw.err=\"current value should be updated\" - # get_item_init should still return the initial value + # get_initial_item should still return the initial value push.0 - exec.account::get_item_init + exec.account::get_initial_item push.{expected_initial_value} assert_eqw.err=\"initial value should remain unchanged\" end @@ -1398,7 +1663,7 @@ fn test_get_item_init() -> miette::Result<()> { } #[test] -fn test_get_map_item_init() -> miette::Result<()> { +fn test_get_initial_map_item() -> miette::Result<()> { let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) .with_component(MockAccountComponent::with_slots(vec![AccountStorage::mock_item_2().slot])) @@ -1423,7 +1688,7 @@ fn test_get_map_item_init() -> miette::Result<()> { # get initial value from map push.{initial_key} push.0 - call.mock_account::get_map_item_init + call.mock_account::get_initial_map_item push.{initial_value} assert_eqw.err=\"initial map value should match expected\" @@ -1440,17 +1705,17 @@ fn test_get_map_item_init() -> miette::Result<()> { push.{new_value} assert_eqw.err=\"current map value should be updated\" - # get_map_item_init should still return the initial value for the initial key + # get_initial_map_item should still return the initial value for the initial key push.{initial_key} push.0 - call.mock_account::get_map_item_init + call.mock_account::get_initial_map_item push.{initial_value} assert_eqw.err=\"initial map value should remain unchanged\" - # get_map_item_init for the new key should return empty word (default) + # get_initial_map_item for the new key should return empty word (default) push.{new_key} push.0 - call.mock_account::get_map_item_init + call.mock_account::get_initial_map_item padw assert_eqw.err=\"new key should have empty initial value\" diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index 3c9a3484fd..9bdc6f5982 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -695,7 +695,7 @@ async fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Resu let source_manager = Arc::new(DefaultSourceManager::default()); let foreign_account_component = AccountComponent::compile( NamedSource::new("foreign_account_code", foreign_account_code_source), - TransactionKernel::with_kernel_library(source_manager.clone()), + TransactionKernel::assembler_with_source_manager(source_manager.clone()), vec![], )? .with_supports_all_types(); @@ -722,7 +722,6 @@ async fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Resu use.std::sys use.miden::tx - use.miden::account begin # Get the added balance of two assets from foreign account @@ -771,6 +770,113 @@ async fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Resu Ok(()) } +/// Test that the `miden::get_initial_balance` procedure works correctly being called from a foreign +/// account. +#[tokio::test] +async fn foreign_account_get_initial_balance() -> anyhow::Result<()> { + let fungible_faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1)?; + let fungible_asset = Asset::Fungible(FungibleAsset::new(fungible_faucet_id, 10)?); + + let foreign_account_code_source = format!( + " + use.miden::account + + export.get_initial_balance + # push the faucet ID on the stack + push.{fungible_faucet_id_suffix} push.{fungible_faucet_id_prefix} + + # get the initial balance of the asset associated with the provided faucet ID + exec.account::get_balance + # => [initial_balance] + + # truncate the stack + swap drop + # => [initial_balance] + end + ", + fungible_faucet_id_prefix = fungible_faucet_id.prefix().as_felt(), + fungible_faucet_id_suffix = fungible_faucet_id.suffix(), + ); + + let source_manager = Arc::new(DefaultSourceManager::default()); + let foreign_account_component = AccountComponent::compile( + NamedSource::new("foreign_account_code", foreign_account_code_source), + TransactionKernel::assembler_with_source_manager(source_manager.clone()), + vec![], + )? + .with_supports_all_types(); + + let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(foreign_account_component.clone()) + .with_assets(vec![fungible_asset]) + .build_existing()?; + + let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(MockAccountComponent::with_empty_slots()) + .storage_mode(AccountStorageMode::Public) + .build_existing()?; + + let mut mock_chain = + MockChainBuilder::with_accounts([native_account.clone(), foreign_account.clone()])? + .build()?; + mock_chain.prove_next_block()?; + + let code = format!( + " + use.std::sys + + use.miden::tx + + begin + # Get the initial balance of the fungible asset from the foreign account + + # pad the stack for the `execute_foreign_procedure` execution + padw padw padw push.0.0.0 + # => [pad(15)] + + # get the hash of the `get_initial_balance` procedure + procref.::foreign_account_code::get_initial_balance + + # push the foreign account ID + push.{foreign_suffix} push.{foreign_prefix} + # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, pad(15)] + + exec.tx::execute_foreign_procedure + # => [init_foreign_balance] + + # assert that the initial balance of the asset in the foreign account equals 10 + push.10 assert_eq.err=\"Initial balance should be 10\" + # => [] + + # truncate the stack + exec.sys::truncate_stack + end + ", + foreign_prefix = foreign_account.id().prefix().as_felt(), + foreign_suffix = foreign_account.id().suffix(), + ); + + let tx_script = ScriptBuilder::with_source_manager(source_manager.clone()) + .with_dynamically_linked_library(foreign_account_component.library())? + .compile_tx_script(code)?; + + let foreign_account_inputs = mock_chain.get_foreign_account_inputs(foreign_account.id())?; + + mock_chain + .build_tx_context(native_account.id(), &[], &[])? + .foreign_accounts([foreign_account_inputs]) + .enable_lazy_loading() + .tx_script(tx_script) + .with_source_manager(source_manager) + .build()? + .execute() + .await?; + + Ok(()) +} + // NESTED FPI TESTS // ================================================================================================ @@ -1683,9 +1789,10 @@ fn foreign_account_data_memory_assertions(foreign_account: &Account, process: &P } } -/// Test that get_item_init and get_map_item_init work correctly with foreign accounts. +/// Test that get_initial_item and get_initial_map_item work correctly with foreign accounts. #[tokio::test] -async fn test_get_item_init_and_get_map_item_init_with_foreign_account() -> anyhow::Result<()> { +async fn test_get_initial_item_and_get_initial_map_item_with_foreign_account() -> anyhow::Result<()> +{ // Create a native account let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) @@ -1695,19 +1802,19 @@ async fn test_get_item_init_and_get_map_item_init_with_foreign_account() -> anyh let (map_key, map_value) = STORAGE_LEAVES_2[0]; - // Create foreign procedures that test get_item_init and get_map_item_init + // Create foreign procedures that test get_initial_item and get_initial_map_item let foreign_account_code_source = " use.miden::account use.std::sys - export.test_get_item_init + export.test_get_initial_item push.0 - exec.account::get_item_init + exec.account::get_initial_item exec.sys::truncate_stack end - export.test_get_map_item_init - exec.account::get_map_item_init + export.test_get_initial_map_item + exec.account::get_initial_map_item exec.sys::truncate_stack end "; @@ -1739,24 +1846,24 @@ async fn test_get_item_init_and_get_map_item_init_with_foreign_account() -> anyh begin - # Test get_item_init on foreign account + # Test get_initial_item on foreign account padw padw padw push.0.0.0 # => [ pad(4), pad(4), pad(4), 0, 0, 0 ] - procref.::foreign_account::test_get_item_init + procref.::foreign_account::test_get_initial_item push.{foreign_account_id_suffix} push.{foreign_account_id_prefix} exec.tx::execute_foreign_procedure push.{expected_value_slot_0} - assert_eqw.err=\"foreign account get_item_init should work\" + assert_eqw.err=\"foreign account get_initial_item should work\" - # Test get_map_item_init on foreign account + # Test get_initial_map_item on foreign account padw padw push.0.0 push.{map_key} push.1 - procref.::foreign_account::test_get_map_item_init + procref.::foreign_account::test_get_initial_map_item push.{foreign_account_id_suffix} push.{foreign_account_id_prefix} exec.tx::execute_foreign_procedure push.{map_value} - assert_eqw.err=\"foreign account get_map_item_init should work\" + assert_eqw.err=\"foreign account get_initial_map_item should work\" exec.sys::truncate_stack end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 5dae4d8e40..723ffa370d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -283,7 +283,6 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { "\ use.miden::contracts::wallets::basic->wallet use.miden::output_note - use.mock::account # Inputs: [tag, aux, note_type, execution_hint, RECIPIENT] # Outputs: [note_idx] diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index 5b5d788d48..f792c10a36 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -36,15 +36,16 @@ Account procedures can be used to read and write to account storage, add or remo | `compute_current_commitment` | Computes and returns the account commitment from account data stored in memory.

Inputs: `[]`
Outputs: `[ACCOUNT_COMMITMENT]` | Any | | `compute_delta_commitment` | Computes the commitment to the native account's delta. Can only be called from auth procedures.

Inputs: `[]`
Outputs: `[DELTA_COMMITMENT]` | Auth | | `get_item` | Gets an item from the account storage.

Inputs: `[index]`
Outputs: `[VALUE]` | Account | -| `get_item_init` | Gets the initial item from the account storage slot as it was at the beginning of the transaction.

Inputs: `[index]`
Outputs: `[VALUE]` | Account | +| `get_initial_item` | Gets the initial item from the account storage slot as it was at the beginning of the transaction.

Inputs: `[index]`
Outputs: `[VALUE]` | Account | | `set_item` | Sets an item in the account storage.

Inputs: `[index, VALUE]`
Outputs: `[OLD_VALUE]` | Native & Account | | `get_map_item` | Returns the VALUE located under the specified KEY within the map contained in the given account storage slot.

Inputs: `[index, KEY]`
Outputs: `[VALUE]` | Account | -| `get_map_item_init` | Gets the initial VALUE from the account storage map as it was at the beginning of the transaction.

Inputs: `[index, KEY]`
Outputs: `[VALUE]` | Account | +| `get_initial_map_item` | Gets the initial VALUE from the account storage map as it was at the beginning of the transaction.

Inputs: `[index, KEY]`
Outputs: `[VALUE]` | Account | | `set_map_item` | Sets VALUE under the specified KEY within the map contained in the given account storage slot.

Inputs: `[index, KEY, VALUE]`
Outputs: `[OLD_MAP_ROOT, OLD_MAP_VALUE]` | Native & Account | | `get_code_commitment` | Gets the account code commitment of the current account.

Inputs: `[]`
Outputs: `[CODE_COMMITMENT]` | Account | | `get_initial_storage_commitment` | Returns the storage commitment of the native account at the beginning of the transaction.

Inputs: `[]`
Outputs: `[INIT_STORAGE_COMMITMENT]` | Any | | `compute_storage_commitment` | Computes the latest account storage commitment of the current account.

Inputs: `[]`
Outputs: `[STORAGE_COMMITMENT]` | Account | | `get_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault.

Inputs: `[faucet_id_prefix, faucet_id_suffix]`
Outputs: `[balance]` | Any | +| `get_initial_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault at the beginning of the transaction.

Inputs: `[faucet_id_prefix, faucet_id_suffix]`
Outputs: `[init_balance]` | Any | | `has_non_fungible_asset` | Returns a boolean indicating whether the non-fungible asset is present in the current account's vault.

Inputs: `[ASSET]`
Outputs: `[has_asset]` | Any | | `add_asset` | Adds the specified asset to the vault. For fungible assets, returns the total after addition.

Inputs: `[ASSET]`
Outputs: `[ASSET']` | Native & Account | | `remove_asset` | Removes the specified asset from the vault.

Inputs: `[ASSET]`
Outputs: `[ASSET]` | Native & Account | From 18027fe72f85606ef506f8a38f8e6a51f6a0766c Mon Sep 17 00:00:00 2001 From: Marti Date: Fri, 10 Oct 2025 11:15:03 +0200 Subject: [PATCH 083/133] chore: use community health files (#1988) --- .github/ISSUE_TEMPLATE/1-bugreport.yml | 38 ------ .github/ISSUE_TEMPLATE/2-feature-request.yml | 20 --- .github/ISSUE_TEMPLATE/3-task.yml | 36 ----- .github/ISSUE_TEMPLATE/config.yml | 1 - CONTRIBUTING.md | 134 ------------------- 5 files changed, 229 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/1-bugreport.yml delete mode 100644 .github/ISSUE_TEMPLATE/2-feature-request.yml delete mode 100644 .github/ISSUE_TEMPLATE/3-task.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE/1-bugreport.yml b/.github/ISSUE_TEMPLATE/1-bugreport.yml deleted file mode 100644 index 7bcf0aa30a..0000000000 --- a/.github/ISSUE_TEMPLATE/1-bugreport.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: "Bug Report" -description: "File a bug report" -type: "Bug" -body: - - type: markdown - attributes: - value: | - Thank you for taking the time to fill out this bug report! - - type: textarea - id: version - attributes: - label: "Packages versions" - description: "Let us know the versions of any other packages used. For example, which version of the protocol are you using?" - placeholder: "miden-base: 0.1.0" - validations: - required: true - - type: textarea - id: bug-description - attributes: - label: "Bug description" - description: "Describe the behavior you are experiencing." - placeholder: "Tell us what happened and what should have happened." - validations: - required: true - - type: textarea - id: reproduce-steps - attributes: - label: "How can this be reproduced?" - description: "If possible, describe how to replicate the unexpected behavior that you see." - placeholder: "Steps!" - validations: - required: false - - type: textarea - id: logs - attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This is automatically formatted as code, no need for backticks. - render: shell diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.yml b/.github/ISSUE_TEMPLATE/2-feature-request.yml deleted file mode 100644 index bf545356b3..0000000000 --- a/.github/ISSUE_TEMPLATE/2-feature-request.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: "Feature request" -description: "Request new goodies" -type: "Feature" -body: - - type: markdown - attributes: - value: | - Thank you for taking the time to fill a feature request! - - type: textarea - id: scenario-why - attributes: - label: "Feature description" - validations: - required: true - - type: textarea - id: scenario-how - attributes: - label: "Why is this feature needed?" - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/3-task.yml b/.github/ISSUE_TEMPLATE/3-task.yml deleted file mode 100644 index 9aa8cfe5e0..0000000000 --- a/.github/ISSUE_TEMPLATE/3-task.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: "Task" -description: "Work item" -type: "Task" -body: - - type: markdown - attributes: - value: | - A task should be less than a week worth of work! - - type: textarea - id: task-what - attributes: - label: "What should be done?" - placeholder: "Refactor how account storage is loaded into transaction executor" - validations: - required: true - - type: textarea - id: task-how - attributes: - label: "How should it be done?" - placeholder: "Only the data required for transaction execution should be loaded from account storage (i.e., lazy-loading)" - validations: - required: true - - type: textarea - id: task-done - attributes: - label: "When is this task done?" - placeholder: "The task is done when lazy-loading of account storage is implemented" - validations: - required: true - - type: textarea - id: task-related - attributes: - label: "Additional context" - description: "Add context to the tasks. E.g. other related tasks or relevant discussions on PRs/chats." - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 0086358db1..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1 +0,0 @@ -blank_issues_enabled: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index d1d3971cc5..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,134 +0,0 @@ -# Contributing to Miden Base - -#### First off, thanks for taking the time to contribute! - -We want to make contributing to this project as easy and transparent as possible, whether it's: - -- Reporting a [bug](https://github.com/0xMiden/miden-base/issues/new?assignees=&labels=bug&projects=&template=1-bugreport.yml) -- Taking part in [discussions](https://github.com/0xMiden/miden-base/discussions) -- Submitting a [fix](https://github.com/0xMiden/miden-base/pulls) -- Proposing new [features](https://github.com/0xMiden/miden-base/issues/new?assignees=&labels=enhancement&projects=&template=2-feature-request.yml) - -  - -## Flow - -We are using [Github Flow](https://docs.github.com/en/get-started/quickstart/github-flow), so all code changes happen through pull requests from a [forked repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo). - -### Branching - -- The current active branch is `next`. Every branch with a fix/feature must be forked from `next`. - -- The branch name should contain a short issue/feature description separated with hyphens [(kebab-case)](https://en.wikipedia.org/wiki/Letter_case#Kebab_case). - - For example, if the issue title is `Fix functionality X in component Y` then the branch name will be something like: `fix-x-in-y`. - -- New branch should be rebased from `next` before submitting a PR in case there have been changes to avoid merge commits. - i.e. this branches state: - - ``` - A---B---C fix-x-in-y - / - D---E---F---G next - | | - (F, G) changes happened after `fix-x-in-y` forked - ``` - - should become this after rebase: - - ``` - A'--B'--C' fix-x-in-y - / - D---E---F---G next - ``` - - More about rebase [here](https://git-scm.com/docs/git-rebase) and [here](https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase#:~:text=What%20is%20git%20rebase%3F,of%20a%20feature%20branching%20workflow.) - -### Commit messages - -- Commit messages should be written in a short, descriptive manner and be prefixed with tags for the change type and scope (if possible) according to the [semantic commit](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716) scheme. - For example, a new change to the `miden-objects` crate might have the following message: `feat(miden-objects): Added Account deserialization` - -- Also squash commits to logically separated, distinguishable stages to keep git log clean: - - ``` - 7hgf8978g9... Added A to X \ - \ (squash) - gh354354gh... oops, typo --- * ---------> 9fh1f51gh7... feat(X): add A && B - / - 85493g2458... Added B to X / - - - 789fdfffdf... Fixed D in Y \ - \ (squash) - 787g8fgf78... blah blah --- * ---------> 4070df6f00... fix(Y): fixed D && C - / - 9080gf6567... Fixed C in Y / - ``` - -### Code Style and Documentation - -- For documentation in the codebase, we follow the [rustdoc](https://doc.rust-lang.org/rust-by-example/meta/doc.html) convention with no more than 100 characters per line. -- For code sections, we use code separators like the following to a width of 100 characters:: - - ``` - // CODE SECTION HEADER - // ================================================================================ - ``` - -- [Rustfmt](https://github.com/rust-lang/rustfmt), [Clippy](https://github.com/rust-lang/rust-clippy), [Rustdoc](https://doc.rust-lang.org/rustdoc/index.html), [Typos](https://github.com/crate-ci/typos) and [Taplo](https://github.com/tamasfe/taplo) linting is included in CI pipeline. Anyways it's preferable to run linting locally before push. To simplify running these commands in a reproducible manner we use `make` commands, you can install the required tools by running: - - ``` - make install-tools - ``` - - and then run: - - ``` - make lint - ``` - -You can find more information about the `make` commands in the [Makefile](Makefile) - -### Testing - -After writing code different types of tests (unit, integration, end-to-end) are required to make sure that the correct behavior has been achieved and that no bugs have been introduced. You can run tests using the following command: - -``` -make test -``` - -### Versioning - -We use [semver](https://semver.org/) naming convention. - -  - -## Pre-PR checklist - -To make sure all commits adhere to our programming standards we use [pre-commit](https://pre-commit.com/) ([file](.pre-commit-config.yaml)) to run automatic commands on each commit. Please install it and follow the setup instructions for your machine. - -1. Repo forked and branch created from `next` according to the naming convention. -2. Commit messages and code style follow conventions. -3. Tests added for new functionality. -4. Documentation/comments updated for all changes according to our documentation convention. -5. Rustfmt, Clippy, Rustdoc, Typos and TOML-formatting linting passed (Will be run automatically by pre-commit). -6. New branch rebased from `next`. - -  - -## Write bug reports with detail, background, and sample code - -**Great Bug Reports** tend to have: - -- A quick summary and/or background -- Steps to reproduce -- What you expected would happen -- What actually happens -- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) - -  - -## Any contributions you make will be under the MIT Software License - -In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. From f36d05087ee2ef7fa2ec71863a125f3aa0d0a25b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 10 Oct 2025 12:32:06 +0200 Subject: [PATCH 084/133] feat: Use `FastProcessor` and `TransactionExecutorHost` in code tests (#1955) * feat: Use `TransactionExecutorHost` in `CodeExecutor` * feat: Refactor `MockHost` to use `TransactionExecutorHost` * fix: unused method warning * chore: Rename `process` to `exec_output` * feat: Document MockHost setup * chore: Rename `ProcessMemoryExt` * chore: Use ext trait methods * chore: add missing docs * chore: use single block * chore: add changelog * fix: execute_code docs * fix: tx context builder doc test * feat: Make `MemoryViewer` an enum * chore: Add `execute_code_blocking` * fix: toml fmt and non-async doc test * fix: fmt * Apply suggestions from code review Co-authored-by: Marti * fix: rustfmt --------- Co-authored-by: Marti --- CHANGELOG.md | 1 + crates/miden-lib/src/transaction/events.rs | 5 + crates/miden-testing/Cargo.toml | 4 +- crates/miden-testing/src/executor.rs | 69 ++++--- .../miden-testing/src/kernel_tests/tx/mod.rs | 76 +++++--- .../src/kernel_tests/tx/test_account.rs | 88 +++++---- .../src/kernel_tests/tx/test_active_note.rs | 26 +-- .../src/kernel_tests/tx/test_asset.rs | 15 +- .../src/kernel_tests/tx/test_asset_vault.rs | 89 +++++---- .../src/kernel_tests/tx/test_auth.rs | 2 +- .../src/kernel_tests/tx/test_epilogue.rs | 46 +++-- .../src/kernel_tests/tx/test_faucet.rs | 60 +++--- .../src/kernel_tests/tx/test_fpi.rs | 57 +++--- .../src/kernel_tests/tx/test_link_map.rs | 16 +- .../src/kernel_tests/tx/test_note.rs | 42 ++--- .../src/kernel_tests/tx/test_output_note.rs | 57 +++--- .../src/kernel_tests/tx/test_prologue.rs | 157 ++++++++-------- .../src/kernel_tests/tx/test_tx.rs | 12 +- crates/miden-testing/src/lib.rs | 1 - crates/miden-testing/src/mock_host.rs | 177 ++++++++---------- .../miden-testing/src/tx_context/builder.rs | 21 ++- .../miden-testing/src/tx_context/context.rs | 94 ++++++---- crates/miden-testing/src/utils.rs | 9 +- crates/miden-tx/src/host/link_map.rs | 110 ++++++++--- crates/miden-tx/src/host/mod.rs | 2 +- crates/miden-tx/src/lib.rs | 2 +- 26 files changed, 686 insertions(+), 552 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf98901eae..028ed7ff29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ - Simplify `MockChain` internals and rework its documentation ([#1942]https://github.com/0xMiden/miden-base/pull/1942). - [BREAKING] Change the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). - [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). +- [BREAKING] Return `ExecutionOutput` from `TransactionContext::execute_code` ([#1955](https://github.com/0xMiden/miden-base/pull/1955)). - Dynamically lookup all masm `EventId`s from source ([#1954](https://github.com/0xMiden/miden-base/pull/1954)). - [BREAKING] Rename `get_item_init` and `get_map_item_init` to `get_initial_item` and `get_initial_map_item` respectively ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). - [BREAKING] Rename `TransactionInputs` to `TransactionExecutionInputs` and make a new `TransactionInputs` struct which does not contain `InputNotes` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). diff --git a/crates/miden-lib/src/transaction/events.rs b/crates/miden-lib/src/transaction/events.rs index 5f0069bbc7..652830065b 100644 --- a/crates/miden-lib/src/transaction/events.rs +++ b/crates/miden-lib/src/transaction/events.rs @@ -87,6 +87,11 @@ impl TransactionEvent { let is_unprivileged = matches!(self, Self::AuthRequest | Self::Unauthorized); !is_unprivileged } + + /// Returns the [`EventId`] of the transaction event. + pub fn event_id(&self) -> EventId { + EventId::from_u64(self.clone() as u64) + } } impl fmt::Display for TransactionEvent { diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index 01e4fe2fc2..313f6cf893 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -32,7 +32,9 @@ itertools = { default-features = false, features = ["use_alloc"], version = "0 rand = { features = ["os_rng", "small_rng"], workspace = true } rand_chacha = { default-features = false, version = "0.9" } thiserror = { workspace = true } -winterfell = { version = "0.13" } +# TODO: Remove when execute_code_blocking is gone. +tokio = { features = ["macros", "rt"], workspace = true } +winterfell = { version = "0.13" } [dev-dependencies] anyhow = { features = ["backtrace", "std"], workspace = true } diff --git a/crates/miden-testing/src/executor.rs b/crates/miden-testing/src/executor.rs index d3a2eebec3..a8ae18cc2e 100644 --- a/crates/miden-testing/src/executor.rs +++ b/crates/miden-testing/src/executor.rs @@ -1,30 +1,19 @@ -use alloc::borrow::ToOwned; -use alloc::sync::Arc; - -use miden_lib::transaction::TransactionKernel; -use miden_objects::assembly::debuginfo::{SourceLanguage, Uri}; -use miden_objects::assembly::{DefaultSourceManager, SourceManagerSync}; -use miden_processor::{ - AdviceInputs, - DefaultHost, - ExecutionError, - Process, - Program, - StackInputs, - SyncHost, -}; - -// MOCK CODE EXECUTOR +#[cfg(test)] +use miden_processor::DefaultHost; +use miden_processor::fast::{ExecutionOutput, FastProcessor}; +use miden_processor::{AdviceInputs, AsyncHost, ExecutionError, Program, StackInputs}; + +// CODE EXECUTOR // ================================================================================================ /// Helper for executing arbitrary code within arbitrary hosts. -pub struct CodeExecutor { +pub(crate) struct CodeExecutor { host: H, stack_inputs: Option, advice_inputs: AdviceInputs, } -impl CodeExecutor { +impl CodeExecutor { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- pub(crate) fn new(host: H) -> Self { @@ -49,7 +38,15 @@ impl CodeExecutor { /// /// To improve the error message quality, convert the returned [`ExecutionError`] into a /// [`Report`](miden_objects::assembly::diagnostics::Report). - pub fn run(self, code: &str) -> Result { + #[cfg(test)] + pub async fn run(self, code: &str) -> Result { + use alloc::borrow::ToOwned; + use alloc::sync::Arc; + + use miden_lib::transaction::TransactionKernel; + use miden_objects::assembly::debuginfo::{SourceLanguage, Uri}; + use miden_objects::assembly::{DefaultSourceManager, SourceManagerSync}; + let source_manager: Arc = Arc::new(DefaultSourceManager::default()); let assembler = TransactionKernel::with_kernel_library(source_manager.clone()); @@ -58,27 +55,39 @@ impl CodeExecutor { source_manager.load(SourceLanguage::Masm, Uri::new("_user_code"), code.to_owned()); let program = assembler.assemble_program(virtual_source_file).unwrap(); - self.execute_program(program) + self.execute_program(program).await } /// Executes the provided [`Program`] and returns the [`Process`] state. /// /// To improve the error message quality, convert the returned [`ExecutionError`] into a /// [`Report`](miden_objects::assembly::diagnostics::Report). - pub fn execute_program(mut self, program: Program) -> Result { - let mut process = Process::new_debug( - program.kernel().clone(), - self.stack_inputs.unwrap_or_default(), - self.advice_inputs, - ); - process.execute(&program, &mut self.host)?; - - Ok(process) + pub async fn execute_program( + mut self, + program: Program, + ) -> Result { + // This reverses the stack inputs (even though it doesn't look like it does) because the + // fast processor expects the reverse order. + // + // Once we use the FastProcessor for execution and proving, we can change the way these + // inputs are constructed in TransactionKernel::prepare_inputs. + let stack_inputs = + StackInputs::new(self.stack_inputs.unwrap_or_default().iter().copied().collect()) + .unwrap(); + + let processor = FastProcessor::new_debug(stack_inputs.as_slice(), self.advice_inputs); + + let execution_output = processor.execute(&program, &mut self.host).await?; + + Ok(execution_output) } } +#[cfg(test)] impl CodeExecutor { pub fn with_default_host() -> Self { + use miden_lib::transaction::TransactionKernel; + let mut host = DefaultHost::default(); let test_lib = TransactionKernel::library(); diff --git a/crates/miden-testing/src/kernel_tests/tx/mod.rs b/crates/miden-testing/src/kernel_tests/tx/mod.rs index 7a98b01242..95c2dc5aee 100644 --- a/crates/miden-testing/src/kernel_tests/tx/mod.rs +++ b/crates/miden-testing/src/kernel_tests/tx/mod.rs @@ -2,6 +2,8 @@ use alloc::string::String; use anyhow::Context; use miden_lib::transaction::memory::{ + self, + MemoryOffset, NOTE_MEM_SIZE, NUM_OUTPUT_NOTES_PTR, OUTPUT_NOTE_ASSETS_OFFSET, @@ -21,8 +23,9 @@ use miden_objects::testing::account_id::{ }; use miden_objects::testing::storage::prepare_assets; use miden_objects::vm::StackInputs; -use miden_objects::{Felt, Hasher, ONE, Word, ZERO}; -use miden_processor::{ContextId, Process}; +use miden_objects::{Felt, Word, ZERO}; +use miden_processor::ContextId; +use miden_processor::fast::ExecutionOutput; use crate::MockChain; @@ -48,37 +51,60 @@ mod test_tx; // HELPER FUNCTIONS // ================================================================================================ -/// Extension trait for a [`Process`] to conveniently read kernel memory. -pub trait ProcessMemoryExt { - /// Reads a word from transaction kernel memory. - /// - /// # Panics - /// - /// Panics if: - /// - the address is not word-aligned. - /// - the memory location is not initialized. +/// Extension trait for an [`ExecutionOutput`] to conveniently read the stack and kernel memory. +pub trait ExecutionOutputExt { + /// Reads a word from transaction kernel memory or returns [`Word::empty`] if that location is + /// not initialized. fn get_kernel_mem_word(&self, addr: u32) -> Word; - /// Reads a word from transaction kernel memory. - /// - /// # Panics - /// - /// Panics if: - /// - the address is not word-aligned. - fn try_get_kernel_mem_word(&self, addr: u32) -> Option; + /// Reads an element from transaction kernel memory or returns [`ZERO`] if that location is not + /// initialized. + fn get_kernel_mem_element(&self, addr: u32) -> Felt { + // TODO: Use Memory::read_element once it no longer requires &mut self. + // https://github.com/0xMiden/miden-vm/issues/2237 + + // Copy of how Memory::read_element is implemented in Miden VM. + let idx = addr % miden_objects::WORD_SIZE as u32; + let word_addr = addr - idx; + + self.get_kernel_mem_word(word_addr)[idx as usize] + } + + /// Reads an element from the stack. + fn get_stack_element(&self, idx: usize) -> Felt; + + /// Reads a [`Word`] from the stack. + fn get_stack_word(&self, index: usize) -> Word; + + /// Reads the [`Word`] of the input note's memory identified by the index at the provided + /// `offset`. + fn get_note_mem_word(&self, note_idx: u32, offset: MemoryOffset) -> Word { + self.get_kernel_mem_word(input_note_data_ptr(note_idx) + offset) + } } -impl ProcessMemoryExt for Process { +impl ExecutionOutputExt for ExecutionOutput { fn get_kernel_mem_word(&self, addr: u32) -> Word { - self.try_get_kernel_mem_word(addr).expect("expected address to be initialized") - } + let tx_kernel_context = ContextId::root(); + let clk = 0u32; + let err_ctx = (); - fn try_get_kernel_mem_word(&self, addr: u32) -> Option { - self.chiplets - .memory - .get_word(ContextId::root(), addr) + self.memory + .read_word(tx_kernel_context, Felt::from(addr), clk.into(), &err_ctx) .expect("expected address to be word-aligned") } + + fn get_stack_element(&self, index: usize) -> Felt { + *self.stack.get(index).expect("index must be in bounds") + } + + fn get_stack_word(&self, index: usize) -> Word { + self.stack.get_stack_word(index).expect("index must be in bounds") + } +} + +pub fn input_note_data_ptr(note_idx: u32) -> memory::MemoryAddress { + memory::INPUT_NOTE_DATA_SECTION_OFFSET + note_idx * memory::NOTE_MEM_SIZE } /// Returns MASM code that defines a procedure called `create_mock_notes` which creates the notes diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 0770fb9a58..a691fcb0b7 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -52,6 +52,7 @@ use rand_chacha::ChaCha20Rng; use super::{Felt, StackInputs, ZERO}; use crate::executor::CodeExecutor; +use crate::kernel_tests::tx::ExecutionOutputExt; use crate::utils::create_public_p2any_note; use crate::{ Auth, @@ -156,8 +157,8 @@ pub async fn compute_current_commitment() -> miette::Result<()> { // ACCOUNT ID TESTS // ================================================================================================ -#[test] -pub fn test_account_type() -> miette::Result<()> { +#[tokio::test] +async fn test_account_type() -> miette::Result<()> { let procedures = vec![ ("is_fungible_faucet", AccountType::FungibleFaucet), ("is_non_fungible_faucet", AccountType::NonFungibleFaucet), @@ -188,18 +189,19 @@ pub fn test_account_type() -> miette::Result<()> { " ); - let process = CodeExecutor::with_default_host() + let exec_output = CodeExecutor::with_default_host() .stack_inputs( StackInputs::new(vec![account_id.prefix().as_felt()]).into_diagnostic()?, ) - .run(&code)?; + .run(&code) + .await?; let type_matches = account_id.account_type() == expected_type; let expected_result = Felt::from(type_matches); has_type |= type_matches; assert_eq!( - process.stack.get(0), + exec_output.get_stack_element(0), expected_result, "Rust and Masm check on account type diverge. proc: {} account_id: {} account_type: {:?} expected_type: {:?}", procedure, @@ -215,8 +217,8 @@ pub fn test_account_type() -> miette::Result<()> { Ok(()) } -#[test] -pub fn test_account_validate_id() -> miette::Result<()> { +#[tokio::test] +async fn test_account_validate_id() -> miette::Result<()> { let test_cases = [ (ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, None), (ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, None), @@ -260,7 +262,8 @@ pub fn test_account_validate_id() -> miette::Result<()> { let result = CodeExecutor::with_default_host() .stack_inputs(StackInputs::new(vec![suffix, prefix]).unwrap()) - .run(code); + .run(code) + .await; match (result, expected_error) { (Ok(_), None) => (), @@ -289,8 +292,8 @@ pub fn test_account_validate_id() -> miette::Result<()> { Ok(()) } -#[test] -fn test_is_faucet_procedure() -> miette::Result<()> { +#[tokio::test] +async fn test_is_faucet_procedure() -> miette::Result<()> { let test_cases = [ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, @@ -317,13 +320,14 @@ fn test_is_faucet_procedure() -> miette::Result<()> { prefix = account_id.prefix().as_felt(), ); - let process = CodeExecutor::with_default_host() + let exec_output = CodeExecutor::with_default_host() .run(&code) + .await .wrap_err("failed to execute is_faucet procedure")?; let is_faucet = account_id.is_faucet(); assert_eq!( - process.stack.get(0), + exec_output.get_stack_element(0), Felt::new(is_faucet as u64), "Rust and MASM is_faucet diverged for account_id {account_id}" ); @@ -357,7 +361,7 @@ pub fn test_compute_code_commitment() -> miette::Result<()> { expected_code_commitment = account.code().commitment() ); - tx_context.execute_code(&code)?; + tx_context.execute_code_blocking(&code)?; Ok(()) } @@ -391,7 +395,7 @@ fn test_get_item() -> miette::Result<()> { item_value = &storage_item.slot.value(), ); - tx_context.execute_code(&code).unwrap(); + tx_context.execute_code_blocking(&code).unwrap(); } Ok(()) @@ -428,25 +432,25 @@ fn test_get_map_item() -> miette::Result<()> { map_key = &key, ); - let process = &mut tx_context.execute_code(&code)?; + let exec_output = &mut tx_context.execute_code_blocking(&code)?; assert_eq!( + exec_output.get_stack_word(0), value, - process.stack.get_word(0), "get_map_item result doesn't match the expected value", ); assert_eq!( + exec_output.get_stack_word(4), Word::empty(), - process.stack.get_word(4), "The rest of the stack must be cleared", ); assert_eq!( + exec_output.get_stack_word(8), Word::empty(), - process.stack.get_word(8), "The rest of the stack must be cleared", ); assert_eq!( + exec_output.get_stack_word(12), Word::empty(), - process.stack.get_word(12), "The rest of the stack must be cleared", ); } @@ -484,17 +488,17 @@ fn test_get_storage_slot_type() -> miette::Result<()> { item_index = storage_item.index, ); - let process = &tx_context.execute_code(&code).unwrap(); + let exec_output = &tx_context.execute_code_blocking(&code).unwrap(); let storage_slot_type = storage_item.slot.slot_type(); - assert_eq!(storage_slot_type, process.stack.get(0).try_into().unwrap()); - assert_eq!(process.stack.get(1), ZERO, "the rest of the stack is empty"); - assert_eq!(process.stack.get(2), ZERO, "the rest of the stack is empty"); - assert_eq!(process.stack.get(3), ZERO, "the rest of the stack is empty"); - assert_eq!(Word::empty(), process.stack.get_word(1), "the rest of the stack is empty"); - assert_eq!(Word::empty(), process.stack.get_word(2), "the rest of the stack is empty"); - assert_eq!(Word::empty(), process.stack.get_word(3), "the rest of the stack is empty"); + assert_eq!(storage_slot_type, exec_output.get_stack_element(0).try_into().unwrap()); + assert_eq!(exec_output.get_stack_element(1), ZERO, "the rest of the stack is empty"); + assert_eq!(exec_output.get_stack_element(2), ZERO, "the rest of the stack is empty"); + assert_eq!(exec_output.get_stack_element(3), ZERO, "the rest of the stack is empty"); + assert_eq!(exec_output.get_stack_word(4), Word::empty(), "the rest of the stack is empty"); + assert_eq!(exec_output.get_stack_word(8), Word::empty(), "the rest of the stack is empty"); + assert_eq!(exec_output.get_stack_word(12), Word::empty(), "the rest of the stack is empty"); } Ok(()) @@ -533,7 +537,7 @@ fn test_set_item() -> miette::Result<()> { new_storage_item_index = 0, ); - tx_context.execute_code(&code).unwrap(); + tx_context.execute_code_blocking(&code).unwrap(); Ok(()) } @@ -582,19 +586,19 @@ fn test_set_map_item() -> miette::Result<()> { new_value = &new_value, ); - let process = &tx_context.execute_code(&code).unwrap(); + let exec_output = &tx_context.execute_code_blocking(&code).unwrap(); let mut new_storage_map = AccountStorage::mock_map(); new_storage_map.insert(new_key, new_value).unwrap(); assert_eq!( new_storage_map.root(), - process.stack.get_word(0), + exec_output.get_stack_word(0), "get_item must return the new updated value", ); assert_eq!( storage_item.slot.value(), - process.stack.get_word(4), + exec_output.get_stack_word(4), "The original value stored in the map doesn't match the expected value", ); @@ -900,7 +904,7 @@ fn test_get_initial_storage_commitment() -> anyhow::Result<()> { "#, expected_storage_commitment = &tx_context.account().storage().commitment(), ); - tx_context.execute_code(&code)?; + tx_context.execute_code_blocking(&code)?; Ok(()) } @@ -974,7 +978,7 @@ fn test_compute_storage_commitment() -> anyhow::Result<()> { end "#, ); - tx_context.execute_code(&code)?; + tx_context.execute_code_blocking(&code)?; Ok(()) } @@ -1090,7 +1094,7 @@ fn test_get_vault_root() -> anyhow::Result<()> { ", expected_vault_root = &account.vault().root(), ); - tx_context.execute_code(&code)?; + tx_context.execute_code_blocking(&code)?; // get the current vault root account.vault_mut().add_asset(fungible_asset)?; @@ -1118,7 +1122,7 @@ fn test_get_vault_root() -> anyhow::Result<()> { fungible_asset = Word::from(&fungible_asset), expected_vault_root = &account.vault().root(), ); - tx_context.execute_code(&code)?; + tx_context.execute_code_blocking(&code)?; Ok(()) } @@ -1427,11 +1431,15 @@ fn test_authenticate_and_track_procedure() -> miette::Result<()> { // Execution of this code will return an EventError(UnknownAccountProcedure) for procs // that are not in the advice provider. - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); match valid { - true => assert!(process.is_ok(), "A valid procedure must successfully authenticate"), - false => assert!(process.is_err(), "An invalid procedure should fail to authenticate"), + true => { + assert!(exec_output.is_ok(), "A valid procedure must successfully authenticate") + }, + false => { + assert!(exec_output.is_err(), "An invalid procedure should fail to authenticate") + }, } } @@ -1657,7 +1665,7 @@ fn test_get_initial_item() -> miette::Result<()> { expected_initial_value = &AccountStorage::mock_item_0().slot.value(), ); - tx_context.execute_code(&code).unwrap(); + tx_context.execute_code_blocking(&code).unwrap(); Ok(()) } @@ -1728,7 +1736,7 @@ fn test_get_initial_map_item() -> miette::Result<()> { new_value = &new_value, ); - tx_context.execute_code(&code).unwrap(); + tx_context.execute_code_blocking(&code).unwrap(); Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 234c5c819d..675907107e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -24,6 +24,7 @@ use miden_objects::testing::account_id::{ }; use miden_objects::{EMPTY_WORD, Felt, ONE, WORD_SIZE, Word}; +use crate::kernel_tests::tx::ExecutionOutputExt; use crate::utils::create_public_p2any_note; use crate::{ Auth, @@ -113,7 +114,7 @@ async fn test_active_note_get_metadata() -> anyhow::Result<()> { METADATA = Word::from(tx_context.input_notes().get_note(0).note().metadata()) ); - tx_context.execute_code(&code)?; + tx_context.execute_code(&code).await?; Ok(()) } @@ -149,11 +150,12 @@ fn test_active_note_get_sender() -> anyhow::Result<()> { end "; - let process = tx_context.execute_code(code)?; + let exec_output = tx_context.execute_code_blocking(code)?; let sender = tx_context.input_notes().get_note(0).note().metadata().sender(); - assert_eq!(process.stack.get(0), sender.prefix().as_felt()); - assert_eq!(process.stack.get(1), sender.suffix()); + assert_eq!(exec_output.stack[0], sender.prefix().as_felt()); + assert_eq!(exec_output.stack[1], sender.suffix()); + Ok(()) } @@ -290,7 +292,7 @@ fn test_active_note_get_assets() -> anyhow::Result<()> { NOTE_1_ASSET_ASSERTIONS = construct_asset_assertions(notes.get_note(1).note()), ); - tx_context.execute_code(&code)?; + tx_context.execute_code_blocking(&code)?; Ok(()) } @@ -378,7 +380,7 @@ fn test_active_note_get_inputs() -> anyhow::Result<()> { NOTE_0_PTR = 100000000, ); - tx_context.execute_code(&code)?; + tx_context.execute_code_blocking(&code)?; Ok(()) } @@ -457,7 +459,9 @@ fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { end "; - tx_context.execute_code(tx_code).context("transaction execution failed")?; + tx_context + .execute_code_blocking(tx_code) + .context("transaction execution failed")?; Ok(()) } @@ -494,10 +498,10 @@ fn test_active_note_get_serial_number() -> anyhow::Result<()> { end "; - let process = tx_context.execute_code(code)?; + let exec_output = tx_context.execute_code_blocking(code)?; let serial_number = tx_context.input_notes().get_note(0).note().serial_num(); - assert_eq!(process.stack.get_word(0), serial_number); + assert_eq!(exec_output.get_stack_word(0), serial_number); Ok(()) } @@ -533,9 +537,9 @@ fn test_active_note_get_script_root() -> anyhow::Result<()> { end "; - let process = tx_context.execute_code(code)?; + let exec_output = tx_context.execute_code_blocking(code)?; let script_root = tx_context.input_notes().get_note(0).note().script().root(); - assert_eq!(process.stack.get_word(0), script_root); + assert_eq!(exec_output.get_stack_word(0), script_root); Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs index 532c53119d..78efa6d420 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs @@ -6,9 +6,10 @@ use miden_objects::testing::constants::{ FUNGIBLE_FAUCET_INITIAL_BALANCE, NON_FUNGIBLE_ASSET_DATA, }; +use miden_objects::{Felt, Hasher, Word}; -use super::{Felt, Hasher, Word}; use crate::TransactionContextBuilder; +use crate::kernel_tests::tx::ExecutionOutputExt; #[test] fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { @@ -36,11 +37,11 @@ fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { " ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); assert_eq!( - process.stack.get_word(0), + exec_output.get_stack_word(0), Word::from([ Felt::new(FUNGIBLE_ASSET_AMOUNT), Felt::new(0), @@ -78,9 +79,9 @@ fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { non_fungible_asset_data_hash = Hasher::hash(&NON_FUNGIBLE_ASSET_DATA), ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; + assert_eq!(exec_output.get_stack_word(0), Word::from(non_fungible_asset)); - assert_eq!(process.stack.get_word(0), Word::from(non_fungible_asset)); Ok(()) } @@ -106,8 +107,8 @@ fn test_validate_non_fungible_asset() -> anyhow::Result<()> { " ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; - assert_eq!(process.stack.get_word(0), non_fungible_asset); + assert_eq!(exec_output.get_stack_word(0), non_fungible_asset); Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 37e97f1284..1e82deb222 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -7,7 +7,6 @@ use miden_lib::errors::tx_kernel_errors::{ ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND, }; use miden_lib::transaction::memory; -use miden_objects::AssetVaultError; use miden_objects::account::AccountId; use miden_objects::asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; use miden_objects::testing::account_id::{ @@ -16,9 +15,9 @@ use miden_objects::testing::account_id::{ ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1, }; use miden_objects::testing::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}; +use miden_objects::{AssetVaultError, Felt, ONE, Word, ZERO}; -use super::{Felt, ONE, Word, ZERO}; -use crate::kernel_tests::tx::ProcessMemoryExt; +use crate::kernel_tests::tx::ExecutionOutputExt; use crate::{TransactionContextBuilder, assert_execution_error}; /// Tests that account::get_balance returns the correct amount. @@ -47,10 +46,10 @@ fn get_balance_returns_correct_amount() -> anyhow::Result<()> { suffix = faucet_id.suffix(), ); - let process = tx_context.execute_code(&code)?; + let exec_output = tx_context.execute_code_blocking(&code)?; assert_eq!( - process.stack.get(0).as_int(), + exec_output.get_stack_element(0).as_int(), tx_context.account().vault().get_balance(faucet_id).unwrap() ); @@ -88,10 +87,10 @@ fn peek_balance_returns_correct_amount() -> anyhow::Result<()> { suffix = faucet_id.suffix(), ); - let process = tx_context.execute_code(&code)?; + let exec_output = tx_context.execute_code_blocking(&code)?; assert_eq!( - process.stack.get(0).as_int(), + exec_output.get_stack_element(0).as_int(), tx_context.account().vault().get_balance(faucet_id).unwrap() ); @@ -100,7 +99,11 @@ fn peek_balance_returns_correct_amount() -> anyhow::Result<()> { #[test] fn test_get_balance_non_fungible_fails() -> anyhow::Result<()> { - let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; + // Disable lazy loading otherwise the handler will return an error before the transaction kernel + // can abort, which is what we want to test. + let tx_context = TransactionContextBuilder::with_existing_mock_account() + .disable_lazy_loading() + .build()?; let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(); let code = format!( @@ -118,9 +121,12 @@ fn test_get_balance_non_fungible_fails() -> anyhow::Result<()> { suffix = faucet_id.suffix(), ); - let process = tx_context.execute_code(&code); + let exec_result = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_VAULT_GET_BALANCE_CAN_ONLY_BE_CALLED_ON_FUNGIBLE_ASSET); + assert_execution_error!( + exec_result, + ERR_VAULT_GET_BALANCE_CAN_ONLY_BE_CALLED_ON_FUNGIBLE_ASSET + ); Ok(()) } @@ -148,9 +154,9 @@ fn test_has_non_fungible_asset() -> anyhow::Result<()> { non_fungible_asset_key = Word::from(non_fungible_asset) ); - let process = tx_context.execute_code(&code)?; + let exec_output = tx_context.execute_code_blocking(&code)?; - assert_eq!(process.stack.get(0), ONE); + assert_eq!(exec_output.get_stack_element(0), ONE); Ok(()) } @@ -186,15 +192,15 @@ fn test_add_fungible_asset_success() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(add_fungible_asset) ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( - process.stack.get_word(0), - Into::::into(account_vault.add_asset(add_fungible_asset).unwrap()) + exec_output.get_stack_word(0), + Word::from(account_vault.add_asset(add_fungible_asset).unwrap()) ); assert_eq!( - process.get_kernel_mem_word(memory::NATIVE_ACCT_VAULT_ROOT_PTR), + exec_output.get_kernel_mem_word(memory::NATIVE_ACCT_VAULT_ROOT_PTR), account_vault.root() ); @@ -230,9 +236,9 @@ fn test_add_non_fungible_asset_fail_overflow() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(add_fungible_asset) ); - let process = tx_context.execute_code(&code); + let exec_result = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED); + assert_execution_error!(exec_result, ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED); assert!(account_vault.add_asset(add_fungible_asset).is_err()); Ok(()) @@ -264,15 +270,15 @@ fn test_add_non_fungible_asset_success() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(add_non_fungible_asset) ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( - process.stack.get_word(0), - Into::::into(account_vault.add_asset(add_non_fungible_asset)?) + exec_output.get_stack_word(0), + Word::from(account_vault.add_asset(add_non_fungible_asset)?) ); assert_eq!( - process.get_kernel_mem_word(memory::NATIVE_ACCT_VAULT_ROOT_PTR), + exec_output.get_kernel_mem_word(memory::NATIVE_ACCT_VAULT_ROOT_PTR), account_vault.root() ); @@ -303,9 +309,9 @@ fn test_add_non_fungible_asset_fail_duplicate() -> anyhow::Result<()> { NON_FUNGIBLE_ASSET = Word::from(non_fungible_asset) ); - let process = tx_context.execute_code(&code); + let exec_result = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS); + assert_execution_error!(exec_result, ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS); assert!(account_vault.add_asset(non_fungible_asset).is_err()); Ok(()) @@ -343,15 +349,15 @@ fn test_remove_fungible_asset_success_no_balance_remaining() -> anyhow::Result<( FUNGIBLE_ASSET = Word::from(remove_fungible_asset) ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( - process.stack.get_word(0), - Into::::into(account_vault.remove_asset(remove_fungible_asset).unwrap()) + exec_output.get_stack_word(0), + Word::from(account_vault.remove_asset(remove_fungible_asset).unwrap()) ); assert_eq!( - process.get_kernel_mem_word(memory::NATIVE_ACCT_VAULT_ROOT_PTR), + exec_output.get_kernel_mem_word(memory::NATIVE_ACCT_VAULT_ROOT_PTR), account_vault.root() ); @@ -385,9 +391,12 @@ fn test_remove_fungible_asset_fail_remove_too_much() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(remove_fungible_asset) ); - let process = tx_context.execute_code(&code); + let exec_result = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW); + assert_execution_error!( + exec_result, + ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW + ); Ok(()) } @@ -424,15 +433,15 @@ fn test_remove_fungible_asset_success_balance_remaining() -> anyhow::Result<()> FUNGIBLE_ASSET = Word::from(remove_fungible_asset) ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( - process.stack.get_word(0), - Into::::into(account_vault.remove_asset(remove_fungible_asset).unwrap()) + exec_output.get_stack_word(0), + Word::from(account_vault.remove_asset(remove_fungible_asset).unwrap()) ); assert_eq!( - process.get_kernel_mem_word(memory::NATIVE_ACCT_VAULT_ROOT_PTR), + exec_output.get_kernel_mem_word(memory::NATIVE_ACCT_VAULT_ROOT_PTR), account_vault.root() ); @@ -470,9 +479,9 @@ fn test_remove_inexisting_non_fungible_asset_fails() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(non_existent_non_fungible_asset) ); - let process = tx_context.execute_code(&code); + let exec_result = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND); + assert_execution_error!(exec_result, ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND); assert_matches!( account_vault.remove_asset(non_existent_non_fungible_asset).unwrap_err(), AssetVaultError::NonFungibleAssetNotFound(err_asset) if err_asset == nonfungible, @@ -509,15 +518,15 @@ fn test_remove_non_fungible_asset_success() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(non_fungible_asset) ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( - process.stack.get_word(0), - Into::::into(account_vault.remove_asset(non_fungible_asset).unwrap()) + exec_output.get_stack_word(0), + Word::from(account_vault.remove_asset(non_fungible_asset).unwrap()) ); assert_eq!( - process.get_kernel_mem_word(memory::NATIVE_ACCT_VAULT_ROOT_PTR), + exec_output.get_kernel_mem_word(memory::NATIVE_ACCT_VAULT_ROOT_PTR), account_vault.root() ); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs index 1be61e13fa..676bd9f4e9 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs @@ -7,8 +7,8 @@ use miden_lib::testing::mock_account::MockAccountExt; use miden_lib::utils::ScriptBuilder; use miden_objects::account::{Account, AccountBuilder}; use miden_objects::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; +use miden_objects::{Felt, ONE}; -use super::{Felt, ONE}; use crate::{Auth, TransactionContextBuilder, assert_transaction_executor_error}; pub const ERR_WRONG_ARGS: MasmError = MasmError::from_static_str(ERR_WRONG_ARGS_MSG); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 8d52fd26c3..4e4b80b1d6 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -38,7 +38,7 @@ use miden_processor::{Felt, ONE}; use rand::rng; use super::{ZERO, create_mock_notes_procedure}; -use crate::kernel_tests::tx::ProcessMemoryExt; +use crate::kernel_tests::tx::ExecutionOutputExt; use crate::utils::{create_public_p2any_note, create_spawn_note}; use crate::{ Auth, @@ -94,7 +94,7 @@ fn test_epilogue() -> anyhow::Result<()> { " ); - let process = tx_context.execute_code(&code)?; + let exec_output = tx_context.execute_code_blocking(&code)?; // The final account is the initial account with the nonce incremented by one. let mut final_account = account.clone(); @@ -138,13 +138,13 @@ fn test_epilogue() -> anyhow::Result<()> { expected_stack.extend((13..16).map(|_| ZERO)); assert_eq!( - *process.stack.build_stack_outputs()?, + exec_output.stack.as_slice(), expected_stack.as_slice(), "Stack state after finalize_transaction does not contain the expected values" ); assert_eq!( - process.stack.depth(), + exec_output.stack.len(), 16, "The stack must be truncated to 16 elements after finalize_transaction" ); @@ -191,11 +191,11 @@ fn test_compute_output_note_id() -> anyhow::Result<()> { " ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( note.assets().commitment(), - process.get_kernel_mem_word( + exec_output.get_kernel_mem_word( OUTPUT_NOTE_SECTION_OFFSET + i * NOTE_MEM_SIZE + OUTPUT_NOTE_ASSET_COMMITMENT_OFFSET @@ -205,7 +205,7 @@ fn test_compute_output_note_id() -> anyhow::Result<()> { assert_eq!( Word::from(note.id()), - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + i * NOTE_MEM_SIZE), + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + i * NOTE_MEM_SIZE), "NOTE_ID didn't match expected value", ); } @@ -271,9 +271,9 @@ fn test_epilogue_asset_preservation_violation_too_few_input() -> anyhow::Result< " ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME); + assert_execution_error!(exec_output, ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME); Ok(()) } @@ -345,9 +345,9 @@ fn test_epilogue_asset_preservation_violation_too_many_fungible_input() -> anyho " ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME); + assert_execution_error!(exec_output, ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME); Ok(()) } @@ -384,13 +384,16 @@ fn test_block_expiration_height_monotonically_decreases() -> anyhow::Result<()> .replace("{value_2}", &v2.to_string()) .replace("{min_value}", &v2.min(v1).to_string()); - let process = &tx_context.execute_code(code)?; + let exec_output = &tx_context.execute_code_blocking(code)?; // Expiry block should be set to transaction's block + the stored expiration delta // (which can only decrease, not increase) let expected_expiry = v1.min(v2) + tx_context.tx_inputs().block_header().block_num().as_u64(); - assert_eq!(process.stack.get(EXPIRATION_BLOCK_ELEMENT_IDX).as_int(), expected_expiry); + assert_eq!( + exec_output.get_stack_element(EXPIRATION_BLOCK_ELEMENT_IDX).as_int(), + expected_expiry + ); } Ok(()) @@ -412,9 +415,9 @@ fn test_invalid_expiration_deltas() -> anyhow::Result<()> { for value in test_values { let code = &code_template.replace("{value_1}", &value.to_string()); - let process = tx_context.execute_code(code); + let exec_output = tx_context.execute_code_blocking(code); - assert_execution_error!(process, ERR_TX_INVALID_EXPIRATION_DELTA); + assert_execution_error!(exec_output, ERR_TX_INVALID_EXPIRATION_DELTA); } Ok(()) @@ -442,10 +445,13 @@ fn test_no_expiration_delta_set() -> anyhow::Result<()> { end "; - let process = &tx_context.execute_code(code_template)?; + let exec_output = &tx_context.execute_code_blocking(code_template)?; - // Default value should be equal to u32::max, set in the prologue - assert_eq!(process.stack.get(EXPIRATION_BLOCK_ELEMENT_IDX).as_int() as u32, u32::MAX); + // Default value should be equal to u32::MAX, set in the prologue + assert_eq!( + exec_output.get_stack_element(EXPIRATION_BLOCK_ELEMENT_IDX).as_int() as u32, + u32::MAX + ); Ok(()) } @@ -482,7 +488,7 @@ fn test_epilogue_increment_nonce_success() -> anyhow::Result<()> { " ); - tx_context.execute_code(code.as_str())?; + tx_context.execute_code_blocking(code.as_str())?; Ok(()) } @@ -583,7 +589,7 @@ fn test_epilogue_empty_transaction_with_empty_output_note() -> anyhow::Result<() let tx_context = TransactionContextBuilder::with_noop_auth_account().build()?; - let result = tx_context.execute_code(&tx_script_source).map(|_| ()); + let result = tx_context.execute_code_blocking(&tx_script_source).map(|_| ()); // assert that even if the output note was created, the transaction is considered empty assert_execution_error!(result, ERR_EPILOGUE_EXECUTED_TRANSACTION_IS_EMPTY); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index 51cdd6dd54..348a8cb343 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -39,6 +39,7 @@ use miden_objects::testing::noop_auth_component::NoopAuthComponent; use miden_objects::testing::storage::FAUCET_STORAGE_DATA_SLOT; use miden_objects::{Felt, Word}; +use crate::kernel_tests::tx::ExecutionOutputExt; use crate::utils::create_public_p2any_note; use crate::{TransactionContextBuilder, assert_execution_error, assert_transaction_executor_error}; @@ -82,19 +83,15 @@ fn test_mint_fungible_asset_succeeds() -> anyhow::Result<()> { suffix = faucet_id.suffix(), ); - let process = &tx_context.execute_code(&code).unwrap(); + let exec_output = &tx_context.execute_code_blocking(&code).unwrap(); let expected_final_storage_amount = FUNGIBLE_FAUCET_INITIAL_BALANCE + FUNGIBLE_ASSET_AMOUNT; let faucet_reserved_slot_storage_location = FAUCET_STORAGE_DATA_SLOT as u32 + NATIVE_ACCT_STORAGE_SLOTS_SECTION_PTR; let faucet_storage_amount_location = faucet_reserved_slot_storage_location + 3; - let faucet_storage_amount = process - .chiplets - .memory - .get_value(process.system.ctx(), faucet_storage_amount_location) - .unwrap() - .as_int(); + let faucet_storage_amount = + exec_output.get_kernel_mem_element(faucet_storage_amount_location).as_int(); assert_eq!(faucet_storage_amount, expected_final_storage_amount); Ok(()) @@ -150,9 +147,9 @@ fn test_mint_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> { asset = Word::from(FungibleAsset::mock(5)) ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); + assert_execution_error!(exec_output, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); Ok(()) } @@ -239,7 +236,7 @@ fn test_mint_non_fungible_asset_succeeds() -> anyhow::Result<()> { asset_vault_key = StorageMap::hash_key(asset_vault_key), ); - tx_context.execute_code(&code)?; + tx_context.execute_code_blocking(&code)?; Ok(()) } @@ -266,9 +263,9 @@ fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow::Result non_fungible_asset = Word::from(non_fungible_asset) ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); + assert_execution_error!(exec_output, ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); Ok(()) } @@ -322,9 +319,9 @@ fn test_mint_non_fungible_asset_fails_asset_already_exists() -> anyhow::Result<( non_fungible_asset = Word::from(non_fungible_asset) ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_FAUCET_NON_FUNGIBLE_ASSET_ALREADY_ISSUED); + assert_execution_error!(exec_output, ERR_FAUCET_NON_FUNGIBLE_ASSET_ALREADY_ISSUED); Ok(()) } @@ -379,19 +376,15 @@ fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { final_input_vault_asset_amount = CONSUMED_ASSET_1_AMOUNT - FUNGIBLE_ASSET_AMOUNT, ); - let process = &tx_context.execute_code(&code).unwrap(); + let exec_output = &tx_context.execute_code_blocking(&code).unwrap(); let expected_final_storage_amount = FUNGIBLE_FAUCET_INITIAL_BALANCE - FUNGIBLE_ASSET_AMOUNT; let faucet_reserved_slot_storage_location = FAUCET_STORAGE_DATA_SLOT as u32 + NATIVE_ACCT_STORAGE_SLOTS_SECTION_PTR; let faucet_storage_amount_location = faucet_reserved_slot_storage_location + 3; - let faucet_storage_amount = process - .chiplets - .memory - .get_value(process.system.ctx(), faucet_storage_amount_location) - .unwrap() - .as_int(); + let faucet_storage_amount = + exec_output.get_kernel_mem_element(faucet_storage_amount_location).as_int(); assert_eq!(faucet_storage_amount, expected_final_storage_amount); Ok(()) @@ -450,9 +443,9 @@ fn test_burn_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> { suffix = faucet_id.suffix(), ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); + assert_execution_error!(exec_output, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); Ok(()) } @@ -482,9 +475,12 @@ fn test_burn_fungible_asset_insufficient_input_amount() -> anyhow::Result<()> { saturating_amount = CONSUMED_ASSET_1_AMOUNT + 1 ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW); + assert_execution_error!( + exec_output, + ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW + ); Ok(()) } @@ -556,7 +552,7 @@ fn test_burn_non_fungible_asset_succeeds() -> anyhow::Result<()> { burnt_asset_vault_key = burnt_asset_vault_key, ); - tx_context.execute_code(&code).unwrap(); + tx_context.execute_code_blocking(&code).unwrap(); Ok(()) } @@ -583,9 +579,9 @@ fn test_burn_non_fungible_asset_fails_does_not_exist() -> anyhow::Result<()> { non_fungible_asset = Word::from(non_fungible_asset_burnt) ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_FAUCET_NON_FUNGIBLE_ASSET_TO_BURN_NOT_FOUND); + assert_execution_error!(exec_output, ERR_FAUCET_NON_FUNGIBLE_ASSET_TO_BURN_NOT_FOUND); Ok(()) } @@ -642,9 +638,9 @@ fn test_burn_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow::Result non_fungible_asset = Word::from(non_fungible_asset_burnt) ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_FAUCET_NON_FUNGIBLE_ASSET_TO_BURN_NOT_FOUND); + assert_execution_error!(exec_output, ERR_FAUCET_NON_FUNGIBLE_ASSET_TO_BURN_NOT_FOUND); Ok(()) } @@ -689,7 +685,7 @@ fn test_is_non_fungible_asset_issued_succeeds() -> anyhow::Result<()> { non_fungible_asset_2 = Word::from(non_fungible_asset_2), ); - tx_context.execute_code(&code).unwrap(); + tx_context.execute_code_blocking(&code).unwrap(); Ok(()) } @@ -723,7 +719,7 @@ fn test_get_total_issuance_succeeds() -> anyhow::Result<()> { "#, ); - tx_context.execute_code(&code).unwrap(); + tx_context.execute_code_blocking(&code).unwrap(); Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index 9bdc6f5982..dfbb38a7e8 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -22,7 +22,6 @@ use miden_lib::transaction::memory::{ NUM_ACCT_STORAGE_SLOTS_OFFSET, }; use miden_lib::utils::ScriptBuilder; -use miden_objects::FieldElement; use miden_objects::account::{ Account, AccountBuilder, @@ -43,13 +42,14 @@ use miden_objects::testing::account_id::{ }; use miden_objects::testing::storage::STORAGE_LEAVES_2; use miden_objects::transaction::AccountInputs; +use miden_objects::{FieldElement, Word, ZERO}; +use miden_processor::fast::ExecutionOutput; use miden_processor::{AdviceInputs, Felt}; use miden_tx::LocalTransactionProver; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; -use super::{Process, Word, ZERO}; -use crate::kernel_tests::tx::ProcessMemoryExt; +use crate::kernel_tests::tx::ExecutionOutputExt; use crate::{Auth, MockChainBuilder, assert_execution_error, assert_transaction_executor_error}; // SIMPLE FPI TESTS @@ -160,15 +160,15 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { get_item_foreign_hash = foreign_account.code().procedures()[1].mast_root(), ); - let process = tx_context.execute_code(&code)?; + let exec_output = tx_context.execute_code_blocking(&code)?; assert_eq!( - process.stack.get_word(0), + exec_output.get_stack_word(0), storage_slots[0].value(), "Value at the top of the stack (value in the storage at index 0) should be equal [1, 2, 3, 4]", ); - foreign_account_data_memory_assertions(&foreign_account, &process); + foreign_account_data_memory_assertions(&foreign_account, &exec_output); // GET MAP ITEM // -------------------------------------------------------------------------------------------- @@ -214,15 +214,15 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { get_map_item_foreign_hash = foreign_account.code().procedures()[2].mast_root(), ); - let process = tx_context.execute_code(&code).unwrap(); + let exec_output = tx_context.execute_code_blocking(&code).unwrap(); assert_eq!( - process.stack.get_word(0), + exec_output.get_stack_word(0), STORAGE_LEAVES_2[0].1, "Value at the top of the stack should be equal [1, 2, 3, 4]", ); - foreign_account_data_memory_assertions(&foreign_account, &process); + foreign_account_data_memory_assertions(&foreign_account, &exec_output); // GET ITEM TWICE // -------------------------------------------------------------------------------------------- @@ -284,7 +284,7 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { get_item_foreign_hash = foreign_account.code().procedures()[1].mast_root(), ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; // Check that the second invocation of the foreign procedure from the same account does not load // the account data again: already loaded data should be reused. @@ -293,8 +293,8 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { // Foreign account: [16384; 24575] <- initialized during first FPI // Next account slot: [24576; 32767] <- should not be initialized assert_eq!( - process.try_get_kernel_mem_word(NATIVE_ACCOUNT_DATA_PTR + ACCOUNT_DATA_LENGTH as u32 * 2), - None, + exec_output.get_kernel_mem_word(NATIVE_ACCOUNT_DATA_PTR + ACCOUNT_DATA_LENGTH as u32 * 2), + Word::empty(), "Memory starting from 24576 should stay uninitialized" ); Ok(()) @@ -467,7 +467,7 @@ fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { foreign_2_suffix = foreign_account_2.id().suffix(), ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; // Check the correctness of the memory layout after multiple foreign procedure invocations from // different foreign accounts @@ -479,7 +479,7 @@ fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { // check that the first word of the first foreign account slot is correct assert_eq!( - process.get_kernel_mem_word(NATIVE_ACCOUNT_DATA_PTR + ACCOUNT_DATA_LENGTH as u32), + exec_output.get_kernel_mem_word(NATIVE_ACCOUNT_DATA_PTR + ACCOUNT_DATA_LENGTH as u32), Word::new([ foreign_account_1.id().suffix(), foreign_account_1.id().prefix().as_felt(), @@ -490,7 +490,7 @@ fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { // check that the first word of the second foreign account slot is correct assert_eq!( - process.get_kernel_mem_word(NATIVE_ACCOUNT_DATA_PTR + ACCOUNT_DATA_LENGTH as u32 * 2), + exec_output.get_kernel_mem_word(NATIVE_ACCOUNT_DATA_PTR + ACCOUNT_DATA_LENGTH as u32 * 2), Word::new([ foreign_account_2.id().suffix(), foreign_account_2.id().prefix().as_felt(), @@ -501,8 +501,8 @@ fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { // check that the first word of the third foreign account slot was not initialized assert_eq!( - process.try_get_kernel_mem_word(NATIVE_ACCOUNT_DATA_PTR + ACCOUNT_DATA_LENGTH as u32 * 3), - None, + exec_output.get_kernel_mem_word(NATIVE_ACCOUNT_DATA_PTR + ACCOUNT_DATA_LENGTH as u32 * 3), + Word::empty(), "Memory starting from 32768 should stay uninitialized" ); @@ -1488,7 +1488,7 @@ async fn test_fpi_stale_account() -> anyhow::Result<()> { foreign_suffix = foreign_account.id().suffix(), ); - let result = tx_context.execute_code(&code).map(|_| ()); + let result = tx_context.execute_code(&code).await.map(|_| ()); assert_execution_error!(result, ERR_FOREIGN_ACCOUNT_INVALID_COMMITMENT); Ok(()) } @@ -1722,11 +1722,14 @@ async fn test_fpi_get_account_nonce() -> anyhow::Result<()> { // HELPER FUNCTIONS // ================================================================================================ -fn foreign_account_data_memory_assertions(foreign_account: &Account, process: &Process) { +fn foreign_account_data_memory_assertions( + foreign_account: &Account, + exec_output: &ExecutionOutput, +) { let foreign_account_data_ptr = NATIVE_ACCOUNT_DATA_PTR + ACCOUNT_DATA_LENGTH as u32; assert_eq!( - process.get_kernel_mem_word(foreign_account_data_ptr + ACCT_ID_AND_NONCE_OFFSET), + exec_output.get_kernel_mem_word(foreign_account_data_ptr + ACCT_ID_AND_NONCE_OFFSET), Word::new([ foreign_account.id().suffix(), foreign_account.id().prefix().as_felt(), @@ -1736,22 +1739,22 @@ fn foreign_account_data_memory_assertions(foreign_account: &Account, process: &P ); assert_eq!( - process.get_kernel_mem_word(foreign_account_data_ptr + ACCT_VAULT_ROOT_OFFSET), + exec_output.get_kernel_mem_word(foreign_account_data_ptr + ACCT_VAULT_ROOT_OFFSET), foreign_account.vault().root(), ); assert_eq!( - process.get_kernel_mem_word(foreign_account_data_ptr + ACCT_STORAGE_COMMITMENT_OFFSET), + exec_output.get_kernel_mem_word(foreign_account_data_ptr + ACCT_STORAGE_COMMITMENT_OFFSET), foreign_account.storage().commitment(), ); assert_eq!( - process.get_kernel_mem_word(foreign_account_data_ptr + ACCT_CODE_COMMITMENT_OFFSET), + exec_output.get_kernel_mem_word(foreign_account_data_ptr + ACCT_CODE_COMMITMENT_OFFSET), foreign_account.code().commitment(), ); assert_eq!( - process.get_kernel_mem_word(foreign_account_data_ptr + NUM_ACCT_STORAGE_SLOTS_OFFSET), + exec_output.get_kernel_mem_word(foreign_account_data_ptr + NUM_ACCT_STORAGE_SLOTS_OFFSET), Word::from([u16::try_from(foreign_account.storage().slots().len()).unwrap(), 0, 0, 0]), ); @@ -1762,7 +1765,7 @@ fn foreign_account_data_memory_assertions(foreign_account: &Account, process: &P .enumerate() { assert_eq!( - process.get_kernel_mem_word( + exec_output.get_kernel_mem_word( foreign_account_data_ptr + ACCT_STORAGE_SLOTS_SECTION_OFFSET + (i as u32) * 4 ), Word::try_from(elements).unwrap(), @@ -1770,7 +1773,7 @@ fn foreign_account_data_memory_assertions(foreign_account: &Account, process: &P } assert_eq!( - process.get_kernel_mem_word(foreign_account_data_ptr + NUM_ACCT_PROCEDURES_OFFSET), + exec_output.get_kernel_mem_word(foreign_account_data_ptr + NUM_ACCT_PROCEDURES_OFFSET), Word::from([u16::try_from(foreign_account.code().num_procedures()).unwrap(), 0, 0, 0]), ); @@ -1781,7 +1784,7 @@ fn foreign_account_data_memory_assertions(foreign_account: &Account, process: &P .enumerate() { assert_eq!( - process.get_kernel_mem_word( + exec_output.get_kernel_mem_word( foreign_account_data_ptr + ACCT_PROCEDURES_SECTION_OFFSET + (i as u32) * 4 ), Word::try_from(elements).unwrap(), diff --git a/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs b/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs index bdd31fa066..449ac9d296 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs @@ -4,8 +4,8 @@ use std::string::String; use anyhow::Context; use miden_objects::{EMPTY_WORD, LexicographicWord, Word}; -use miden_processor::{ONE, ProcessState, ZERO}; -use miden_tx::LinkMap; +use miden_processor::{ONE, ZERO}; +use miden_tx::{LinkMap, MemoryViewer}; use rand::seq::IteratorRandom; use winter_rand_utils::rand_value; @@ -173,10 +173,10 @@ fn insertion() -> anyhow::Result<()> { ); let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - let mut process = tx_context.execute_code(&code).context("failed to execute code")?; - let state = ProcessState::from(&mut process); + let exec_output = tx_context.execute_code_blocking(&code).context("failed to execute code")?; + let mem_viewer = MemoryViewer::ExecutionOutputs(&exec_output); - let map = LinkMap::new(map_ptr.into(), &state); + let map = LinkMap::new(map_ptr.into(), &mem_viewer); let mut map_iter = map.iter(); let entry0 = map_iter.next().expect("map should have four entries"); @@ -542,11 +542,11 @@ fn execute_link_map_test(operations: Vec) -> anyhow::Result<()> { ); let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - let mut process = tx_context.execute_code(&code).context("failed to execute code")?; - let state = ProcessState::from(&mut process); + let exec_output = tx_context.execute_code_blocking(&code).context("failed to execute code")?; + let mem_viewer = MemoryViewer::ExecutionOutputs(&exec_output); for (map_ptr, control_map) in control_maps { - let map = LinkMap::new(map_ptr.into(), &state); + let map = LinkMap::new(map_ptr.into(), &mem_viewer); let actual_map_len = map.iter().count(); assert_eq!( actual_map_len, diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 11d4451165..93799f299f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -32,12 +32,11 @@ use miden_objects::testing::account_id::{ }; use miden_objects::transaction::{AccountInputs, OutputNote, TransactionArgs}; use miden_objects::{Felt, Word, ZERO}; +use miden_processor::fast::ExecutionOutput; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; -use super::Process; -use crate::kernel_tests::tx::ProcessMemoryExt; -use crate::utils::input_note_data_ptr; +use crate::kernel_tests::tx::{ExecutionOutputExt, input_note_data_ptr}; use crate::{ Auth, MockChain, @@ -82,10 +81,10 @@ fn test_note_setup() -> anyhow::Result<()> { end "; - let process = tx_context.execute_code(code)?; + let exec_output = tx_context.execute_code_blocking(code)?; - note_setup_stack_assertions(&process, &tx_context); - note_setup_memory_assertions(&process); + note_setup_stack_assertions(&exec_output, &tx_context); + note_setup_memory_assertions(&exec_output); Ok(()) } @@ -163,15 +162,15 @@ fn test_note_script_and_note_args() -> miette::Result<()> { .with_note_args(note_args_map); tx_context.set_tx_args(tx_args); - let process = tx_context.execute_code(code).unwrap(); + let exec_output = tx_context.execute_code_blocking(code).unwrap(); - assert_eq!(process.stack.get_word(0), note_args[0]); - assert_eq!(process.stack.get_word(4), note_args[1]); + assert_eq!(exec_output.get_stack_word(0), note_args[0]); + assert_eq!(exec_output.get_stack_word(4), note_args[1]); Ok(()) } -fn note_setup_stack_assertions(process: &Process, inputs: &TransactionContext) { +fn note_setup_stack_assertions(exec_output: &ExecutionOutput, inputs: &TransactionContext) { let mut expected_stack = [ZERO; 16]; // replace the top four elements with the tx script root @@ -180,13 +179,13 @@ fn note_setup_stack_assertions(process: &Process, inputs: &TransactionContext) { expected_stack[..4].copy_from_slice(¬e_script_root); // assert that the stack contains the note inputs at the end of execution - assert_eq!(process.stack.trace_state(), expected_stack) + assert_eq!(exec_output.stack.as_slice(), expected_stack.as_slice()) } -fn note_setup_memory_assertions(process: &Process) { +fn note_setup_memory_assertions(exec_output: &ExecutionOutput) { // assert that the correct pointer is stored in bookkeeping memory assert_eq!( - process.get_kernel_mem_word(ACTIVE_INPUT_NOTE_PTR)[0], + exec_output.get_kernel_mem_word(ACTIVE_INPUT_NOTE_PTR)[0], Felt::from(input_note_data_ptr(0)) ); } @@ -256,7 +255,7 @@ fn test_build_recipient() -> anyhow::Result<()> { serial_num = serial_num, ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; // Create expected recipients and get their digests let note_inputs_4 = NoteInputs::new(word_1.to_vec())?; @@ -280,7 +279,7 @@ fn test_build_recipient() -> anyhow::Result<()> { expected_stack.extend_from_slice(recipient_13.digest().as_elements()); expected_stack.reverse(); - assert_eq!(process.stack.get_state_at(process.system.clk())[0..12], expected_stack); + assert_eq!(exec_output.stack[0..12], expected_stack); Ok(()) } @@ -344,7 +343,7 @@ fn test_compute_inputs_commitment() -> anyhow::Result<()> { addr_3 = BASE_ADDR + 12, ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; let mut inputs_5 = word_1.to_vec(); inputs_5.push(word_2[0]); @@ -368,7 +367,7 @@ fn test_compute_inputs_commitment() -> anyhow::Result<()> { expected_stack.extend_from_slice(Word::empty().as_elements()); expected_stack.reverse(); - assert_eq!(process.stack.get_state_at(process.system.clk())[0..16], expected_stack); + assert_eq!(exec_output.stack[0..16], expected_stack); Ok(()) } @@ -421,14 +420,9 @@ fn test_build_metadata() -> miette::Result<()> { tag = test_metadata.tag(), ); - let process = tx_context.execute_code(&code).unwrap(); + let exec_output = tx_context.execute_code_blocking(&code).unwrap(); - let metadata_word = Word::new([ - process.stack.get(3), - process.stack.get(2), - process.stack.get(1), - process.stack.get(0), - ]); + let metadata_word = exec_output.get_stack_word(0); assert_eq!(Word::from(test_metadata), metadata_word, "failed in iteration {iteration}"); } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 03ec1ae3c0..b4f4eb5417 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -47,7 +47,7 @@ use miden_objects::transaction::{OutputNote, OutputNotes}; use miden_objects::{Felt, Word, ZERO}; use super::{TestSetup, setup_test}; -use crate::kernel_tests::tx::ProcessMemoryExt; +use crate::kernel_tests::tx::ExecutionOutputExt; use crate::utils::create_public_p2any_note; use crate::{Auth, MockChain, TransactionContextBuilder, assert_execution_error}; @@ -87,16 +87,16 @@ fn test_create_note() -> anyhow::Result<()> { tag = tag, ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( - process.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), + exec_output.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), Word::from([1, 0, 0, 0u32]), "number of output notes must increment by 1", ); assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_RECIPIENT_OFFSET), + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_RECIPIENT_OFFSET), recipient, "recipient must be stored at the correct memory location", ); @@ -111,13 +111,13 @@ fn test_create_note() -> anyhow::Result<()> { .into(); assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET), + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET), expected_note_metadata, "metadata must be stored at the correct memory location", ); assert_eq!( - process.stack.get(0), + exec_output.get_stack_element(0), ZERO, "top item on the stack is the index of the output note" ); @@ -132,10 +132,10 @@ fn test_create_note_with_invalid_tag() -> anyhow::Result<()> { let valid_tag: Felt = NoteTag::for_local_use_case(0, 0).unwrap().into(); // Test invalid tag - assert!(tx_context.execute_code(¬e_creation_script(invalid_tag)).is_err()); + assert!(tx_context.execute_code_blocking(¬e_creation_script(invalid_tag)).is_err()); // Test valid tag - assert!(tx_context.execute_code(¬e_creation_script(valid_tag)).is_ok()); + assert!(tx_context.execute_code_blocking(¬e_creation_script(valid_tag)).is_ok()); Ok(()) } @@ -200,9 +200,9 @@ fn test_create_note_too_many_notes() -> anyhow::Result<()> { aux = ZERO, ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT); + assert_execution_error!(exec_output, ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT); Ok(()) } @@ -362,23 +362,24 @@ fn test_get_output_notes_commitment() -> anyhow::Result<()> { ), ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( - process.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), + exec_output.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), Word::from([2u32, 0, 0, 0]), "The test creates two notes", ); assert_eq!( NoteMetadata::try_from( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET) + exec_output + .get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET) ) .unwrap(), *output_note_1.metadata(), "Validate the output note 1 metadata", ); assert_eq!( - NoteMetadata::try_from(process.get_kernel_mem_word( + NoteMetadata::try_from(exec_output.get_kernel_mem_word( OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET + NOTE_MEM_SIZE )) .unwrap(), @@ -386,7 +387,7 @@ fn test_get_output_notes_commitment() -> anyhow::Result<()> { "Validate the output note 1 metadata", ); - assert_eq!(process.stack.get_word(0), expected_output_notes_commitment); + assert_eq!(exec_output.get_stack_word(0), expected_output_notes_commitment); Ok(()) } @@ -437,16 +438,16 @@ fn test_create_note_and_add_asset() -> anyhow::Result<()> { asset = asset, ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), asset, "asset must be stored at the correct memory location", ); assert_eq!( - process.stack.get(0), + exec_output.get_stack_element(0), ZERO, "top item on the stack is the index to the output note" ); @@ -519,28 +520,28 @@ fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { nft = non_fungible_asset_encoded, ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), asset, "asset must be stored at the correct memory location", ); assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 4), + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 4), asset_2_and_3, "asset_2 and asset_3 must be stored at the same correct memory location", ); assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 8), + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 8), non_fungible_asset_encoded, "non_fungible_asset must be stored at the correct memory location", ); assert_eq!( - process.stack.get(0), + exec_output.get_stack_element(0), ZERO, "top item on the stack is the index to the output note" ); @@ -595,9 +596,9 @@ fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { nft = encoded, ); - let process = tx_context.execute_code(&code); + let exec_output = tx_context.execute_code_blocking(&code); - assert_execution_error!(process, ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS); + assert_execution_error!(exec_output, ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS); Ok(()) } @@ -693,10 +694,10 @@ fn test_build_recipient_hash() -> anyhow::Result<()> { aux = aux, ); - let process = &tx_context.execute_code(&code)?; + let exec_output = &tx_context.execute_code_blocking(&code)?; assert_eq!( - process.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), + exec_output.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), Word::from([1, 0, 0, 0u32]), "number of output notes must increment by 1", ); @@ -704,7 +705,7 @@ fn test_build_recipient_hash() -> anyhow::Result<()> { let recipient_digest = recipient.clone().digest(); assert_eq!( - process.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_RECIPIENT_OFFSET), + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_RECIPIENT_OFFSET), recipient_digest, "recipient hash not correct", ); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index 249f46f31f..de76bf5093 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -37,7 +37,6 @@ use miden_lib::transaction::memory::{ INPUT_NOTE_SERIAL_NUM_OFFSET, INPUT_NOTES_COMMITMENT_PTR, KERNEL_PROCEDURES_PTR, - MemoryOffset, NATIVE_ACCT_CODE_COMMITMENT_PTR, NATIVE_ACCT_ID_AND_NONCE_PTR, NATIVE_ACCT_ID_PTR, @@ -88,14 +87,15 @@ use miden_objects::transaction::{ TransactionScript, }; use miden_objects::{EMPTY_WORD, ONE, WORD_SIZE}; -use miden_processor::{AdviceInputs, Process, Word}; +use miden_processor::fast::ExecutionOutput; +use miden_processor::{AdviceInputs, Word}; use miden_tx::TransactionExecutorError; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use super::{Felt, ZERO}; -use crate::kernel_tests::tx::ProcessMemoryExt; -use crate::utils::{create_public_p2any_note, input_note_data_ptr}; +use crate::kernel_tests::tx::ExecutionOutputExt; +use crate::utils::create_public_p2any_note; use crate::{ Auth, MockChain, @@ -163,148 +163,148 @@ fn test_transaction_prologue() -> anyhow::Result<()> { .with_note_args(note_args_map); tx_context.set_tx_args(tx_args); - let process = &tx_context.execute_code(code)?; + let exec_output = &tx_context.execute_code_blocking(code)?; - global_input_memory_assertions(process, &tx_context); - block_data_memory_assertions(process, &tx_context); - partial_blockchain_memory_assertions(process, &tx_context); - kernel_data_memory_assertions(process); - account_data_memory_assertions(process, &tx_context); - input_notes_memory_assertions(process, &tx_context, ¬e_args); + global_input_memory_assertions(exec_output, &tx_context); + block_data_memory_assertions(exec_output, &tx_context); + partial_blockchain_memory_assertions(exec_output, &tx_context); + kernel_data_memory_assertions(exec_output); + account_data_memory_assertions(exec_output, &tx_context); + input_notes_memory_assertions(exec_output, &tx_context, ¬e_args); Ok(()) } -fn global_input_memory_assertions(process: &Process, inputs: &TransactionContext) { +fn global_input_memory_assertions(exec_output: &ExecutionOutput, inputs: &TransactionContext) { assert_eq!( - process.get_kernel_mem_word(BLOCK_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(BLOCK_COMMITMENT_PTR), inputs.tx_inputs().block_header().commitment(), "The block commitment should be stored at the BLOCK_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(NATIVE_ACCT_ID_PTR)[0], + exec_output.get_kernel_mem_word(NATIVE_ACCT_ID_PTR)[0], inputs.account().id().suffix(), "The account ID prefix should be stored at the ACCT_ID_PTR[0]" ); assert_eq!( - process.get_kernel_mem_word(NATIVE_ACCT_ID_PTR)[1], + exec_output.get_kernel_mem_word(NATIVE_ACCT_ID_PTR)[1], inputs.account().id().prefix().as_felt(), "The account ID suffix should be stored at the ACCT_ID_PTR[1]" ); assert_eq!( - process.get_kernel_mem_word(INIT_ACCT_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(INIT_ACCT_COMMITMENT_PTR), inputs.account().commitment(), "The account commitment should be stored at the INIT_ACCT_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(INIT_NATIVE_ACCT_VAULT_ROOT_PTR), + exec_output.get_kernel_mem_word(INIT_NATIVE_ACCT_VAULT_ROOT_PTR), inputs.account().vault().root(), "The initial native account vault root should be stored at the INIT_ACCT_VAULT_ROOT_PTR" ); assert_eq!( - process.get_kernel_mem_word(INIT_NATIVE_ACCT_STORAGE_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(INIT_NATIVE_ACCT_STORAGE_COMMITMENT_PTR), inputs.account().storage().commitment(), "The initial native account storage commitment should be stored at the INIT_ACCT_STORAGE_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(INPUT_NOTES_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(INPUT_NOTES_COMMITMENT_PTR), inputs.input_notes().commitment(), "The nullifier commitment should be stored at the INPUT_NOTES_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(INIT_NONCE_PTR)[0], + exec_output.get_kernel_mem_word(INIT_NONCE_PTR)[0], inputs.account().nonce(), "The initial nonce should be stored at the INIT_NONCE_PTR" ); assert_eq!( - process.get_kernel_mem_word(TX_SCRIPT_ROOT_PTR), + exec_output.get_kernel_mem_word(TX_SCRIPT_ROOT_PTR), inputs.tx_args().tx_script().as_ref().unwrap().root(), "The transaction script root should be stored at the TX_SCRIPT_ROOT_PTR" ); } -fn block_data_memory_assertions(process: &Process, inputs: &TransactionContext) { +fn block_data_memory_assertions(exec_output: &ExecutionOutput, inputs: &TransactionContext) { assert_eq!( - process.get_kernel_mem_word(BLOCK_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(BLOCK_COMMITMENT_PTR), inputs.tx_inputs().block_header().commitment(), "The block commitment should be stored at the BLOCK_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(PREV_BLOCK_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(PREV_BLOCK_COMMITMENT_PTR), inputs.tx_inputs().block_header().prev_block_commitment(), "The previous block commitment should be stored at the PARENT_BLOCK_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(CHAIN_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(CHAIN_COMMITMENT_PTR), inputs.tx_inputs().block_header().chain_commitment(), "The chain commitment should be stored at the CHAIN_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(ACCT_DB_ROOT_PTR), + exec_output.get_kernel_mem_word(ACCT_DB_ROOT_PTR), inputs.tx_inputs().block_header().account_root(), "The account db root should be stored at the ACCT_DB_ROOT_PRT" ); assert_eq!( - process.get_kernel_mem_word(NULLIFIER_DB_ROOT_PTR), + exec_output.get_kernel_mem_word(NULLIFIER_DB_ROOT_PTR), inputs.tx_inputs().block_header().nullifier_root(), "The nullifier db root should be stored at the NULLIFIER_DB_ROOT_PTR" ); assert_eq!( - process.get_kernel_mem_word(TX_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(TX_COMMITMENT_PTR), inputs.tx_inputs().block_header().tx_commitment(), "The TX commitment should be stored at the TX_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(TX_KERNEL_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(TX_KERNEL_COMMITMENT_PTR), inputs.tx_inputs().block_header().tx_kernel_commitment(), "The kernel commitment should be stored at the TX_KERNEL_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(PROOF_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(PROOF_COMMITMENT_PTR), inputs.tx_inputs().block_header().proof_commitment(), "The proof commitment should be stored at the PROOF_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(BLOCK_METADATA_PTR)[BLOCK_NUMBER_IDX], + exec_output.get_kernel_mem_word(BLOCK_METADATA_PTR)[BLOCK_NUMBER_IDX], inputs.tx_inputs().block_header().block_num().into(), "The block number should be stored at BLOCK_METADATA_PTR[BLOCK_NUMBER_IDX]" ); assert_eq!( - process.get_kernel_mem_word(BLOCK_METADATA_PTR)[PROTOCOL_VERSION_IDX], + exec_output.get_kernel_mem_word(BLOCK_METADATA_PTR)[PROTOCOL_VERSION_IDX], inputs.tx_inputs().block_header().version().into(), "The protocol version should be stored at BLOCK_METADATA_PTR[PROTOCOL_VERSION_IDX]" ); assert_eq!( - process.get_kernel_mem_word(BLOCK_METADATA_PTR)[TIMESTAMP_IDX], + exec_output.get_kernel_mem_word(BLOCK_METADATA_PTR)[TIMESTAMP_IDX], inputs.tx_inputs().block_header().timestamp().into(), "The timestamp should be stored at BLOCK_METADATA_PTR[TIMESTAMP_IDX]" ); assert_eq!( - process.get_kernel_mem_word(FEE_PARAMETERS_PTR)[NATIVE_ASSET_ID_SUFFIX_IDX], + exec_output.get_kernel_mem_word(FEE_PARAMETERS_PTR)[NATIVE_ASSET_ID_SUFFIX_IDX], inputs.tx_inputs().block_header().fee_parameters().native_asset_id().suffix(), "The native asset ID suffix should be stored at FEE_PARAMETERS_PTR[NATIVE_ASSET_ID_SUFFIX_IDX]" ); assert_eq!( - process.get_kernel_mem_word(FEE_PARAMETERS_PTR)[NATIVE_ASSET_ID_PREFIX_IDX], + exec_output.get_kernel_mem_word(FEE_PARAMETERS_PTR)[NATIVE_ASSET_ID_PREFIX_IDX], inputs .tx_inputs() .block_header() @@ -316,7 +316,7 @@ fn block_data_memory_assertions(process: &Process, inputs: &TransactionContext) ); assert_eq!( - process.get_kernel_mem_word(FEE_PARAMETERS_PTR)[VERIFICATION_BASE_FEE_IDX], + exec_output.get_kernel_mem_word(FEE_PARAMETERS_PTR)[VERIFICATION_BASE_FEE_IDX], inputs .tx_inputs() .block_header() @@ -327,20 +327,23 @@ fn block_data_memory_assertions(process: &Process, inputs: &TransactionContext) ); assert_eq!( - process.get_kernel_mem_word(NOTE_ROOT_PTR), + exec_output.get_kernel_mem_word(NOTE_ROOT_PTR), inputs.tx_inputs().block_header().note_root(), "The note root should be stored at the NOTE_ROOT_PTR" ); } -fn partial_blockchain_memory_assertions(process: &Process, prepared_tx: &TransactionContext) { +fn partial_blockchain_memory_assertions( + exec_output: &ExecutionOutput, + prepared_tx: &TransactionContext, +) { // update the partial blockchain to point to the block against which this transaction is being // executed let mut partial_blockchain = prepared_tx.tx_inputs().blockchain().clone(); partial_blockchain.add_block(prepared_tx.tx_inputs().block_header().clone(), true); assert_eq!( - process.get_kernel_mem_word(PARTIAL_BLOCKCHAIN_NUM_LEAVES_PTR)[0], + exec_output.get_kernel_mem_word(PARTIAL_BLOCKCHAIN_NUM_LEAVES_PTR)[0], Felt::new(partial_blockchain.chain_length().as_u64()), "The number of leaves should be stored at the PARTIAL_BLOCKCHAIN_NUM_LEAVES_PTR" ); @@ -352,17 +355,17 @@ fn partial_blockchain_memory_assertions(process: &Process, prepared_tx: &Transac ); let word_aligned_peak_idx = peak_idx * WORD_SIZE as u32; assert_eq!( - process.get_kernel_mem_word(PARTIAL_BLOCKCHAIN_PEAKS_PTR + word_aligned_peak_idx), + exec_output.get_kernel_mem_word(PARTIAL_BLOCKCHAIN_PEAKS_PTR + word_aligned_peak_idx), *peak ); } } -fn kernel_data_memory_assertions(process: &Process) { +fn kernel_data_memory_assertions(exec_output: &ExecutionOutput) { // check that the number of kernel procedures stored in the memory is equal to the number of // procedures in the `TransactionKernel::PROCEDURES` array assert_eq!( - process.get_kernel_mem_word(NUM_KERNEL_PROCEDURES_PTR)[0].as_int(), + exec_output.get_kernel_mem_word(NUM_KERNEL_PROCEDURES_PTR)[0].as_int(), TransactionKernel::PROCEDURES.len() as u64, "Number of the kernel procedures should be stored at the NUM_KERNEL_PROCEDURES_PTR" ); @@ -371,16 +374,16 @@ fn kernel_data_memory_assertions(process: &Process) { // `TransactionKernel::PROCEDURES` array for (i, &proc_hash) in TransactionKernel::PROCEDURES.iter().enumerate() { assert_eq!( - process.get_kernel_mem_word(KERNEL_PROCEDURES_PTR + (i * WORD_SIZE) as u32), + exec_output.get_kernel_mem_word(KERNEL_PROCEDURES_PTR + (i * WORD_SIZE) as u32), proc_hash, "hash of kernel procedure at index `{i}` does not match the hash stored in memory" ); } } -fn account_data_memory_assertions(process: &Process, inputs: &TransactionContext) { +fn account_data_memory_assertions(exec_output: &ExecutionOutput, inputs: &TransactionContext) { assert_eq!( - process.get_kernel_mem_word(NATIVE_ACCT_ID_AND_NONCE_PTR), + exec_output.get_kernel_mem_word(NATIVE_ACCT_ID_AND_NONCE_PTR), Word::new([ inputs.account().id().suffix(), inputs.account().id().prefix().as_felt(), @@ -391,25 +394,25 @@ fn account_data_memory_assertions(process: &Process, inputs: &TransactionContext ); assert_eq!( - process.get_kernel_mem_word(NATIVE_ACCT_VAULT_ROOT_PTR), + exec_output.get_kernel_mem_word(NATIVE_ACCT_VAULT_ROOT_PTR), inputs.account().vault().root(), "The account vault root should be stored at NATIVE_ACCT_VAULT_ROOT_PTR" ); assert_eq!( - process.get_kernel_mem_word(NATIVE_ACCT_STORAGE_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(NATIVE_ACCT_STORAGE_COMMITMENT_PTR), inputs.account().storage().commitment(), "The account storage commitment should be stored at NATIVE_ACCT_STORAGE_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(NATIVE_ACCT_CODE_COMMITMENT_PTR), + exec_output.get_kernel_mem_word(NATIVE_ACCT_CODE_COMMITMENT_PTR), inputs.account().code().commitment(), "account code commitment should be stored at NATIVE_ACCT_CODE_COMMITMENT_PTR" ); assert_eq!( - process.get_kernel_mem_word(NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR), + exec_output.get_kernel_mem_word(NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR), Word::from([u16::try_from(inputs.account().storage().slots().len()).unwrap(), 0, 0, 0]), "The number of initialised storage slots should be stored at NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR" ); @@ -422,7 +425,7 @@ fn account_data_memory_assertions(process: &Process, inputs: &TransactionContext .enumerate() { assert_eq!( - process.get_kernel_mem_word( + exec_output.get_kernel_mem_word( NATIVE_ACCT_STORAGE_SLOTS_SECTION_PTR + (i * WORD_SIZE) as u32 ), Word::try_from(elements).unwrap(), @@ -431,7 +434,7 @@ fn account_data_memory_assertions(process: &Process, inputs: &TransactionContext } assert_eq!( - process.get_kernel_mem_word(NATIVE_NUM_ACCT_PROCEDURES_PTR), + exec_output.get_kernel_mem_word(NATIVE_NUM_ACCT_PROCEDURES_PTR), Word::from([u16::try_from(inputs.account().code().procedures().len()).unwrap(), 0, 0, 0]), "The number of procedures should be stored at NATIVE_NUM_ACCT_PROCEDURES_PTR" ); @@ -444,7 +447,7 @@ fn account_data_memory_assertions(process: &Process, inputs: &TransactionContext .enumerate() { assert_eq!( - process + exec_output .get_kernel_mem_word(NATIVE_ACCT_PROCEDURES_SECTION_PTR + (i * WORD_SIZE) as u32), Word::try_from(elements).unwrap(), "The account procedures and storage offsets should be stored starting at NATIVE_ACCT_PROCEDURES_SECTION_PTR" @@ -453,12 +456,12 @@ fn account_data_memory_assertions(process: &Process, inputs: &TransactionContext } fn input_notes_memory_assertions( - process: &Process, + exec_output: &ExecutionOutput, inputs: &TransactionContext, note_args: &[Word], ) { assert_eq!( - process.get_kernel_mem_word(INPUT_NOTE_SECTION_PTR), + exec_output.get_kernel_mem_word(INPUT_NOTE_SECTION_PTR), Word::from([inputs.input_notes().num_notes(), 0, 0, 0]), "number of input notes should be stored at the INPUT_NOTES_OFFSET" ); @@ -467,7 +470,7 @@ fn input_notes_memory_assertions( let note = input_note.note(); assert_eq!( - process.get_kernel_mem_word( + exec_output.get_kernel_mem_word( INPUT_NOTE_NULLIFIER_SECTION_PTR + note_idx * WORD_SIZE as u32 ), note.nullifier().as_word(), @@ -475,55 +478,55 @@ fn input_notes_memory_assertions( ); assert_eq!( - read_note_element(process, note_idx, INPUT_NOTE_ID_OFFSET), + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_ID_OFFSET), note.id().as_word(), "ID hash should be computed and stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, INPUT_NOTE_SERIAL_NUM_OFFSET), + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_SERIAL_NUM_OFFSET), note.serial_num(), "note serial num should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, INPUT_NOTE_SCRIPT_ROOT_OFFSET), + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_SCRIPT_ROOT_OFFSET), note.script().root(), "note script root should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, INPUT_NOTE_INPUTS_COMMITMENT_OFFSET), + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_INPUTS_COMMITMENT_OFFSET), note.inputs().commitment(), "note input commitment should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, INPUT_NOTE_RECIPIENT_OFFSET), + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_RECIPIENT_OFFSET), note.recipient().digest(), "note recipient should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, INPUT_NOTE_ASSETS_COMMITMENT_OFFSET), + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_ASSETS_COMMITMENT_OFFSET), note.assets().commitment(), "note asset commitment should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, INPUT_NOTE_METADATA_OFFSET), + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_METADATA_OFFSET), Word::from(note.metadata()), "note metadata should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, INPUT_NOTE_ARGS_OFFSET), + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_ARGS_OFFSET), note_args[note_idx as usize], "note args should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, INPUT_NOTE_NUM_ASSETS_OFFSET), + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_NUM_ASSETS_OFFSET), Word::from([::try_from(note.assets().num_assets()).unwrap(), 0, 0, 0]), "number of assets should be stored at the correct offset" ); @@ -531,8 +534,7 @@ fn input_notes_memory_assertions( for (asset, asset_idx) in note.assets().iter().cloned().zip(0_u32..) { let word: Word = asset.into(); assert_eq!( - read_note_element( - process, + exec_output.get_note_mem_word( note_idx, INPUT_NOTE_ASSETS_OFFSET + asset_idx * WORD_SIZE as u32 ), @@ -751,7 +753,7 @@ pub async fn create_account_invalid_seed() -> anyhow::Result<()> { end "; - let result = tx_context.execute_code(code); + let result = tx_context.execute_code(code).await; assert_execution_error!(result, ERR_ACCOUNT_SEED_AND_COMMITMENT_DIGEST_MISMATCH); @@ -774,9 +776,12 @@ fn test_get_blk_version() -> anyhow::Result<()> { end "; - let process = tx_context.execute_code(code)?; + let exec_output = tx_context.execute_code_blocking(code)?; - assert_eq!(process.stack.get(0), tx_context.tx_inputs().block_header().version().into()); + assert_eq!( + exec_output.get_stack_element(0), + tx_context.tx_inputs().block_header().version().into() + ); Ok(()) } @@ -797,16 +802,12 @@ fn test_get_blk_timestamp() -> anyhow::Result<()> { end "; - let process = tx_context.execute_code(code)?; + let exec_output = tx_context.execute_code_blocking(code)?; - assert_eq!(process.stack.get(0), tx_context.tx_inputs().block_header().timestamp().into()); + assert_eq!( + exec_output.get_stack_element(0), + tx_context.tx_inputs().block_header().timestamp().into() + ); Ok(()) } - -// HELPER FUNCTIONS -// ================================================================================================ - -fn read_note_element(process: &Process, note_idx: u32, offset: MemoryOffset) -> Word { - process.get_kernel_mem_word(input_note_data_ptr(note_idx) + offset) -} diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 723ffa370d..1a05ca4e71 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -54,12 +54,12 @@ use miden_objects::transaction::{ TransactionArgs, TransactionSummary, }; -use miden_objects::{FieldElement, Hasher, Word}; +use miden_objects::{Felt, FieldElement, Hasher, ONE, Word}; use miden_processor::crypto::RpoRandomCoin; use miden_tx::auth::UnreachableAuth; use miden_tx::{TransactionExecutor, TransactionExecutorError}; -use super::{Felt, ONE}; +use crate::kernel_tests::tx::ExecutionOutputExt; use crate::utils::{create_public_p2any_note, create_spawn_note}; use crate::{Auth, MockChain, TransactionContextBuilder}; @@ -186,22 +186,22 @@ async fn test_block_procedures() -> anyhow::Result<()> { end "; - let process = &tx_context.execute_code(code)?; + let exec_output = &tx_context.execute_code(code).await?; assert_eq!( - process.stack.get_word(0), + exec_output.get_stack_word(0), tx_context.tx_inputs().block_header().commitment(), "top word on the stack should be equal to the block header commitment" ); assert_eq!( - process.stack.get(4).as_int(), + exec_output.get_stack_element(4).as_int(), tx_context.tx_inputs().block_header().timestamp() as u64, "fifth element on the stack should be equal to the timestamp of the last block creation" ); assert_eq!( - process.stack.get(5).as_int(), + exec_output.get_stack_element(5).as_int(), tx_context.tx_inputs().block_header().block_num().as_u64(), "sixth element on the stack should be equal to the block number" ); diff --git a/crates/miden-testing/src/lib.rs b/crates/miden-testing/src/lib.rs index 1675950099..fbbd4402b8 100644 --- a/crates/miden-testing/src/lib.rs +++ b/crates/miden-testing/src/lib.rs @@ -21,7 +21,6 @@ pub use tx_context::{TransactionContext, TransactionContextBuilder}; pub mod executor; -pub use mock_host::MockHost; mod mock_host; pub mod utils; diff --git a/crates/miden-testing/src/mock_host.rs b/crates/miden-testing/src/mock_host.rs index ba7fd8bbfd..51461729d1 100644 --- a/crates/miden-testing/src/mock_host.rs +++ b/crates/miden-testing/src/mock_host.rs @@ -1,102 +1,92 @@ -use alloc::boxed::Box; -use alloc::rc::Rc; +use alloc::collections::BTreeSet; use alloc::sync::Arc; use alloc::vec::Vec; use miden_lib::StdLibrary; -use miden_lib::transaction::{EventId, TransactionEvent, TransactionEventError}; -use miden_objects::account::{AccountCode, AccountVaultDelta}; -use miden_objects::assembly::debuginfo::SourceManagerSync; -use miden_objects::assembly::{DefaultSourceManager, SourceManager}; -use miden_objects::transaction::AccountInputs; -use miden_objects::{Felt, Word}; +use miden_lib::transaction::{EventId, TransactionEvent}; +use miden_objects::Word; use miden_processor::{ AdviceMutation, + AsyncHost, BaseHost, - ContextId, EventError, - EventHandlerRegistry, + FutureMaybeSend, MastForest, - MastForestStore, ProcessState, - SyncHost, }; -use miden_tx::{AccountProcedureIndexMap, LinkMap, TransactionMastStore}; +use miden_tx::TransactionExecutorHost; +use miden_tx::auth::UnreachableAuth; + +use crate::TransactionContext; // MOCK HOST // ================================================================================================ -/// This is very similar to the TransactionHost in miden-tx. The differences include: -/// - We do not track account delta here. -/// - There is special handling of EMPTY_DIGEST in account procedure index map. -/// - This host uses `MemAdviceProvider` which is instantiated from the passed in advice inputs. -pub struct MockHost { - acct_procedure_index_map: AccountProcedureIndexMap, - mast_store: Rc, - source_manager: Arc, - /// Handle the VM default events _before_ passing it to user defined ones. - stdlib_handlers: EventHandlerRegistry, +/// The [`MockHost`] wraps a [`TransactionExecutorHost`] and forwards event handling requests to it, +/// with the difference that it only handles a subset of the events that the executor host handles. +/// +/// Why don't we always forward requests to the executor host? In some tests, when using +/// [`TransactionContext::execute_code`], we want to test that the transaction kernel fails +/// with a certain error when given invalid inputs, but the event handler in the executor host would +/// prematurely abort the transaction due to the invalid inputs. To avoid this situation, the event +/// handler can be disabled and we can test that the transaction kernel has the expected behavior +/// (e.g. even if the transaction host was malicious). +/// +/// Some event handlers, such as delta or output note tracking, will similarly interfere with +/// testing a procedure in isolation and these are also turned off in this host. +pub(crate) struct MockHost<'store> { + /// The underlying [`TransactionExecutorHost`] that the mock host will forward requests to. + exec_host: TransactionExecutorHost<'store, 'static, TransactionContext, UnreachableAuth>, + + /// The set of event IDs that the mock host will forward to the [`TransactionExecutorHost`]. + /// + /// Event IDs that are not in this set are not handled. This can be useful in certain test + /// scenarios. + handled_events: BTreeSet, } -impl MockHost { +impl<'store> MockHost<'store> { /// Returns a new [`MockHost`] instance with the provided inputs. pub fn new( - native_account_code: &AccountCode, - mast_store: Rc, - foreign_account_inputs: &[AccountInputs], + exec_host: TransactionExecutorHost<'store, 'static, TransactionContext, UnreachableAuth>, ) -> Self { - let account_procedure_index_map = AccountProcedureIndexMap::new( - foreign_account_inputs - .iter() - .map(AccountInputs::code) - .chain([native_account_code]), - ) - .expect("account procedure index map should be valid"); - - let stdlib_handlers = { - let mut registry = EventHandlerRegistry::new(); - - let stdlib = StdLibrary::default(); - for (event_id, handler) in stdlib.handlers() { - registry - .register(event_id, handler) - .expect("There are no duplicates in the stdlibrary handlers"); - } - registry - }; - - Self { - acct_procedure_index_map: account_procedure_index_map, - mast_store, - source_manager: Arc::new(DefaultSourceManager::default()), - stdlib_handlers, - } - } - - /// Sets the provided [`SourceManagerSync`] on the host. - pub fn with_source_manager(mut self, source_manager: Arc) -> Self { - self.source_manager = source_manager; - self + // StdLibrary events are always handled. + let stdlib_handlers = StdLibrary::default() + .handlers() + .into_iter() + .map(|(handler_event_id, _)| handler_event_id); + let mut handled_events = BTreeSet::from_iter(stdlib_handlers); + + // The default set of transaction events that are always handled. + handled_events.extend( + [ + &TransactionEvent::AccountPushProcedureIndex, + &TransactionEvent::LinkMapSet, + &TransactionEvent::LinkMapGet, + ] + .map(TransactionEvent::event_id), + ); + + Self { exec_host, handled_events } } - /// Consumes `self` and returns the advice provider and account vault delta. - pub fn into_parts(self) -> AccountVaultDelta { - AccountVaultDelta::default() - } - - // EVENT HANDLERS - // -------------------------------------------------------------------------------------------- - - fn on_push_account_procedure_index( - &mut self, - process: &ProcessState, - ) -> Result, EventError> { - let proc_idx = self.acct_procedure_index_map.get_proc_index(process).map_err(Box::new)?; - Ok(vec![AdviceMutation::extend_stack([Felt::from(proc_idx)])]) + // Adds the transaction events needed for Lazy loading to the set of handled events. + pub fn enable_lazy_loading(&mut self) { + self.handled_events.extend( + [ + &TransactionEvent::AccountBeforeForeignLoad, + &TransactionEvent::AccountVaultBeforeGetBalance, + &TransactionEvent::AccountVaultBeforeHasNonFungibleAsset, + &TransactionEvent::AccountVaultBeforeAddAsset, + &TransactionEvent::AccountStorageBeforeSetMapItem, + &TransactionEvent::AccountStorageBeforeGetMapItem, + ] + .map(TransactionEvent::event_id), + ); } } -impl BaseHost for MockHost { +impl<'store> BaseHost for MockHost<'store> { fn get_label_and_source_file( &self, location: &miden_objects::assembly::debuginfo::Location, @@ -104,37 +94,28 @@ impl BaseHost for MockHost { miden_objects::assembly::debuginfo::SourceSpan, Option>, ) { - let maybe_file = self.source_manager.get_by_uri(location.uri()); - let span = self.source_manager.location_to_span(location.clone()).unwrap_or_default(); - (span, maybe_file) + self.exec_host.get_label_and_source_file(location) } } -impl SyncHost for MockHost { - fn get_mast_forest(&self, node_digest: &Word) -> Option> { - self.mast_store.get(node_digest) +impl<'store> AsyncHost for MockHost<'store> { + fn get_mast_forest(&self, node_digest: &Word) -> impl FutureMaybeSend>> { + self.exec_host.get_mast_forest(node_digest) } - fn on_event(&mut self, process: &ProcessState) -> Result, EventError> { + fn on_event( + &mut self, + process: &ProcessState, + ) -> impl FutureMaybeSend, EventError>> { let event_id = EventId::from_felt(process.get_stack_item(0)); - if let Some(result) = self.stdlib_handlers.handle_event(event_id, process).transpose() { - return result; - } - let event = TransactionEvent::try_from(event_id).map_err(Box::new)?; - if process.ctx() != ContextId::root() { - return Err(Box::new(TransactionEventError::NotRootContext(event))); + async move { + // If the host should handle the event, delegate to the tx executor host. + if self.handled_events.contains(&event_id) { + self.exec_host.on_event(process).await + } else { + Ok(Vec::new()) + } } - - let advice_mutations = match event { - TransactionEvent::AccountPushProcedureIndex => { - self.on_push_account_procedure_index(process) - }, - TransactionEvent::LinkMapSet => LinkMap::handle_set_event(process), - TransactionEvent::LinkMapGet => LinkMap::handle_get_event(process), - _ => Ok(Vec::new()), - }?; - - Ok(advice_mutations) } } diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 950e3083e7..18329cb801 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -55,10 +55,14 @@ pub type MockAuthenticator = BasicAuthenticator; /// /// Create a new account and execute code: /// ``` +/// # use anyhow::Result; /// # use miden_testing::TransactionContextBuilder; /// # use miden_objects::{account::AccountBuilder,Felt, FieldElement}; /// # use miden_lib::transaction::TransactionKernel; -/// let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); +/// # +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> Result<()> { +/// let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; /// /// let code = " /// use.$kernel::prologue @@ -71,8 +75,10 @@ pub type MockAuthenticator = BasicAuthenticator; /// end /// "; /// -/// let process = tx_context.execute_code(code).unwrap(); -/// assert_eq!(process.stack.get(0), Felt::new(5),); +/// let exec_output = tx_context.execute_code(code).await?; +/// assert_eq!(exec_output.stack.get(0).unwrap(), &Felt::new(5)); +/// # Ok(()) +/// # } /// ``` pub struct TransactionContextBuilder { source_manager: Arc, @@ -223,6 +229,14 @@ impl TransactionContextBuilder { self } + /// Disables lazy loading. + /// + /// This is the opposite of [`Self::enable_lazy_loading`] - see its docs for details. + pub fn disable_lazy_loading(mut self) -> Self { + self.is_lazy_loading_enabled = false; + self + } + /// Extend the note arguments map with the provided one. pub fn extend_note_args(mut self, note_args: BTreeMap) -> Self { self.note_args.extend(note_args); @@ -350,6 +364,7 @@ impl TransactionContextBuilder { mast_store, authenticator: self.authenticator, source_manager: self.source_manager, + is_lazy_loading_enabled: self.is_lazy_loading_enabled, note_scripts: self.note_scripts, }) } diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index 227f610145..cdd7c281ae 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -1,6 +1,5 @@ use alloc::borrow::ToOwned; use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::rc::Rc; use alloc::sync::Arc; use alloc::vec::Vec; @@ -20,26 +19,23 @@ use miden_objects::transaction::{ TransactionArgs, TransactionInputs, }; -use miden_processor::{ - ExecutionError, - FutureMaybeSend, - MastForest, - MastForestStore, - Process, - Word, -}; -use miden_tx::auth::BasicAuthenticator; +use miden_processor::fast::ExecutionOutput; +use miden_processor::{ExecutionError, FutureMaybeSend, MastForest, MastForestStore, Word}; +use miden_tx::auth::{BasicAuthenticator, UnreachableAuth}; use miden_tx::{ + AccountProcedureIndexMap, DataStore, DataStoreError, + ScriptMastForestStore, TransactionExecutor, TransactionExecutorError, + TransactionExecutorHost, TransactionMastStore, }; use rand_chacha::ChaCha20Rng; -use crate::MockHost; use crate::executor::CodeExecutor; +use crate::mock_host::MockHost; use crate::tx_context::builder::MockAuthenticator; // TRANSACTION CONTEXT @@ -57,21 +53,31 @@ pub struct TransactionContext { pub(super) mast_store: TransactionMastStore, pub(super) authenticator: Option, pub(super) source_manager: Arc, + pub(super) is_lazy_loading_enabled: bool, pub(super) note_scripts: BTreeMap, } impl TransactionContext { + /// TODO: Remove. + pub fn execute_code_blocking(&self, code: &str) -> Result { + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(self.execute_code(code)) + } + /// Executes arbitrary code within the context of a mocked transaction environment and returns - /// the resulting [Process]. + /// the resulting [`ExecutionOutput`]. /// /// The code is compiled with the assembler returned by /// [`TransactionKernel::with_mock_libraries`] and executed with advice inputs constructed from - /// the data stored in the context. The program is run on a [`MockHost`] which is loaded with - /// the procedures exposed by the transaction kernel, and also individual kernel functions (not - /// normally exposed). + /// the data stored in the context. The program is run on a modified [`TransactionExecutorHost`] + /// which is loaded with the procedures exposed by the transaction kernel, and also + /// individual kernel functions (not normally exposed). /// /// To improve the error message quality, convert the returned [`ExecutionError`] into a - /// [`Report`](miden_objects::assembly::diagnostics::Report). + /// [`Report`](miden_objects::assembly::diagnostics::Report) or use `?` with + /// [`miden_objects::assembly::diagnostics::Result`]. /// /// # Errors /// @@ -80,7 +86,7 @@ impl TransactionContext { /// # Panics /// /// - If the provided `code` is not a valid program. - pub fn execute_code(&self, code: &str) -> Result { + pub async fn execute_code(&self, code: &str) -> Result { let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&self.tx_inputs) .expect("error initializing transaction inputs"); @@ -98,27 +104,45 @@ impl TransactionContext { .assemble_program(virtual_source_file) .expect("code was not well formed"); - let mast_store = Rc::new(TransactionMastStore::new()); + // Load transaction kernel and the program into the mast forest in self. + // Note that native and foreign account's code are already loaded by the + // TransactionContextBuilder. + self.mast_store.insert(TransactionKernel::library().mast_forest().clone()); + self.mast_store.insert(program.mast_forest().clone()); - mast_store.insert(program.mast_forest().clone()); - mast_store.insert(TransactionKernel::library().mast_forest().clone()); - mast_store.load_account_code(self.account().code()); - for acc_inputs in self.tx_inputs.tx_args().foreign_account_inputs() { - mast_store.load_account_code(acc_inputs.code()); - } + let account_procedure_idx_map = AccountProcedureIndexMap::new( + [self.tx_inputs().account().code()] + .into_iter() + .chain(self.tx_args().foreign_account_inputs().iter().map(|inputs| inputs.code())), + ) + .expect("constructing account procedure index map should work"); + + // The ref block is unimportant when using execute_code so we can set it to any value. + let ref_block = self.tx_inputs().block_header().block_num(); + + let exec_host = TransactionExecutorHost::<'_, '_, _, UnreachableAuth>::new( + &PartialAccount::from(self.account()), + self.tx_inputs().input_notes().clone(), + self, + ScriptMastForestStore::default(), + account_procedure_idx_map, + None, + ref_block, + self.source_manager(), + ); let advice_inputs = advice_inputs.into_advice_inputs(); - CodeExecutor::new( - MockHost::new( - self.tx_inputs().account().code(), - mast_store, - self.tx_inputs().tx_args().foreign_account_inputs(), - ) - .with_source_manager(self.source_manager()), - ) - .stack_inputs(stack_inputs) - .extend_advice_inputs(advice_inputs) - .execute_program(program) + + let mut mock_host = MockHost::new(exec_host); + if self.is_lazy_loading_enabled { + mock_host.enable_lazy_loading() + } + + CodeExecutor::new(mock_host) + .stack_inputs(stack_inputs) + .extend_advice_inputs(advice_inputs) + .execute_program(program) + .await } /// Executes the transaction through a [TransactionExecutor] diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 0adee8e3b1..c5845bf418 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -2,7 +2,7 @@ use alloc::string::String; use alloc::vec::Vec; use miden_lib::testing::note::NoteBuilder; -use miden_lib::transaction::{TransactionKernel, memory}; +use miden_lib::transaction::TransactionKernel; use miden_objects::Word; use miden_objects::account::AccountId; use miden_objects::asset::Asset; @@ -64,13 +64,6 @@ macro_rules! assert_transaction_executor_error { }; } -// TEST UTILITIES -// ================================================================================================ - -pub fn input_note_data_ptr(note_idx: u32) -> memory::MemoryAddress { - memory::INPUT_NOTE_DATA_SECTION_OFFSET + note_idx * memory::NOTE_MEM_SIZE -} - // HELPER NOTES // ================================================================================================ diff --git a/crates/miden-tx/src/host/link_map.rs b/crates/miden-tx/src/host/link_map.rs index cfb19f7d25..52347109d4 100644 --- a/crates/miden-tx/src/host/link_map.rs +++ b/crates/miden-tx/src/host/link_map.rs @@ -2,6 +2,7 @@ use alloc::vec::Vec; use core::cmp::Ordering; use miden_objects::{Felt, LexicographicWord, Word, ZERO}; +use miden_processor::fast::ExecutionOutput; use miden_processor::{AdviceMutation, ContextId, EventError, ProcessState}; // LINK MAP @@ -16,11 +17,11 @@ use miden_processor::{AdviceMutation, ContextId, EventError, ProcessState}; /// # Warning /// /// The functions on this type assume that the provided map_ptr points to a valid map in the -/// provided process. If those assumptions are violated, the functions may panic. -#[derive(Debug, Clone, Copy)] +/// provided memory viewer. If those assumptions are violated, the functions may panic. +#[derive(Clone, Copy)] pub struct LinkMap<'process> { map_ptr: u32, - process: &'process ProcessState<'process>, + mem: &'process MemoryViewer<'process>, } impl<'process> LinkMap<'process> { @@ -28,10 +29,10 @@ impl<'process> LinkMap<'process> { // -------------------------------------------------------------------------------------------- /// Creates a new link map from the provided map_ptr in the provided process. - pub fn new(map_ptr: Felt, process: &'process ProcessState<'process>) -> Self { + pub fn new(map_ptr: Felt, mem: &'process MemoryViewer<'process>) -> Self { let map_ptr: u32 = map_ptr.try_into().expect("map_ptr must be a valid u32"); - Self { map_ptr, process } + Self { map_ptr, mem } } // PUBLIC METHODS @@ -45,7 +46,8 @@ impl<'process> LinkMap<'process> { let map_ptr = process.get_stack_item(1); let map_key = process.get_stack_word(2); - let link_map = LinkMap::new(map_ptr, process); + let mem_viewer = MemoryViewer::ProcessState(process); + let link_map = LinkMap::new(map_ptr, &mem_viewer); let (set_op, entry_ptr) = link_map.compute_set_operation(LexicographicWord::from(map_key)); @@ -63,7 +65,8 @@ impl<'process> LinkMap<'process> { let map_ptr = process.get_stack_item(1); let map_key = process.get_stack_word(2); - let link_map = LinkMap::new(map_ptr, process); + let mem_viewer = MemoryViewer::ProcessState(process); + let link_map = LinkMap::new(map_ptr, &mem_viewer); let (get_op, entry_ptr) = link_map.compute_get_operation(LexicographicWord::from(map_key)); Ok(vec![AdviceMutation::extend_stack([ @@ -93,7 +96,7 @@ impl<'process> LinkMap<'process> { // Returns None if the value was either not yet initialized or points to 0. // It can point to 0 for example if a get operation is executed before a set operation, // which initializes the value in memory to 0 but does not change it. - self.get_kernel_mem_value(self.map_ptr).and_then(|head_ptr| { + self.mem.get_kernel_mem_element(self.map_ptr).and_then(|head_ptr| { if head_ptr == ZERO { None } else { @@ -120,23 +123,29 @@ impl<'process> LinkMap<'process> { /// Returns the key of the entry at the given pointer. fn key(&self, entry_ptr: u32) -> LexicographicWord { LexicographicWord::from( - self.get_kernel_mem_word(entry_ptr + 4).expect("entry pointer should be valid"), + self.mem + .get_kernel_mem_word(entry_ptr + 4) + .expect("entry pointer should be valid"), ) } /// Returns the values of the entry at the given pointer. fn value(&self, entry_ptr: u32) -> (Word, Word) { - let value0 = - self.get_kernel_mem_word(entry_ptr + 8).expect("entry pointer should be valid"); - let value1 = - self.get_kernel_mem_word(entry_ptr + 12).expect("entry pointer should be valid"); + let value0 = self + .mem + .get_kernel_mem_word(entry_ptr + 8) + .expect("entry pointer should be valid"); + let value1 = self + .mem + .get_kernel_mem_word(entry_ptr + 12) + .expect("entry pointer should be valid"); (value0, value1) } /// Returns the metadata of the entry at the given pointer. fn metadata(&self, entry_ptr: u32) -> EntryMetadata { let entry_metadata = - self.get_kernel_mem_word(entry_ptr).expect("entry pointer should be valid"); + self.mem.get_kernel_mem_word(entry_ptr).expect("entry pointer should be valid"); let map_ptr = entry_metadata[0]; let map_ptr = map_ptr.try_into().expect("entry_ptr should point to a u32 map_ptr"); @@ -212,19 +221,6 @@ impl<'process> LinkMap<'process> { }; (get_op, entry_ptr) } - - // HELPER METHODS - // -------------------------------------------------------------------------------------------- - - fn get_kernel_mem_value(&self, addr: u32) -> Option { - self.process.get_mem_value(ContextId::root(), addr) - } - - fn get_kernel_mem_word(&self, addr: u32) -> Option { - self.process - .get_mem_word(ContextId::root(), addr) - .expect("address should be word-aligned") - } } // LINK MAP ITER @@ -297,3 +293,63 @@ enum SetOperation { InsertAtHead = 1, InsertAfterEntry = 2, } + +// MEMORY VIEWER +// ================================================================================================ + +/// A abstraction over ways to view a process' memory. +/// +/// More specifically, it allows using a [`LinkMap`] both with a [`ProcessState`], i.e. a process +/// that is actively executing and also an [`ExecutionOutput`], i.e. a process that has finished +/// execution. +/// +/// This should all go away again once we change a LinkMap's implementation to be based on an actual +/// map type instead of viewing a process' memory directly. +pub enum MemoryViewer<'mem> { + ProcessState(&'mem ProcessState<'mem>), + ExecutionOutputs(&'mem ExecutionOutput), +} + +impl<'mem> MemoryViewer<'mem> { + /// Reads an element from transaction kernel memory. + fn get_kernel_mem_element(&self, addr: u32) -> Option { + match self { + MemoryViewer::ProcessState(process_state) => { + process_state.get_mem_value(ContextId::root(), addr) + }, + MemoryViewer::ExecutionOutputs(_execution_output) => { + // TODO: Use Memory::read_element once it no longer requires &mut self. + // https://github.com/0xMiden/miden-vm/issues/2237 + + // Copy of how Memory::read_element is implemented in Miden VM. + let idx = addr % miden_objects::WORD_SIZE as u32; + let word_addr = addr - idx; + + Some(self.get_kernel_mem_word(word_addr)?[idx as usize]) + }, + } + } + + /// Reads a word from transaction kernel memory. + fn get_kernel_mem_word(&self, addr: u32) -> Option { + match self { + MemoryViewer::ProcessState(process_state) => process_state + .get_mem_word(ContextId::root(), addr) + .expect("address should be word-aligned"), + MemoryViewer::ExecutionOutputs(execution_output) => { + let tx_kernel_context = ContextId::root(); + let clk = 0u32; + let err_ctx = (); + + // Note that this never returns None even if the location is uninitialized, but the + // link map does not rely on this. + Some( + execution_output + .memory + .read_word(tx_kernel_context, Felt::from(addr), clk.into(), &err_ctx) + .expect("expected address to be word-aligned"), + ) + }, + } + } +} diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 8312d2184c..7993dd9b09 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -5,7 +5,7 @@ use account_delta_tracker::AccountDeltaTracker; mod storage_delta_tracker; mod link_map; -pub use link_map::LinkMap; +pub use link_map::{LinkMap, MemoryViewer}; mod account_procedures; pub use account_procedures::AccountProcedureIndexMap; diff --git a/crates/miden-tx/src/lib.rs b/crates/miden-tx/src/lib.rs index 4fe695d179..a706897e39 100644 --- a/crates/miden-tx/src/lib.rs +++ b/crates/miden-tx/src/lib.rs @@ -21,7 +21,7 @@ pub use executor::{ }; mod host; -pub use host::{AccountProcedureIndexMap, LinkMap, ScriptMastForestStore}; +pub use host::{AccountProcedureIndexMap, LinkMap, MemoryViewer, ScriptMastForestStore}; mod prover; pub use prover::{ From e595a82cb14f81a6a91e67e52d9996778899fa20 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 10 Oct 2025 13:07:47 +0200 Subject: [PATCH 085/133] feat: Retrieve full foreign account inputs (#1963) feat: Refactor asset preservation rule tests chore: Add missing lazy loading event feat: Refactor stale foreign account test feat: Remove `TransactionArgs::foreign_account_inputs` chore: Refactor `MockChain::get_foreign_account_inputs` chore: Use printdiagnostic for nice errors chore: Remove `transaction_with_stale_foreign_account_inputs_fails` chore: Remove unused foreign account injection feat: Introduce `new_minimal` and `new_full` constructors for storage chore: Remove `TransactionContextBuilder::enable_lazy_loading` chore: clarify asset vault tracking chore: format and cleanup chore: add changelog feat: Replace `From` impl with `PartialVault::{new_minimal, new_full}` --- CHANGELOG.md | 1 + .../kernels/transaction/lib/asset_vault.masm | 4 + crates/miden-lib/src/transaction/inputs.rs | 3 - crates/miden-objects/src/account/partial.rs | 27 ++- .../src/account/storage/map/partial.rs | 39 +++- .../src/account/storage/partial.rs | 64 ++++-- .../miden-objects/src/asset/vault/partial.rs | 37 +++- .../src/transaction/inputs/mod.rs | 5 - .../miden-objects/src/transaction/tx_args.rs | 34 +-- .../src/kernel_tests/tx/test_asset_vault.rs | 5 + .../src/kernel_tests/tx/test_epilogue.rs | 201 +++++++----------- .../src/kernel_tests/tx/test_fpi.rs | 54 ++--- .../src/kernel_tests/tx/test_lazy_loading.rs | 12 +- .../src/kernel_tests/tx/test_note.rs | 10 +- .../src/kernel_tests/tx/test_prologue.rs | 16 +- .../src/kernel_tests/tx/test_tx.rs | 40 +--- crates/miden-testing/src/mock_chain/chain.rs | 14 +- .../src/mock_chain/chain_builder.rs | 7 + crates/miden-testing/src/mock_host.rs | 4 + .../miden-testing/src/tx_context/builder.rs | 112 ++-------- .../miden-testing/src/tx_context/context.rs | 56 +++-- crates/miden-tx/src/executor/mod.rs | 29 +-- 22 files changed, 315 insertions(+), 459 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 028ed7ff29..a3baa494ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Added `AssetVault::{num_assets, num_leaves, inner_nodes}` ([#1939]https://github.com/0xMiden/miden-base/pull/1939). - Added `account::get_initial_balance` procedure to `miden` lib ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). - [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). +- [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). ### Changes diff --git a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm index 62f0811056..736d4dd8eb 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm @@ -85,6 +85,10 @@ end #! manipulation from a malicious host. Therefore this should only be used when the inclusion of the #! peeked balance is verified at a later point. #! +#! WARNING: This is a generic vault procedure and so it cannot emit an event to lazy load asset +#! merkle paths from the merkle store, since this is only possible for the account vault. Ensure +#! that the merkle paths are present prior to calling. +#! #! To get the verified balance, use get_balance. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] diff --git a/crates/miden-lib/src/transaction/inputs.rs b/crates/miden-lib/src/transaction/inputs.rs index a60b80f6c1..1486205eac 100644 --- a/crates/miden-lib/src/transaction/inputs.rs +++ b/crates/miden-lib/src/transaction/inputs.rs @@ -55,9 +55,6 @@ impl TransactionAdviceInputs { inputs.add_map_entry(account_id_key, seed.to_vec()); } - // Inject foreign account inputs. - inputs.add_foreign_accounts(tx_inputs.tx_args().foreign_account_inputs())?; - // Extend with extra user-supplied advice. inputs.extend(tx_inputs.tx_args().advice_inputs().clone()); diff --git a/crates/miden-objects/src/account/partial.rs b/crates/miden-objects/src/account/partial.rs index caa498d02c..fd1161f07e 100644 --- a/crates/miden-objects/src/account/partial.rs +++ b/crates/miden-objects/src/account/partial.rs @@ -167,15 +167,34 @@ impl PartialAccount { } impl From<&Account> for PartialAccount { - /// Constructs a [`PartialAccount`] from the provided account with an empty seed, assuming it is - /// an existing account. + /// Constructs a [`PartialAccount`] from the provided account. + /// + /// The behavior is different whether the [`Account::is_new`] or not: + /// - For new accounts, the storage is tracked in full. This is because transactions that create + /// accounts need the full state. + /// - For existing accounts, the storage is tracked minimally, i.e. the minimal necessary data + /// is included. + /// + /// Because new accounts always have empty vaults, in both cases, the asset vault is a minimal + /// representation. + /// + /// For precise control over how an account is converted to a partial account, use + /// [`PartialAccount::new`]. fn from(account: &Account) -> Self { + let partial_storage = if account.is_new() { + // This is somewhat expensive, but it allows us to do this conversion from &Account and + // it penalizes only the rare case (new accounts). + PartialStorage::new_full(account.storage.clone()) + } else { + PartialStorage::new_minimal(account.storage()) + }; + Self::new( account.id(), account.nonce(), account.code().clone(), - account.storage().into(), - account.vault().into(), + partial_storage, + PartialVault::new_minimal(account.vault()), account.seed(), ) .expect("account should ensure that seed is valid for account") diff --git a/crates/miden-objects/src/account/storage/map/partial.rs b/crates/miden-objects/src/account/storage/map/partial.rs index 2b4b967734..1559b8e173 100644 --- a/crates/miden-objects/src/account/storage/map/partial.rs +++ b/crates/miden-objects/src/account/storage/map/partial.rs @@ -60,6 +60,36 @@ impl PartialStorageMap { Ok(PartialStorageMap { partial_smt, entries: map }) } + /// Converts a [`StorageMap`] into a partial storage representation. + /// + /// The resulting [`PartialStorageMap`] will contain the _full_ entries and merkle paths of the + /// original storage map. + pub fn new_full(storage_map: StorageMap) -> Self { + let partial_smt = PartialSmt::from(storage_map.smt); + let entries = storage_map.entries; + + PartialStorageMap { partial_smt, entries } + } + + /// Converts a [`StorageMap`] into a partial storage representation. + /// + /// The resulting [`PartialStorageMap`] will contain only a single, unspecified key-value pair + /// in order to have the same root as the original storage map. Is it otherwise the most + /// _minimal_ representation of the storage map. + pub fn new_minimal(storage_map: &StorageMap) -> Self { + let mut partial_map = PartialStorageMap::default(); + + // Construct a partial storage map that tracks the empty word, but none of the assets that + // are actually in the asset tree. That way, the partial map has the same root as the full + // map. This is the most minimal and correct partial map we can build. + // TODO: Workaround for https://github.com/0xMiden/miden-base/issues/1966. Fix when implemented. + partial_map + .add(storage_map.open(&Word::empty())) + .expect("adding the first proof should never fail"); + + partial_map + } + // ACCESSORS // -------------------------------------------------------------------------------------------- @@ -131,15 +161,6 @@ impl PartialStorageMap { } } -impl From for PartialStorageMap { - fn from(value: StorageMap) -> Self { - let smt = value.smt; - let map = value.entries; - - PartialStorageMap { partial_smt: smt.into(), entries: map } - } -} - impl Serializable for PartialStorageMap { fn write_into(&self, target: &mut W) { target.write(&self.partial_smt); diff --git a/crates/miden-objects/src/account/storage/partial.rs b/crates/miden-objects/src/account/storage/partial.rs index cab4281fa4..577aca0c2d 100644 --- a/crates/miden-objects/src/account/storage/partial.rs +++ b/crates/miden-objects/src/account/storage/partial.rs @@ -25,6 +25,9 @@ pub struct PartialStorage { } impl PartialStorage { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + /// Returns a new instance of partial storage with the specified header and storage map SMTs. /// /// The storage commitment is computed during instantiation based on the provided header. @@ -48,6 +51,47 @@ impl PartialStorage { Ok(Self { commitment, header: storage_header, maps }) } + /// Converts an [`AccountStorage`] into a partial storage representation. + /// + /// This creates a partial storage that contains the _full_ proofs for all key-value pairs + /// in all map slots of the account storage. + pub fn new_full(account_storage: AccountStorage) -> Self { + let header: AccountStorageHeader = account_storage.to_header(); + let commitment = header.compute_commitment(); + + let mut maps = BTreeMap::new(); + for slot in account_storage { + if let StorageSlot::Map(storage_map) = slot { + let partial_map = PartialStorageMap::new_full(storage_map); + maps.insert(partial_map.root(), partial_map); + } + } + + PartialStorage { header, maps, commitment } + } + + /// Converts an [`AccountStorage`] into a partial storage representation. + /// + /// For every storage map, a single unspecified key-value pair is tracked so that the + /// [`PartialStorageMap`] represents the correct root. + pub fn new_minimal(account_storage: &AccountStorage) -> Self { + let header: AccountStorageHeader = account_storage.to_header(); + let commitment = header.compute_commitment(); + + let mut maps = BTreeMap::new(); + for slot in account_storage.slots() { + if let StorageSlot::Map(storage_map) = slot { + let partial_map = PartialStorageMap::new_minimal(storage_map); + maps.insert(partial_map.root(), partial_map); + } + } + + PartialStorage { header, maps, commitment } + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + /// Returns a reference to the header of this partial storage. pub fn header(&self) -> &AccountStorageHeader { &self.header @@ -87,26 +131,6 @@ impl PartialStorage { } } -impl From<&AccountStorage> for PartialStorage { - /// Converts a full account storage into a partial storage representation. - /// - /// This creates a partial storage that contains proofs for all key-value pairs - /// in all map slots of the account storage. - fn from(account_storage: &AccountStorage) -> Self { - let mut map_smts = BTreeMap::new(); - for slot in account_storage.slots() { - if let StorageSlot::Map(map) = slot { - let smt: PartialStorageMap = map.clone().into(); - map_smts.insert(smt.root(), smt); - } - } - - let header: AccountStorageHeader = account_storage.to_header(); - let commitment = header.compute_commitment(); - PartialStorage { header, maps: map_smts, commitment } - } -} - impl Serializable for PartialStorage { fn write_into(&self, target: &mut W) { target.write(&self.header); diff --git a/crates/miden-objects/src/asset/vault/partial.rs b/crates/miden-objects/src/asset/vault/partial.rs index 5a0eaa5945..3925dfbc38 100644 --- a/crates/miden-objects/src/asset/vault/partial.rs +++ b/crates/miden-objects/src/asset/vault/partial.rs @@ -37,6 +37,35 @@ impl PartialVault { Ok(PartialVault { partial_smt }) } + /// Converts an [`AssetVault`] into a partial vault representation. + /// + /// The resulting [`PartialVault`] will contain the _full_ merkle paths of the original asset + /// vault. + pub fn new_full(vault: AssetVault) -> Self { + let partial_smt = PartialSmt::from(vault.asset_tree); + + PartialVault { partial_smt } + } + + /// Converts an [`AssetVault`] into a partial vault representation. + /// + /// The resulting [`PartialVault`] will contain only a single, unspecified key-value pair + /// in order to have the same root as the original storage map. Is it otherwise the most + /// _minimal_ representation of the asset vault. + pub fn new_minimal(vault: &AssetVault) -> Self { + let mut partial_vault = PartialVault::default(); + + // Construct a partial vault that tracks the empty word, but none of the assets that are + // actually in the asset tree. That way, the partial vault has the same root as the full + // vault. This is the most minimal and correct partial vault we can build. + // TODO: Workaround for https://github.com/0xMiden/miden-base/issues/1966. Fix when implemented. + partial_vault + .add(vault.open(Word::empty())) + .expect("adding the first proof should never fail"); + + partial_vault + } + // ACCESSORS // -------------------------------------------------------------------------------------------- @@ -141,14 +170,6 @@ impl PartialVault { } } -impl From<&AssetVault> for PartialVault { - fn from(value: &AssetVault) -> Self { - let vault_partial_smt = value.asset_tree.clone().into(); - - PartialVault { partial_smt: vault_partial_smt } - } -} - impl Serializable for PartialVault { fn write_into(&self, target: &mut W) { target.write(&self.partial_smt) diff --git a/crates/miden-objects/src/transaction/inputs/mod.rs b/crates/miden-objects/src/transaction/inputs/mod.rs index fe36c29a62..59ea16e12a 100644 --- a/crates/miden-objects/src/transaction/inputs/mod.rs +++ b/crates/miden-objects/src/transaction/inputs/mod.rs @@ -164,11 +164,6 @@ impl TransactionInputs { &self.foreign_account_code } - /// Returns the foreign account inputs to be consumed in the transaction. - pub fn foreign_account_inputs(&self) -> &[AccountInputs] { - self.tx_args.foreign_account_inputs() - } - /// Returns the advice inputs to be consumed in the transaction. pub fn advice_inputs(&self) -> &AdviceInputs { &self.advice_inputs diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index 7242eddf8b..6f8d1dc09e 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -1,11 +1,11 @@ -use alloc::collections::{BTreeMap, BTreeSet}; +use alloc::collections::BTreeMap; use alloc::sync::Arc; use alloc::vec::Vec; use miden_crypto::merkle::InnerNodeInfo; use miden_processor::MastNodeExt; -use super::{AccountInputs, Felt, Hasher, Word}; +use super::{Felt, Hasher, Word}; use crate::account::{PublicKeyCommitment, Signature}; use crate::note::{NoteId, NoteRecipient}; use crate::utils::serde::{ @@ -39,13 +39,12 @@ use crate::{EMPTY_WORD, MastForest, MastNodeId}; /// this argument is not specified, the [`EMPTY_WORD`] would be used as a default value. If the /// [AdviceInputs] are propagated with some user defined map entries, this argument could be used /// as a key to access the corresponding value. -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct TransactionArgs { tx_script: Option, tx_script_args: Word, note_args: BTreeMap, advice_inputs: AdviceInputs, - foreign_account_inputs: Vec, auth_args: Word, } @@ -55,7 +54,7 @@ impl TransactionArgs { /// Returns new [TransactionArgs] instantiated with the provided transaction script, advice /// map and foreign account inputs. - pub fn new(advice_map: AdviceMap, foreign_account_inputs: Vec) -> Self { + pub fn new(advice_map: AdviceMap) -> Self { let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() }; Self { @@ -63,7 +62,6 @@ impl TransactionArgs { tx_script_args: EMPTY_WORD, note_args: Default::default(), advice_inputs, - foreign_account_inputs, auth_args: EMPTY_WORD, } } @@ -140,19 +138,6 @@ impl TransactionArgs { &self.advice_inputs } - /// Returns a reference to the foreign account inputs in the transaction arguments. - pub fn foreign_account_inputs(&self) -> &[AccountInputs] { - &self.foreign_account_inputs - } - - /// Collects and returns a set containing all code commitments from foreign accounts. - pub fn to_foreign_account_code_commitments(&self) -> BTreeSet { - self.foreign_account_inputs() - .iter() - .map(|acc| acc.code().commitment()) - .collect() - } - /// Returns a reference to the authentication procedure argument, or [`EMPTY_WORD`] if the /// argument was not specified. /// @@ -239,13 +224,18 @@ impl TransactionArgs { } } +impl Default for TransactionArgs { + fn default() -> Self { + Self::new(AdviceMap::default()) + } +} + impl Serializable for TransactionArgs { fn write_into(&self, target: &mut W) { self.tx_script.write_into(target); self.tx_script_args.write_into(target); self.note_args.write_into(target); self.advice_inputs.write_into(target); - self.foreign_account_inputs.write_into(target); self.auth_args.write_into(target); } } @@ -256,7 +246,6 @@ impl Deserializable for TransactionArgs { let tx_script_args = Word::read_from(source)?; let note_args = BTreeMap::::read_from(source)?; let advice_inputs = AdviceInputs::read_from(source)?; - let foreign_account_inputs = Vec::::read_from(source)?; let auth_args = Word::read_from(source)?; Ok(Self { @@ -264,7 +253,6 @@ impl Deserializable for TransactionArgs { tx_script_args, note_args, advice_inputs, - foreign_account_inputs, auth_args, }) } @@ -347,7 +335,7 @@ mod tests { #[test] fn test_tx_args_serialization() { - let tx_args = TransactionArgs::new(AdviceMap::default(), std::vec::Vec::default()); + let tx_args = TransactionArgs::new(AdviceMap::default()); let bytes: std::vec::Vec = tx_args.to_bytes(); let decoded = TransactionArgs::read_from_bytes(&bytes).unwrap(); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 1e82deb222..6f6830a423 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -76,6 +76,11 @@ fn peek_balance_returns_correct_amount() -> anyhow::Result<()> { push.{suffix} push.{prefix} # => [prefix, suffix, account_vault_root_ptr, balance] + # emit an event to fetch the merkle path for the asset since peek_balance does not do + # that + emit.event("miden::account::vault_before_get_balance") + # => [prefix, suffix, account_vault_root_ptr, balance] + exec.asset_vault::peek_balance # => [peeked_balance] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 4e4b80b1d6..26321c012e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -9,33 +9,25 @@ use miden_lib::errors::tx_kernel_errors::{ }; use miden_lib::testing::mock_account::MockAccountExt; use miden_lib::testing::note::NoteBuilder; +use miden_lib::transaction::EXPIRATION_BLOCK_ELEMENT_IDX; use miden_lib::transaction::memory::{ NOTE_MEM_SIZE, OUTPUT_NOTE_ASSET_COMMITMENT_OFFSET, OUTPUT_NOTE_SECTION_OFFSET, }; -use miden_lib::transaction::{EXPIRATION_BLOCK_ELEMENT_IDX, TransactionKernel}; use miden_lib::utils::ScriptBuilder; use miden_objects::Word; use miden_objects::account::{Account, AccountDelta, AccountStorageDelta, AccountVaultDelta}; -use miden_objects::asset::{Asset, AssetVault, FungibleAsset}; +use miden_objects::asset::{Asset, FungibleAsset}; use miden_objects::note::{NoteTag, NoteType}; use miden_objects::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3, ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_SENDER, }; -use miden_objects::testing::constants::{ - CONSUMED_ASSET_1_AMOUNT, - CONSUMED_ASSET_2_AMOUNT, - CONSUMED_ASSET_3_AMOUNT, -}; use miden_objects::transaction::{OutputNote, OutputNotes}; use miden_processor::{Felt, ONE}; -use rand::rng; use super::{ZERO, create_mock_notes_procedure}; use crate::kernel_tests::tx::ExecutionOutputExt; @@ -212,142 +204,109 @@ fn test_compute_output_note_id() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_epilogue_asset_preservation_violation_too_few_input() -> anyhow::Result<()> { +/// Tests that a transaction fails due to the asset preservation rules when the input note has an +/// asset with amount 100 and the output note has the same asset with amount 200. +#[tokio::test] +async fn epilogue_fails_when_num_output_assets_exceed_num_input_assets() -> anyhow::Result<()> { + // Create an input asset with amount 100 and an output asset with amount 200. + let input_asset = FungibleAsset::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1.try_into()?, 100)?; + let output_asset = input_asset.add(input_asset)?; + let mut builder = MockChain::builder(); - let account = builder - .add_existing_mock_account_with_assets(Auth::IncrNonce, AssetVault::mock().assets())?; + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + // Add an input note that (automatically) adds its assets to the transaction's input vault, but + // _does not_ add the asset to the account. This is just to keep the test conceptually simple - + // there is no account involved. + let input_note = NoteBuilder::new(account.id(), *builder.rng_mut()) + .add_assets([Asset::from(input_asset)]) + .build()?; + builder.add_output_note(OutputNote::Full(input_note.clone())); let mock_chain = builder.build()?; - let fungible_asset_1: Asset = FungibleAsset::new( - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1.try_into()?, - CONSUMED_ASSET_1_AMOUNT, - )? - .into(); - let fungible_asset_2: Asset = FungibleAsset::new( - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into()?, - CONSUMED_ASSET_2_AMOUNT, - )? - .into(); - - let output_note_1 = NoteBuilder::new(account.id(), rng()) - .add_assets([fungible_asset_1]) - .dynamically_linked_libraries(TransactionKernel::mock_libraries()) - .build()?; - let output_note_2 = NoteBuilder::new(account.id(), rng()) - .add_assets([fungible_asset_2]) - .dynamically_linked_libraries(TransactionKernel::mock_libraries()) - .build()?; + let code = format!( + " + use.mock::account + use.mock::util + + begin + # create a note with the output asset + push.{OUTPUT_ASSET} + exec.util::create_random_note_with_asset drop + # => [] + end + ", + OUTPUT_ASSET = Word::from(output_asset), + ); - let input_note = create_spawn_note([&output_note_1, &output_note_2])?; + let builder = ScriptBuilder::with_mock_libraries()?; + let source_manager = builder.source_manager(); + let tx_script = builder.compile_tx_script(code)?; let tx_context = mock_chain .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[input_note])? - .extend_expected_output_notes(vec![ - OutputNote::Full(output_note_1), - OutputNote::Full(output_note_2), - ]) + .tx_script(tx_script) + .with_source_manager(source_manager) .build()?; - let output_notes_data_procedure = - create_mock_notes_procedure(tx_context.expected_output_notes()); - - let code = format!( - " - use.$kernel::prologue - use.mock::account - use.$kernel::epilogue - - {output_notes_data_procedure} - - begin - exec.prologue::prepare_transaction - exec.create_mock_notes - exec.epilogue::finalize_transaction - - # truncate the stack - movupw.3 dropw movupw.3 dropw movup.9 drop - end - " + let exec_output = tx_context.execute().await; + assert_transaction_executor_error!( + exec_output, + ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME ); - let exec_output = tx_context.execute_code_blocking(&code); - - assert_execution_error!(exec_output, ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME); Ok(()) } -#[test] -fn test_epilogue_asset_preservation_violation_too_many_fungible_input() -> anyhow::Result<()> { +/// Tests that a transaction fails due to the asset preservation rules when the input note has an +/// asset with amount 200 and the output note has the same asset with amount 100. +#[tokio::test] +async fn epilogue_fails_when_num_input_assets_exceed_num_output_assets() -> anyhow::Result<()> { + // Create an input asset with amount 200 and an output asset with amount 100. + let output_asset = FungibleAsset::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1.try_into()?, 100)?; + let input_asset = output_asset.add(output_asset)?; + let mut builder = MockChain::builder(); - let account = builder - .add_existing_mock_account_with_assets(Auth::IncrNonce, AssetVault::mock().assets())?; + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + // Add an input note that (automatically) adds its assets to the transaction's input vault, but + // _does not_ add the asset to the account. This is just to keep the test conceptually simple - + // there is no account involved. + let input_note = NoteBuilder::new(account.id(), *builder.rng_mut()) + .add_assets([Asset::from(output_asset)]) + .build()?; + builder.add_output_note(OutputNote::Full(input_note.clone())); let mock_chain = builder.build()?; - let fungible_asset_1: Asset = FungibleAsset::new( - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1.try_into()?, - CONSUMED_ASSET_1_AMOUNT, - )? - .into(); - let fungible_asset_2: Asset = FungibleAsset::new( - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into()?, - CONSUMED_ASSET_2_AMOUNT, - )? - .into(); - let fungible_asset_3: Asset = FungibleAsset::new( - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3.try_into()?, - CONSUMED_ASSET_3_AMOUNT, - )? - .into(); - - let output_note_1 = NoteBuilder::new(account.id(), rng()) - .add_assets([fungible_asset_1]) - .dynamically_linked_libraries(TransactionKernel::mock_libraries()) - .build()?; - let output_note_2 = NoteBuilder::new(account.id(), rng()) - .add_assets([fungible_asset_2]) - .dynamically_linked_libraries(TransactionKernel::mock_libraries()) - .build()?; - let output_note_3 = NoteBuilder::new(account.id(), rng()) - .add_assets([fungible_asset_3]) - .dynamically_linked_libraries(TransactionKernel::mock_libraries()) - .build()?; + let code = format!( + " + use.mock::account + use.mock::util + + begin + # create a note with the output asset + push.{OUTPUT_ASSET} + exec.util::create_random_note_with_asset drop + # => [] + end + ", + OUTPUT_ASSET = Word::from(input_asset), + ); - let input_note = create_spawn_note([&output_note_1, &output_note_2, &output_note_3])?; + let builder = ScriptBuilder::with_mock_libraries()?; + let source_manager = builder.source_manager(); + let tx_script = builder.compile_tx_script(code)?; let tx_context = mock_chain .build_tx_context(TxContextInput::AccountId(account.id()), &[], &[input_note])? - .extend_expected_output_notes(vec![ - OutputNote::Full(output_note_1), - OutputNote::Full(output_note_2), - ]) + .tx_script(tx_script) + .with_source_manager(source_manager) .build()?; - let output_notes_data_procedure = - create_mock_notes_procedure(tx_context.expected_output_notes()); - - let code = format!( - " - use.$kernel::prologue - use.mock::account - use.$kernel::epilogue - - {output_notes_data_procedure} - - begin - exec.prologue::prepare_transaction - exec.create_mock_notes - exec.epilogue::finalize_transaction - - # truncate the stack - movupw.3 dropw movupw.3 dropw movup.9 drop - end - " + let exec_output = tx_context.execute().await; + assert_transaction_executor_error!( + exec_output, + ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME ); - let exec_output = tx_context.execute_code_blocking(&code); - - assert_execution_error!(exec_output, ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME); Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index dfbb38a7e8..7088c0195f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -30,7 +30,6 @@ use miden_objects::account::{ AccountProcedureInfo, AccountStorage, AccountStorageMode, - PartialAccount, StorageSlot, }; use miden_objects::assembly::DefaultSourceManager; @@ -41,7 +40,6 @@ use miden_objects::testing::account_id::{ ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, }; use miden_objects::testing::storage::STORAGE_LEAVES_2; -use miden_objects::transaction::AccountInputs; use miden_objects::{FieldElement, Word, ZERO}; use miden_processor::fast::ExecutionOutput; use miden_processor::{AdviceInputs, Felt}; @@ -379,8 +377,7 @@ fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { .expect("failed to get foreign account inputs"); let tx_context = mock_chain - .build_tx_context(native_account.id(), &[], &[]) - .expect("failed to build tx context") + .build_tx_context(native_account.id(), &[], &[])? .foreign_accounts(vec![foreign_account_inputs_1, foreign_account_inputs_2]) .build()?; @@ -640,7 +637,6 @@ async fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts([foreign_account_inputs]) - .enable_lazy_loading() .tx_script(tx_script) .with_source_manager(source_manager) .build()? @@ -760,7 +756,6 @@ async fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Resu mock_chain .build_tx_context(native_account.id(), &[], &[])? .foreign_accounts([foreign_account_inputs]) - .enable_lazy_loading() .tx_script(tx_script) .with_source_manager(source_manager) .build()? @@ -867,7 +862,6 @@ async fn foreign_account_get_initial_balance() -> anyhow::Result<()> { mock_chain .build_tx_context(native_account.id(), &[], &[])? .foreign_accounts([foreign_account_inputs]) - .enable_lazy_loading() .tx_script(tx_script) .with_source_manager(source_manager) .build()? @@ -1088,7 +1082,6 @@ async fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(foreign_account_inputs) - .enable_lazy_loading() .extend_advice_inputs(advice_inputs) .tx_script(tx_script) .with_source_manager(source_manager) @@ -1213,7 +1206,7 @@ async fn test_nested_fpi_stack_overflow() -> anyhow::Result<()> { mock_chain.prove_next_block().unwrap(); - let foreign_accounts: Vec = foreign_accounts + let foreign_accounts: Vec<_> = foreign_accounts .iter() .map(|acc| { mock_chain @@ -1247,21 +1240,18 @@ async fn test_nested_fpi_stack_overflow() -> anyhow::Result<()> { end ", foreign_account_proc_hash = - foreign_accounts.last().unwrap().code().procedures()[1].mast_root(), - foreign_prefix = foreign_accounts.last().unwrap().id().prefix().as_felt(), - foreign_suffix = foreign_accounts.last().unwrap().id().suffix(), + foreign_accounts.last().unwrap().0.code().procedures()[1].mast_root(), + foreign_prefix = foreign_accounts.last().unwrap().0.id().prefix().as_felt(), + foreign_suffix = foreign_accounts.last().unwrap().0.id().suffix(), ); let tx_script = ScriptBuilder::default().compile_tx_script(code).unwrap(); let tx_context = mock_chain - .build_tx_context(native_account.id(), &[], &[]) - .expect("failed to build tx context") + .build_tx_context(native_account.id(), &[], &[])? .foreign_accounts(foreign_accounts) - .enable_lazy_loading() .tx_script(tx_script) - .build() - .unwrap(); + .build()?; let result = tx_context.execute().await; @@ -1370,7 +1360,6 @@ async fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(vec![foreign_account_inputs]) - .enable_lazy_loading() .extend_advice_inputs(advice_inputs) .tx_script(tx_script) .build()? @@ -1427,33 +1416,19 @@ async fn test_fpi_stale_account() -> anyhow::Result<()> { .storage_mut() .set_item(0, Word::from([Felt::ONE, Felt::ONE, Felt::ONE, Felt::ONE]))?; - // Place the modified account in the advice provider, which will cause the commitment mismatch. - let foreign_account_inputs = mock_chain + // We pass the modified foreign account with a witness that is valid against the ref block. This + // means the foreign account's commitment does not match the commitment that the account witness + // proves inclusion for. + let (_foreign_account, foreign_account_witness) = mock_chain .get_foreign_account_inputs(foreign_account.id()) .expect("failed to get foreign account inputs"); - // We want to create a mixed ForeignAccountInputs because we want to have a valid account - // witness against the ref block, but have newer account data (ie, a new state). Otherwise, - // any non-validity of the account witness is caught in - // TransactionExecutor::execute_transaction() (see `test_fpi_anchoring_validations()` for - // context on this check) - let overridden_partial_accounts = PartialAccount::new( - foreign_account.id(), - foreign_account.nonce(), - foreign_account.code().clone(), - foreign_account.storage().into(), - foreign_account.vault().into(), - None, - )?; - let overridden_foreign_account_inputs = - AccountInputs::new(overridden_partial_accounts, foreign_account_inputs.witness().clone()); - // The account tree from which the transaction inputs are fetched here has the state from the // original unmodified foreign account. This should result in the foreign account's proof to be // invalid for this account tree root. let tx_context = mock_chain .build_tx_context(native_account, &[], &[])? - .foreign_accounts(vec![overridden_foreign_account_inputs]) + .foreign_accounts(vec![(foreign_account.clone(), foreign_account_witness)]) .build()?; // Attempt to run FPI. @@ -1474,7 +1449,7 @@ async fn test_fpi_stale_account() -> anyhow::Result<()> { # => [pad(16)] # push some hash onto the stack - for this test it does not matter - padw + push.[1,2,3,4] # => [FOREIGN_PROC_ROOT, pad(16)] # push the foreign account ID @@ -1490,6 +1465,7 @@ async fn test_fpi_stale_account() -> anyhow::Result<()> { let result = tx_context.execute_code(&code).await.map(|_| ()); assert_execution_error!(result, ERR_FOREIGN_ACCOUNT_INVALID_COMMITMENT); + Ok(()) } @@ -1598,7 +1574,6 @@ async fn test_fpi_get_account_id() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(vec![foreign_account_inputs]) - .enable_lazy_loading() .tx_script(tx_script) .build()? .execute() @@ -1710,7 +1685,6 @@ async fn test_fpi_get_account_nonce() -> anyhow::Result<()> { .build_tx_context(native_account.id(), &[], &[]) .expect("failed to build tx context") .foreign_accounts(vec![foreign_account_inputs]) - .enable_lazy_loading() .tx_script(tx_script) .build()? .execute() diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index a3813ebe63..221477472f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -59,7 +59,6 @@ async fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<( let tx_context = TransactionContextBuilder::with_existing_mock_account() .tx_script(tx_script) .extend_input_notes(vec![asset_note]) - .enable_lazy_loading() .with_source_manager(source_manager) .build()?; let account = tx_context.account().clone(); @@ -125,7 +124,6 @@ async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result .build()? .build_tx_context(account, &[], &[])? .tx_script(tx_script) - .enable_lazy_loading() .with_source_manager(source_manager) .build()?; let account = tx_context.account().clone(); @@ -156,13 +154,7 @@ async fn loading_fee_asset_succeeds() -> anyhow::Result<()> { FungibleAsset::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into()?, 50)?.into(), ], )?; - builder - .build()? - .build_tx_context(account, &[], &[])? - .enable_lazy_loading() - .build()? - .execute() - .await?; + builder.build()?.build_tx_context(account, &[], &[])?.build()?.execute().await?; Ok(()) } @@ -220,7 +212,6 @@ async fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { let tx = TransactionContextBuilder::with_existing_mock_account() .tx_script(tx_script) - .enable_lazy_loading() .with_source_manager(source_manager) .build()? .execute() @@ -283,7 +274,6 @@ async fn getting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> { TransactionContextBuilder::with_existing_mock_account() .tx_script(tx_script) - .enable_lazy_loading() .with_source_manager(source_manager) .build()? .execute() diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 93799f299f..dc04799f6f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -1,6 +1,5 @@ use alloc::collections::BTreeMap; use alloc::sync::Arc; -use alloc::vec::Vec; use anyhow::Context; use miden_lib::account::wallets::BasicWallet; @@ -30,7 +29,7 @@ use miden_objects::testing::account_id::{ ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_SENDER, }; -use miden_objects::transaction::{AccountInputs, OutputNote, TransactionArgs}; +use miden_objects::transaction::{OutputNote, TransactionArgs}; use miden_objects::{Felt, Word, ZERO}; use miden_processor::fast::ExecutionOutput; use rand::SeedableRng; @@ -155,11 +154,8 @@ fn test_note_script_and_note_args() -> miette::Result<()> { (tx_context.input_notes().get_note(1).note().id(), note_args[0]), ]); - let tx_args = TransactionArgs::new( - tx_context.tx_args().advice_inputs().clone().map, - Vec::::new(), - ) - .with_note_args(note_args_map); + let tx_args = TransactionArgs::new(tx_context.tx_args().advice_inputs().clone().map) + .with_note_args(note_args_map); tx_context.set_tx_args(tx_args); let exec_output = tx_context.execute_code_blocking(code).unwrap(); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index de76bf5093..5f62fb0726 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -80,12 +80,7 @@ use miden_objects::testing::account_id::{ ACCOUNT_ID_SENDER, }; use miden_objects::testing::noop_auth_component::NoopAuthComponent; -use miden_objects::transaction::{ - AccountInputs, - ExecutedTransaction, - TransactionArgs, - TransactionScript, -}; +use miden_objects::transaction::{ExecutedTransaction, TransactionArgs, TransactionScript}; use miden_objects::{EMPTY_WORD, ONE, WORD_SIZE}; use miden_processor::fast::ExecutionOutput; use miden_processor::{AdviceInputs, Word}; @@ -155,12 +150,9 @@ fn test_transaction_prologue() -> anyhow::Result<()> { (tx_context.input_notes().get_note(1).note().id(), note_args[1]), ]); - let tx_args = TransactionArgs::new( - tx_context.tx_args().advice_inputs().clone().map, - Vec::::new(), - ) - .with_tx_script(tx_script) - .with_note_args(note_args_map); + let tx_args = TransactionArgs::new(tx_context.tx_args().advice_inputs().clone().map) + .with_tx_script(tx_script) + .with_note_args(note_args_map); tx_context.set_tx_args(tx_args); let exec_output = &tx_context.execute_code_blocking(code)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 1a05ca4e71..d31111067e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -1,5 +1,4 @@ use alloc::sync::Arc; -use alloc::vec::Vec; use anyhow::Context; use assert_matches::assert_matches; @@ -63,43 +62,6 @@ use crate::kernel_tests::tx::ExecutionOutputExt; use crate::utils::{create_public_p2any_note, create_spawn_note}; use crate::{Auth, MockChain, TransactionContextBuilder}; -/// Tests that executing a transaction with a foreign account whose inputs are stale fails. -#[tokio::test] -async fn transaction_with_stale_foreign_account_inputs_fails() -> anyhow::Result<()> { - // Create a chain with an account - let mut builder = MockChain::builder(); - let native_account = builder.add_existing_wallet(Auth::IncrNonce)?; - let foreign_account = builder.add_existing_wallet(Auth::IncrNonce)?; - let new_account = builder.create_new_wallet(Auth::IncrNonce)?; - - let mut mock_chain = builder.build()?; - - // Retrieve inputs which will become stale - let inputs = mock_chain - .get_foreign_account_inputs(foreign_account.id()) - .expect("failed to get foreign account inputs"); - - // Create a new unrelated account to modify the account tree. - let tx = mock_chain.build_tx_context(new_account, &[], &[])?.build()?.execute().await?; - mock_chain.add_pending_executed_transaction(&tx)?; - mock_chain.prove_next_block()?; - - // Attempt to execute with older foreign account inputs. The AccountWitness in the foreign - // account's inputs have become stale and so this should fail. - let transaction = mock_chain - .build_tx_context(native_account.id(), &[], &[])? - .foreign_accounts(vec![inputs]) - .build()? - .execute() - .await; - - assert_matches::assert_matches!( - transaction, - Err(TransactionExecutorError::ForeignAccountNotAnchoredInReference(_)) - ); - Ok(()) -} - /// Tests that consuming a note created in a block that is newer than the reference block of the /// transaction fails. #[tokio::test] @@ -627,7 +589,7 @@ async fn execute_tx_view_script() -> anyhow::Result<()> { .with_source_manager(source_manager); let stack_outputs = executor - .execute_tx_view_script(account_id, block_ref, tx_script, advice_inputs, Vec::default()) + .execute_tx_view_script(account_id, block_ref, tx_script, advice_inputs) .await?; assert_eq!(stack_outputs[..3], [Felt::new(7), Felt::new(2), ONE]); diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 2f91a6f277..626d7aa695 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -20,7 +20,6 @@ use miden_objects::block::{ }; use miden_objects::note::{Note, NoteHeader, NoteId, NoteInclusionProof, Nullifier}; use miden_objects::transaction::{ - AccountInputs, ExecutedTransaction, InputNote, InputNotes, @@ -712,18 +711,19 @@ impl MockChain { } /// Gets foreign account inputs to execute FPI transactions. - pub fn get_foreign_account_inputs( + /// + /// Only used internally and so does not need to be public. + #[cfg(test)] + pub(crate) fn get_foreign_account_inputs( &self, account_id: AccountId, - ) -> anyhow::Result { - let account = self.committed_account(account_id)?; + ) -> anyhow::Result<(Account, AccountWitness)> { + let account = self.committed_account(account_id)?.clone(); let account_witness = self.account_tree().open(account_id); assert_eq!(account_witness.state_commitment(), account.commitment()); - let partial_account = PartialAccount::from(account); - - Ok(AccountInputs::new(partial_account, account_witness)) + Ok((account, account_witness)) } /// Gets the inputs for a block for the provided batches. diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index c89645d7c4..eabab74d51 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -637,6 +637,13 @@ impl MockChainBuilder { // HELPER FUNCTIONS // ---------------------------------------------------------------------------------------- + /// Returns a mutable reference to the builder's RNG. + /// + /// This can be used when creating accounts or notes and randomness is required. + pub fn rng_mut(&mut self) -> &mut RpoRandomCoin { + &mut self.rng + } + /// Constructs a fungible asset based on the native asset ID and the provided amount. fn native_fee_asset(&self, amount: u64) -> anyhow::Result { FungibleAsset::new(self.native_asset_id, amount).context("failed to create fee asset") diff --git a/crates/miden-testing/src/mock_host.rs b/crates/miden-testing/src/mock_host.rs index 51461729d1..0787061bf7 100644 --- a/crates/miden-testing/src/mock_host.rs +++ b/crates/miden-testing/src/mock_host.rs @@ -63,6 +63,9 @@ impl<'store> MockHost<'store> { &TransactionEvent::AccountPushProcedureIndex, &TransactionEvent::LinkMapSet, &TransactionEvent::LinkMapGet, + // TODO: It should be possible to remove this after implementing + // https://github.com/0xMiden/miden-base/issues/1852. + &TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount, ] .map(TransactionEvent::event_id), ); @@ -78,6 +81,7 @@ impl<'store> MockHost<'store> { &TransactionEvent::AccountVaultBeforeGetBalance, &TransactionEvent::AccountVaultBeforeHasNonFungibleAsset, &TransactionEvent::AccountVaultBeforeAddAsset, + &TransactionEvent::AccountVaultBeforeRemoveAsset, &TransactionEvent::AccountStorageBeforeSetMapItem, &TransactionEvent::AccountStorageBeforeGetMapItem, ] diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index 18329cb801..fa4ed27024 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -9,31 +9,19 @@ use anyhow::Context; use miden_lib::testing::account_component::IncrNonceAuthComponent; use miden_lib::testing::mock_account::MockAccountExt; use miden_objects::EMPTY_WORD; -use miden_objects::account::{ - Account, - AccountHeader, - AccountId, - PartialAccount, - PartialStorage, - PartialStorageMap, - PublicKeyCommitment, - Signature, - StorageSlot, -}; +use miden_objects::account::{Account, AccountHeader, AccountId, PublicKeyCommitment, Signature}; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::debuginfo::SourceManagerSync; -use miden_objects::asset::PartialVault; +use miden_objects::block::AccountWitness; use miden_objects::note::{Note, NoteId, NoteScript}; use miden_objects::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; use miden_objects::testing::noop_auth_component::NoopAuthComponent; use miden_objects::transaction::{ - AccountInputs, OutputNote, TransactionArgs, TransactionInputs, TransactionScript, }; -use miden_objects::vm::AdviceMap; use miden_processor::{AdviceInputs, Felt, Word}; use miden_tx::TransactionMastStore; use miden_tx::auth::BasicAuthenticator; @@ -86,7 +74,7 @@ pub struct TransactionContextBuilder { advice_inputs: AdviceInputs, authenticator: Option, expected_output_notes: Vec, - foreign_account_inputs: BTreeMap, + foreign_account_inputs: BTreeMap, input_notes: Vec, tx_script: Option, tx_script_args: Word, @@ -114,7 +102,7 @@ impl TransactionContextBuilder { foreign_account_inputs: BTreeMap::new(), auth_args: EMPTY_WORD, signatures: Vec::new(), - is_lazy_loading_enabled: false, + is_lazy_loading_enabled: true, note_scripts: BTreeMap::new(), } } @@ -177,9 +165,13 @@ impl TransactionContextBuilder { } /// Set foreign account codes that are used by the transaction - pub fn foreign_accounts(mut self, inputs: impl IntoIterator) -> Self { - self.foreign_account_inputs - .extend(inputs.into_iter().map(|account_inputs| (account_inputs.id(), account_inputs))); + pub fn foreign_accounts( + mut self, + inputs: impl IntoIterator, + ) -> Self { + self.foreign_account_inputs.extend( + inputs.into_iter().map(|(account, witness)| (account.id(), (account, witness))), + ); self } @@ -218,20 +210,10 @@ impl TransactionContextBuilder { self } - /// Causes the transaction to only construct a minimal partial account as the transaction - /// input, causing lazy loading of assets and storage map items throughout transaction - /// execution. Additionally, foreign accounts aren't provided via the transaction args but are - /// lazy loaded as well. - /// - /// This exists to test lazy loading selectively and should go away in the future. - pub fn enable_lazy_loading(mut self) -> Self { - self.is_lazy_loading_enabled = true; - self - } - /// Disables lazy loading. /// - /// This is the opposite of [`Self::enable_lazy_loading`] - see its docs for details. + /// Only affects [`TransactionContext::execute_code`] and causes the host to _not_ handle lazy + /// loading events. pub fn disable_lazy_loading(mut self) -> Self { self.is_lazy_loading_enabled = false; self @@ -286,7 +268,7 @@ impl TransactionContextBuilder { /// If no transaction inputs were provided manually, an ad-hoc MockChain is created in order /// to generate valid block data for the required notes. pub fn build(self) -> anyhow::Result { - let tx_inputs = match self.tx_inputs { + let mut tx_inputs = match self.tx_inputs { Some(tx_inputs) => tx_inputs, None => { // If no specific transaction inputs was provided, initialize an ad-hoc mockchain @@ -310,14 +292,8 @@ impl TransactionContextBuilder { }, }; - let foreign_account_inputs = if self.is_lazy_loading_enabled { - Vec::new() - } else { - self.foreign_account_inputs.values().cloned().collect() - }; + let tx_args = TransactionArgs::default().with_note_args(self.note_args); - let tx_args = TransactionArgs::new(AdviceMap::default(), foreign_account_inputs) - .with_note_args(self.note_args); let mut tx_args = if let Some(tx_script) = self.tx_script { tx_args.with_tx_script_and_args(tx_script, self.tx_script_args) } else { @@ -331,26 +307,14 @@ impl TransactionContextBuilder { tx_args.add_signature(public_key_commitment, message, signature); } - // If partial loading is enabled, construct an account that doesn't contain all - // merkle paths of assets and storage maps, in order to test lazy loading. - // Otherwise, load the full account. - let tx_inputs = if self.is_lazy_loading_enabled { - let (_, block_header, partial_blockchain, input_notes, _) = tx_inputs.into_parts(); - // Note that we use self.account instead of account, because we cannot do the same - // operation on a partial vault. - let account = minimal_partial_account(&self.account)?; - TransactionInputs::new(account, block_header, partial_blockchain, input_notes)? - .with_tx_args(tx_args) - } else { - tx_inputs.with_tx_args(tx_args) - }; + tx_inputs.set_tx_args(tx_args); let mast_store = { let mast_forest_store = TransactionMastStore::new(); mast_forest_store.load_account_code(tx_inputs.account().code()); - for acc_inputs in self.foreign_account_inputs.values() { - mast_forest_store.insert(acc_inputs.code().mast()); + for (account, _) in self.foreign_account_inputs.values() { + mast_forest_store.insert(account.code().mast()); } mast_forest_store @@ -375,43 +339,3 @@ impl Default for TransactionContextBuilder { Self::with_existing_mock_account() } } - -/// Creates a minimal [`PartialAccount`] from the provided full [`Account`]. -fn minimal_partial_account(account: &Account) -> anyhow::Result { - // Construct a partial vault that tracks the empty word, but none of the assets - // that are actually in the asset tree. That way, the partial vault has the same - // root as the full vault, but will not add any relevant merkle paths to the - // merkle store, which will test lazy loading of assets. - let mut partial_vault = PartialVault::default(); - partial_vault.add(account.vault().open(Word::empty()))?; - - // Construct a partial storage that tracks the empty word in all storage maps, but none - // of the other keys, following the same rationale as the partial vault above. - let storage_header = account.storage().to_header(); - let storage_maps = - account.storage().slots().iter().filter_map(|storage_slot| match storage_slot { - StorageSlot::Map(storage_map) => { - let mut partial_storage_map = PartialStorageMap::default(); - let key = Word::empty(); - let witness = storage_map.open(&key); - partial_storage_map - .add(witness) - .expect("adding the first proof should never error"); - Some(partial_storage_map) - }, - _ => None, - }); - let partial_storage = PartialStorage::new(storage_header, storage_maps) - .expect("provided storage maps should match storage header"); - - let account = PartialAccount::new( - account.id(), - account.nonce(), - account.code().clone(), - partial_storage, - partial_vault, - None, - )?; - - Ok(account) -} diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index cdd7c281ae..d9efbbbf27 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -8,7 +8,7 @@ use miden_objects::account::{Account, AccountId, PartialAccount, StorageMapWitne use miden_objects::assembly::debuginfo::{SourceLanguage, Uri}; use miden_objects::assembly::{SourceManager, SourceManagerSync}; use miden_objects::asset::AssetWitness; -use miden_objects::block::{BlockHeader, BlockNumber}; +use miden_objects::block::{AccountWitness, BlockHeader, BlockNumber}; use miden_objects::note::{Note, NoteScript}; use miden_objects::transaction::{ AccountInputs, @@ -48,7 +48,7 @@ use crate::tx_context::builder::MockAuthenticator; pub struct TransactionContext { pub(super) account: Account, pub(super) expected_output_notes: Vec, - pub(super) foreign_account_inputs: BTreeMap, + pub(super) foreign_account_inputs: BTreeMap, pub(super) tx_inputs: TransactionInputs, pub(super) mast_store: TransactionMastStore, pub(super) authenticator: Option, @@ -113,7 +113,7 @@ impl TransactionContext { let account_procedure_idx_map = AccountProcedureIndexMap::new( [self.tx_inputs().account().code()] .into_iter() - .chain(self.tx_args().foreign_account_inputs().iter().map(|inputs| inputs.code())), + .chain(self.foreign_account_inputs.values().map(|(account, _)| account.code())), ) .expect("constructing account procedure index map should work"); @@ -220,11 +220,17 @@ impl DataStore for TransactionContext { // Note that we cannot validate that the foreign account inputs are valid for the // transaction's reference block. async move { - self.foreign_account_inputs.get(&foreign_account_id).cloned().ok_or_else(|| { - DataStoreError::other(format!( - "failed to find foreign account {foreign_account_id}" - )) - }) + let (foreign_account, account_witness) = + self.foreign_account_inputs.get(&foreign_account_id).ok_or_else(|| { + DataStoreError::other(format!( + "failed to find foreign account {foreign_account_id}" + )) + })?; + + Ok(AccountInputs::new( + PartialAccount::from(foreign_account), + account_witness.clone(), + )) } } @@ -245,7 +251,7 @@ impl DataStore for TransactionContext { Ok(self.account().vault().open(vault_key)) } else { - let foreign_account_inputs = self + let (foreign_account, _witness) = self .foreign_account_inputs .iter() .find_map( @@ -259,21 +265,14 @@ impl DataStore for TransactionContext { )) })?; - if foreign_account_inputs.account().vault().root() != vault_root { + if foreign_account.vault().root() != vault_root { return Err(DataStoreError::other(format!( "foreign account {account_id} has vault root {} but {vault_root} was requested", - foreign_account_inputs.account().vault().root() + foreign_account.vault().root() ))); } - foreign_account_inputs.account().vault().open(vault_key).map_err(|err| { - DataStoreError::other_with_source( - format!( - "failed to open vault_key {vault_key} in foreign account {account_id}" - ), - err, - ) - }) + Ok(foreign_account.vault().open(vault_key)) } } } @@ -306,7 +305,7 @@ impl DataStore for TransactionContext { Ok(storage_map.open(&map_key)) } else { - let foreign_account_inputs = self + let (foreign_account, _witness) = self .foreign_account_inputs .iter() .find_map( @@ -320,22 +319,21 @@ impl DataStore for TransactionContext { )) })?; - let map = foreign_account_inputs - .account() + let map = foreign_account .storage() - .maps() - .find(|map| map.root() == map_root) + .slots() + .iter() + .find_map(|slot| match slot { + StorageSlot::Map(storage_map) if storage_map.root() == map_root => {Some(storage_map)}, + _ => None, + }) .ok_or_else(|| { DataStoreError::other(format!( "failed to find storage map with root {map_root} in foreign account {account_id}" )) })?; - map.open(&map_key).map_err(|err| { - DataStoreError::other_with_source(format!( - "failed to open {map_key} in storage map of foreign account {account_id}" - ), err) - }) + Ok(map.open(&map_key)) } } } diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index 7d31f13066..cdf5d0711c 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -1,15 +1,13 @@ use alloc::collections::BTreeSet; use alloc::sync::Arc; -use alloc::vec::Vec; use miden_lib::transaction::TransactionKernel; use miden_objects::account::AccountId; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::debuginfo::SourceManagerSync; use miden_objects::asset::Asset; -use miden_objects::block::{BlockHeader, BlockNumber}; +use miden_objects::block::BlockNumber; use miden_objects::transaction::{ - AccountInputs, ExecutedTransaction, InputNote, InputNotes, @@ -222,10 +220,8 @@ where block_ref: BlockNumber, tx_script: TransactionScript, advice_inputs: AdviceInputs, - foreign_account_inputs: Vec, ) -> Result<[Felt; 16], TransactionExecutorError> { - let mut tx_args = TransactionArgs::new(Default::default(), foreign_account_inputs) - .with_tx_script(tx_script); + let mut tx_args = TransactionArgs::default().with_tx_script(tx_script); tx_args.extend_advice_inputs(advice_inputs); let notes = InputNotes::default(); @@ -268,8 +264,6 @@ where .await .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?; - validate_account_inputs(&tx_args, &block_header)?; - let tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes) .map_err(TransactionExecutorError::InvalidTransactionInputs)? .with_tx_args(tx_args); @@ -404,25 +398,6 @@ fn build_executed_transaction Result<(), TransactionExecutorError> { - // Validate that foreign account inputs are anchored in the reference block - for foreign_account in tx_args.foreign_account_inputs() { - let computed_account_root = foreign_account.compute_account_root().map_err(|err| { - TransactionExecutorError::InvalidAccountWitness(foreign_account.id(), err) - })?; - if computed_account_root != ref_block.account_root() { - return Err(TransactionExecutorError::ForeignAccountNotAnchoredInReference( - foreign_account.id(), - )); - } - } - Ok(()) -} - /// Validates that input notes were not created after the reference block. /// /// Returns the set of block numbers required to execute the provided notes. From 719ff03d1482e6ce2ad4e986f59ec7b9a8ddf962 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Fri, 10 Oct 2025 12:15:39 -0700 Subject: [PATCH 086/133] chore: refresh Cargo.lock --- Cargo.lock | 225 +++++++++++++++++++++++++---------------------------- 1 file changed, 108 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00cd0181fc..678b3732bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,9 +319,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "bytes" @@ -337,9 +337,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.40" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "jobserver", @@ -782,7 +782,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -813,9 +813,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "findshlibs" @@ -849,9 +849,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "3.1.2" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f150ffc8782f35521cec2b23727707cb4045706ba3c854e86bef66b3a8cdbd" +checksum = "6ad492b2cf1d89d568a43508ab24f98501fe03f2f31c01e1d0fe7366a71745d2" dependencies = [ "autocfg", ] @@ -1013,12 +1013,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1266,9 +1267,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" @@ -1290,11 +1291,10 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -1343,9 +1343,9 @@ dependencies = [ [[package]] name = "miden-air" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de4f41b69be0ed3fd236525bb731188bb8d902cb99009f5c2ef12c6570a9b1ef" +checksum = "28b72121bacf147becf7eadcfc7bcd62d41c1e97c0d2cfdc1fd5dba2531721a8" dependencies = [ "miden-core", "thiserror", @@ -1355,9 +1355,9 @@ dependencies = [ [[package]] name = "miden-assembly" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6707e889509193d5e503becb0edc054be598b60e58dba27c65e81df5b93d0f37" +checksum = "bc57c069054e1f34052e60b55e7c7e066c09fe6f23fc6ca747b25b9afa9b6abd" dependencies = [ "log", "miden-assembly-syntax", @@ -1369,9 +1369,9 @@ dependencies = [ [[package]] name = "miden-assembly-syntax" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654c557877f7bcdef7fb31db683fdbc4ee70c27c0f302920113eba68b690d5d9" +checksum = "b73b2c264d5f65a3f62f49a6c4dfedf37152454d6d963330bd0c5b183b2e2e6c" dependencies = [ "aho-corasick", "lalrpop", @@ -1400,9 +1400,9 @@ dependencies = [ [[package]] name = "miden-core" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d53557ea0e5e529675c562c1a90eb5afc242fdac14d1f85b98d770304f9a24" +checksum = "996f8d8e2a4dd4eef54225f6f116c73b14f03256bc23ea5d4260fe803dd103e2" dependencies = [ "enum_dispatch", "miden-crypto", @@ -1445,9 +1445,9 @@ dependencies = [ [[package]] name = "miden-debug-types" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "188d28a5d7df27e4735095e717845a4558b2d121f822f66e8cc2ddfddf92d5bc" +checksum = "3c24f4040fd3a5b714f2492c723b91dffe29b1af591d15f393dd102063725cd0" dependencies = [ "memchr", "miden-crypto", @@ -1490,9 +1490,9 @@ dependencies = [ [[package]] name = "miden-mast-package" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1777c12930a070feed3f5c777db8374ba71bc36f153223c48cd9ee82cde3f4" +checksum = "c25726075ff6b7262fbb12d4914672ae6999fbeaa6e3883c9cff69348ad1c09c" dependencies = [ "derive_more", "miden-assembly-syntax", @@ -1577,9 +1577,9 @@ dependencies = [ [[package]] name = "miden-processor" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac41df96664be720b0fbffd9c132a35a0432b5d2e625ba191a83b0d89646a2c" +checksum = "943b22d826ce116cbaf8633c699e45eece83dc4c2facb1677e067b42f433aba8" dependencies = [ "miden-air", "miden-core", @@ -1594,9 +1594,9 @@ dependencies = [ [[package]] name = "miden-prover" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab58a684b0133195fbf2583d3759635fbf1fd796d3a1608752509f279be0b30" +checksum = "743e27899f2a56e92da93efc287fe76a635446251dbecc2138e8b85ecdcfd0bd" dependencies = [ "miden-air", "miden-debug-types", @@ -1608,9 +1608,9 @@ dependencies = [ [[package]] name = "miden-stdlib" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c09061431876b4cf6b2a3cfdf876bd8fde129233effd5906d3bfda8ec2602e29" +checksum = "7cebf10ea08fc7e015e4391953e8953ed689104973084e1c4f057473017e6518" dependencies = [ "env_logger", "fs-err", @@ -1673,9 +1673,9 @@ dependencies = [ [[package]] name = "miden-utils-diagnostics" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa555f9ee912ca23317d75fab2a65380b8191de66715237c228cf09165266dec" +checksum = "9da59c10824c85d585145aff6ef760f0baaaf6c4576c8ad241c281ce2e345b79" dependencies = [ "miden-crypto", "miden-debug-types", @@ -1686,9 +1686,9 @@ dependencies = [ [[package]] name = "miden-utils-sync" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1873b21e18ba6ab6c697f155a2adb8f35f5ac386f6bb41ae7c207b2ad683642" +checksum = "498138c030b4bf3808e2ef87ba1170b22d5572a8ef6a0acd5a5f10d2bfc91e72" dependencies = [ "lock_api", "loom", @@ -1697,9 +1697,9 @@ dependencies = [ [[package]] name = "miden-verifier" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759989d95534d658ad6a9d7946bbe617814611a1b89b42e944462ea8ffb4de9d" +checksum = "edf15bf92a77f8f7d0291b155af55bb5576fe4277d897dc035e73dc39275bace" dependencies = [ "miden-air", "miden-core", @@ -1767,11 +1767,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1916,9 +1916,9 @@ checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1926,15 +1926,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -2221,9 +2221,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] @@ -2358,7 +2358,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2479,9 +2479,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ "serde_core", ] @@ -2586,9 +2586,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "str_stack" @@ -2693,7 +2693,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.1.2", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2702,7 +2702,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2732,7 +2732,7 @@ checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "smawk", "unicode-linebreak", - "unicode-width 0.2.1", + "unicode-width 0.2.2", ] [[package]] @@ -2826,9 +2826,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "indexmap", "serde_core", @@ -2841,18 +2841,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.6" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", "toml_datetime", @@ -2862,18 +2862,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "tracing" @@ -3005,9 +3005,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -3187,7 +3187,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -3244,9 +3244,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.1" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -3255,9 +3255,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.2" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -3272,9 +3272,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" @@ -3313,15 +3313,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -3337,16 +3328,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.4", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -3382,19 +3373,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.4" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.0", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -3420,9 +3411,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -3438,9 +3429,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -3456,9 +3447,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -3468,9 +3459,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -3486,9 +3477,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -3504,9 +3495,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -3522,9 +3513,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -3540,9 +3531,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" From 998c14cefdbb7362754e8575ad1f0cc70150f609 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 13 Oct 2025 08:43:24 +0200 Subject: [PATCH 087/133] chore: Remove `execute_code_blocking` (#1991) * chore: Remove `execute_code_blocking` * chore: Remove tokio dep from miden-testing * fix: toml fmt --- crates/miden-testing/Cargo.toml | 4 +- .../src/kernel_tests/tx/test_account.rs | 74 +++++++++--------- .../src/kernel_tests/tx/test_active_note.rs | 38 +++++---- .../src/kernel_tests/tx/test_asset.rs | 18 ++--- .../src/kernel_tests/tx/test_asset_vault.rs | 78 +++++++++---------- .../src/kernel_tests/tx/test_epilogue.rs | 42 +++++----- .../src/kernel_tests/tx/test_faucet.rs | 78 +++++++++---------- .../src/kernel_tests/tx/test_fpi.rs | 16 ++-- .../src/kernel_tests/tx/test_link_map.rs | 46 +++++------ .../src/kernel_tests/tx/test_note.rs | 30 +++---- .../src/kernel_tests/tx/test_output_note.rs | 50 ++++++------ .../src/kernel_tests/tx/test_prologue.rs | 18 ++--- .../miden-testing/src/tx_context/context.rs | 8 -- 13 files changed, 244 insertions(+), 256 deletions(-) diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index 313f6cf893..01e4fe2fc2 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -32,9 +32,7 @@ itertools = { default-features = false, features = ["use_alloc"], version = "0 rand = { features = ["os_rng", "small_rng"], workspace = true } rand_chacha = { default-features = false, version = "0.9" } thiserror = { workspace = true } -# TODO: Remove when execute_code_blocking is gone. -tokio = { features = ["macros", "rt"], workspace = true } -winterfell = { version = "0.13" } +winterfell = { version = "0.13" } [dev-dependencies] anyhow = { features = ["backtrace", "std"], workspace = true } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index a691fcb0b7..a79f333af2 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -340,8 +340,8 @@ async fn test_is_faucet_procedure() -> miette::Result<()> { // ================================================================================================ // TODO: update this test once the ability to change the account code will be implemented -#[test] -pub fn test_compute_code_commitment() -> miette::Result<()> { +#[tokio::test] +pub async fn test_compute_code_commitment() -> miette::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); let account = tx_context.account(); @@ -361,7 +361,7 @@ pub fn test_compute_code_commitment() -> miette::Result<()> { expected_code_commitment = account.code().commitment() ); - tx_context.execute_code_blocking(&code)?; + tx_context.execute_code(&code).await?; Ok(()) } @@ -369,8 +369,8 @@ pub fn test_compute_code_commitment() -> miette::Result<()> { // ACCOUNT STORAGE TESTS // ================================================================================================ -#[test] -fn test_get_item() -> miette::Result<()> { +#[tokio::test] +async fn test_get_item() -> miette::Result<()> { for storage_item in [AccountStorage::mock_item_0(), AccountStorage::mock_item_1()] { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); @@ -395,14 +395,14 @@ fn test_get_item() -> miette::Result<()> { item_value = &storage_item.slot.value(), ); - tx_context.execute_code_blocking(&code).unwrap(); + tx_context.execute_code(&code).await.unwrap(); } Ok(()) } -#[test] -fn test_get_map_item() -> miette::Result<()> { +#[tokio::test] +async fn test_get_map_item() -> miette::Result<()> { let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) .with_component(MockAccountComponent::with_slots(vec![AccountStorage::mock_item_2().slot])) @@ -432,7 +432,7 @@ fn test_get_map_item() -> miette::Result<()> { map_key = &key, ); - let exec_output = &mut tx_context.execute_code_blocking(&code)?; + let exec_output = &mut tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_stack_word(0), value, @@ -458,8 +458,8 @@ fn test_get_map_item() -> miette::Result<()> { Ok(()) } -#[test] -fn test_get_storage_slot_type() -> miette::Result<()> { +#[tokio::test] +async fn test_get_storage_slot_type() -> miette::Result<()> { for storage_item in [ AccountStorage::mock_item_0(), AccountStorage::mock_item_1(), @@ -488,7 +488,7 @@ fn test_get_storage_slot_type() -> miette::Result<()> { item_index = storage_item.index, ); - let exec_output = &tx_context.execute_code_blocking(&code).unwrap(); + let exec_output = &tx_context.execute_code(&code).await.unwrap(); let storage_slot_type = storage_item.slot.slot_type(); @@ -504,8 +504,8 @@ fn test_get_storage_slot_type() -> miette::Result<()> { Ok(()) } -#[test] -fn test_set_item() -> miette::Result<()> { +#[tokio::test] +async fn test_set_item() -> miette::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); let new_storage_item = Word::from([91, 92, 93, 94u32]); @@ -537,13 +537,13 @@ fn test_set_item() -> miette::Result<()> { new_storage_item_index = 0, ); - tx_context.execute_code_blocking(&code).unwrap(); + tx_context.execute_code(&code).await.unwrap(); Ok(()) } -#[test] -fn test_set_map_item() -> miette::Result<()> { +#[tokio::test] +async fn test_set_map_item() -> miette::Result<()> { let (new_key, new_value) = (Word::from([109, 110, 111, 112u32]), Word::from([9, 10, 11, 12u32])); @@ -586,7 +586,7 @@ fn test_set_map_item() -> miette::Result<()> { new_value = &new_value, ); - let exec_output = &tx_context.execute_code_blocking(&code).unwrap(); + let exec_output = &tx_context.execute_code(&code).await.unwrap(); let mut new_storage_map = AccountStorage::mock_map(); new_storage_map.insert(new_key, new_value).unwrap(); @@ -884,8 +884,8 @@ async fn creating_account_with_procedure_offset_plus_size_out_of_bounds_fails() Ok(()) } -#[test] -fn test_get_initial_storage_commitment() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_initial_storage_commitment() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let code = format!( @@ -904,7 +904,7 @@ fn test_get_initial_storage_commitment() -> anyhow::Result<()> { "#, expected_storage_commitment = &tx_context.account().storage().commitment(), ); - tx_context.execute_code_blocking(&code)?; + tx_context.execute_code(&code).await?; Ok(()) } @@ -918,8 +918,8 @@ fn test_get_initial_storage_commitment() -> anyhow::Result<()> { /// - Right after the previous call to make sure it returns the same commitment from the cached /// data. /// - After updating the 2nd storage slot (map slot). -#[test] -fn test_compute_storage_commitment() -> anyhow::Result<()> { +#[tokio::test] +async fn test_compute_storage_commitment() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); let mut account_clone = tx_context.account().clone(); let account_storage = account_clone.storage_mut(); @@ -978,7 +978,7 @@ fn test_compute_storage_commitment() -> anyhow::Result<()> { end "#, ); - tx_context.execute_code_blocking(&code)?; + tx_context.execute_code(&code).await?; Ok(()) } @@ -1063,8 +1063,8 @@ async fn proven_tx_storage_map_matches_executed_tx_for_new_account() -> anyhow:: // ACCOUNT VAULT TESTS // ================================================================================================ -#[test] -fn test_get_vault_root() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_vault_root() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let mut account = tx_context.account().clone(); @@ -1094,7 +1094,7 @@ fn test_get_vault_root() -> anyhow::Result<()> { ", expected_vault_root = &account.vault().root(), ); - tx_context.execute_code_blocking(&code)?; + tx_context.execute_code(&code).await?; // get the current vault root account.vault_mut().add_asset(fungible_asset)?; @@ -1122,7 +1122,7 @@ fn test_get_vault_root() -> anyhow::Result<()> { fungible_asset = Word::from(&fungible_asset), expected_vault_root = &account.vault().root(), ); - tx_context.execute_code_blocking(&code)?; + tx_context.execute_code(&code).await?; Ok(()) } @@ -1390,8 +1390,8 @@ async fn test_get_init_balance_subtraction() -> anyhow::Result<()> { // PROCEDURE AUTHENTICATION TESTS // ================================================================================================ -#[test] -fn test_authenticate_and_track_procedure() -> miette::Result<()> { +#[tokio::test] +async fn test_authenticate_and_track_procedure() -> miette::Result<()> { let mock_component = MockAccountComponent::with_empty_slots(); let account_code = AccountCode::from_components( @@ -1431,7 +1431,7 @@ fn test_authenticate_and_track_procedure() -> miette::Result<()> { // Execution of this code will return an EventError(UnknownAccountProcedure) for procs // that are not in the advice provider. - let exec_output = tx_context.execute_code_blocking(&code); + let exec_output = tx_context.execute_code(&code).await; match valid { true => { @@ -1624,8 +1624,8 @@ async fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { // ACCOUNT INITIAL STORAGE TESTS // ================================================================================================ -#[test] -fn test_get_initial_item() -> miette::Result<()> { +#[tokio::test] +async fn test_get_initial_item() -> miette::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); // Test that get_initial_item returns the initial value before any changes @@ -1665,13 +1665,13 @@ fn test_get_initial_item() -> miette::Result<()> { expected_initial_value = &AccountStorage::mock_item_0().slot.value(), ); - tx_context.execute_code_blocking(&code).unwrap(); + tx_context.execute_code(&code).await.unwrap(); Ok(()) } -#[test] -fn test_get_initial_map_item() -> miette::Result<()> { +#[tokio::test] +async fn test_get_initial_map_item() -> miette::Result<()> { let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) .with_component(MockAccountComponent::with_slots(vec![AccountStorage::mock_item_2().slot])) @@ -1736,7 +1736,7 @@ fn test_get_initial_map_item() -> miette::Result<()> { new_value = &new_value, ); - tx_context.execute_code_blocking(&code).unwrap(); + tx_context.execute_code(&code).await.unwrap(); Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 675907107e..3d2b98b10f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -119,8 +119,8 @@ async fn test_active_note_get_metadata() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_active_note_get_sender() -> anyhow::Result<()> { +#[tokio::test] +async fn test_active_note_get_sender() -> anyhow::Result<()> { let tx_context = { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); @@ -150,7 +150,7 @@ fn test_active_note_get_sender() -> anyhow::Result<()> { end "; - let exec_output = tx_context.execute_code_blocking(code)?; + let exec_output = tx_context.execute_code(code).await?; let sender = tx_context.input_notes().get_note(0).note().metadata().sender(); assert_eq!(exec_output.stack[0], sender.prefix().as_felt()); @@ -159,8 +159,8 @@ fn test_active_note_get_sender() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_active_note_get_assets() -> anyhow::Result<()> { +#[tokio::test] +async fn test_active_note_get_assets() -> anyhow::Result<()> { // Creates a mockchain with an account and a note that it can consume let tx_context = { let mut builder = MockChain::builder(); @@ -292,12 +292,12 @@ fn test_active_note_get_assets() -> anyhow::Result<()> { NOTE_1_ASSET_ASSERTIONS = construct_asset_assertions(notes.get_note(1).note()), ); - tx_context.execute_code_blocking(&code)?; + tx_context.execute_code(&code).await?; Ok(()) } -#[test] -fn test_active_note_get_inputs() -> anyhow::Result<()> { +#[tokio::test] +async fn test_active_note_get_inputs() -> anyhow::Result<()> { // Creates a mockchain with an account and a note that it can consume let tx_context = { let mut builder = MockChain::builder(); @@ -380,7 +380,7 @@ fn test_active_note_get_inputs() -> anyhow::Result<()> { NOTE_0_PTR = 100000000, ); - tx_context.execute_code_blocking(&code)?; + tx_context.execute_code(&code).await?; Ok(()) } @@ -391,8 +391,8 @@ fn test_active_note_get_inputs() -> anyhow::Result<()> { /// Previously this setup was leading to the incorrect number of note inputs computed during the /// `get_inputs` procedure, see the [issue #1363](https://github.com/0xMiden/miden-base/issues/1363) /// for more details. -#[test] -fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { +#[tokio::test] +async fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { let sender_id = ACCOUNT_ID_SENDER .try_into() .context("failed to convert ACCOUNT_ID_SENDER to account ID")?; @@ -459,15 +459,13 @@ fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { end "; - tx_context - .execute_code_blocking(tx_code) - .context("transaction execution failed")?; + tx_context.execute_code(tx_code).await.context("transaction execution failed")?; Ok(()) } -#[test] -fn test_active_note_get_serial_number() -> anyhow::Result<()> { +#[tokio::test] +async fn test_active_note_get_serial_number() -> anyhow::Result<()> { let tx_context = { let mut builder = MockChain::builder(); let account = builder.add_existing_wallet(Auth::BasicAuth)?; @@ -498,15 +496,15 @@ fn test_active_note_get_serial_number() -> anyhow::Result<()> { end "; - let exec_output = tx_context.execute_code_blocking(code)?; + let exec_output = tx_context.execute_code(code).await?; let serial_number = tx_context.input_notes().get_note(0).note().serial_num(); assert_eq!(exec_output.get_stack_word(0), serial_number); Ok(()) } -#[test] -fn test_active_note_get_script_root() -> anyhow::Result<()> { +#[tokio::test] +async fn test_active_note_get_script_root() -> anyhow::Result<()> { let tx_context = { let mut builder = MockChain::builder(); let account = builder.add_existing_wallet(Auth::BasicAuth)?; @@ -537,7 +535,7 @@ fn test_active_note_get_script_root() -> anyhow::Result<()> { end "; - let exec_output = tx_context.execute_code_blocking(code)?; + let exec_output = tx_context.execute_code(code).await?; let script_root = tx_context.input_notes().get_note(0).note().script().root(); assert_eq!(exec_output.get_stack_word(0), script_root); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs index 78efa6d420..f1a35db25d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs @@ -11,8 +11,8 @@ use miden_objects::{Felt, Hasher, Word}; use crate::TransactionContextBuilder; use crate::kernel_tests::tx::ExecutionOutputExt; -#[test] -fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_fungible_faucet( ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, Felt::new(FUNGIBLE_FAUCET_INITIAL_BALANCE), @@ -37,7 +37,7 @@ fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { " ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); assert_eq!( @@ -52,8 +52,8 @@ fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_non_fungible_faucet(NonFungibleAsset::mock_issuer().into()) .build()?; @@ -79,14 +79,14 @@ fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { non_fungible_asset_data_hash = Hasher::hash(&NON_FUNGIBLE_ASSET_DATA), ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!(exec_output.get_stack_word(0), Word::from(non_fungible_asset)); Ok(()) } -#[test] -fn test_validate_non_fungible_asset() -> anyhow::Result<()> { +#[tokio::test] +async fn test_validate_non_fungible_asset() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_non_fungible_faucet(NonFungibleAsset::mock_issuer().into()) .build()?; @@ -107,7 +107,7 @@ fn test_validate_non_fungible_asset() -> anyhow::Result<()> { " ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!(exec_output.get_stack_word(0), non_fungible_asset); Ok(()) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 6f6830a423..2afd5ea086 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -21,8 +21,8 @@ use crate::kernel_tests::tx::ExecutionOutputExt; use crate::{TransactionContextBuilder, assert_execution_error}; /// Tests that account::get_balance returns the correct amount. -#[test] -fn get_balance_returns_correct_amount() -> anyhow::Result<()> { +#[tokio::test] +async fn get_balance_returns_correct_amount() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); @@ -46,7 +46,7 @@ fn get_balance_returns_correct_amount() -> anyhow::Result<()> { suffix = faucet_id.suffix(), ); - let exec_output = tx_context.execute_code_blocking(&code)?; + let exec_output = tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_stack_element(0).as_int(), @@ -57,8 +57,8 @@ fn get_balance_returns_correct_amount() -> anyhow::Result<()> { } /// Tests that asset_vault::peek_balance returns the correct amount. -#[test] -fn peek_balance_returns_correct_amount() -> anyhow::Result<()> { +#[tokio::test] +async fn peek_balance_returns_correct_amount() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); @@ -92,7 +92,7 @@ fn peek_balance_returns_correct_amount() -> anyhow::Result<()> { suffix = faucet_id.suffix(), ); - let exec_output = tx_context.execute_code_blocking(&code)?; + let exec_output = tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_stack_element(0).as_int(), @@ -102,8 +102,8 @@ fn peek_balance_returns_correct_amount() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_get_balance_non_fungible_fails() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_balance_non_fungible_fails() -> anyhow::Result<()> { // Disable lazy loading otherwise the handler will return an error before the transaction kernel // can abort, which is what we want to test. let tx_context = TransactionContextBuilder::with_existing_mock_account() @@ -126,7 +126,7 @@ fn test_get_balance_non_fungible_fails() -> anyhow::Result<()> { suffix = faucet_id.suffix(), ); - let exec_result = tx_context.execute_code_blocking(&code); + let exec_result = tx_context.execute_code(&code).await; assert_execution_error!( exec_result, @@ -136,8 +136,8 @@ fn test_get_balance_non_fungible_fails() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_has_non_fungible_asset() -> anyhow::Result<()> { +#[tokio::test] +async fn test_has_non_fungible_asset() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let non_fungible_asset = tx_context.account().vault().assets().find(Asset::is_non_fungible).unwrap(); @@ -159,15 +159,15 @@ fn test_has_non_fungible_asset() -> anyhow::Result<()> { non_fungible_asset_key = Word::from(non_fungible_asset) ); - let exec_output = tx_context.execute_code_blocking(&code)?; + let exec_output = tx_context.execute_code(&code).await?; assert_eq!(exec_output.get_stack_element(0), ONE); Ok(()) } -#[test] -fn test_add_fungible_asset_success() -> anyhow::Result<()> { +#[tokio::test] +async fn test_add_fungible_asset_success() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let mut account_vault = tx_context.account().vault().clone(); let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); @@ -197,7 +197,7 @@ fn test_add_fungible_asset_success() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(add_fungible_asset) ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_stack_word(0), @@ -212,8 +212,8 @@ fn test_add_fungible_asset_success() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_add_non_fungible_asset_fail_overflow() -> anyhow::Result<()> { +#[tokio::test] +async fn test_add_non_fungible_asset_fail_overflow() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let mut account_vault = tx_context.account().vault().clone(); @@ -241,7 +241,7 @@ fn test_add_non_fungible_asset_fail_overflow() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(add_fungible_asset) ); - let exec_result = tx_context.execute_code_blocking(&code); + let exec_result = tx_context.execute_code(&code).await; assert_execution_error!(exec_result, ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED); assert!(account_vault.add_asset(add_fungible_asset).is_err()); @@ -249,8 +249,8 @@ fn test_add_non_fungible_asset_fail_overflow() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_add_non_fungible_asset_success() -> anyhow::Result<()> { +#[tokio::test] +async fn test_add_non_fungible_asset_success() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into()?; let mut account_vault = tx_context.account().vault().clone(); @@ -275,7 +275,7 @@ fn test_add_non_fungible_asset_success() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(add_non_fungible_asset) ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_stack_word(0), @@ -290,8 +290,8 @@ fn test_add_non_fungible_asset_success() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_add_non_fungible_asset_fail_duplicate() -> anyhow::Result<()> { +#[tokio::test] +async fn test_add_non_fungible_asset_fail_duplicate() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into().unwrap(); let mut account_vault = tx_context.account().vault().clone(); @@ -314,7 +314,7 @@ fn test_add_non_fungible_asset_fail_duplicate() -> anyhow::Result<()> { NON_FUNGIBLE_ASSET = Word::from(non_fungible_asset) ); - let exec_result = tx_context.execute_code_blocking(&code); + let exec_result = tx_context.execute_code(&code).await; assert_execution_error!(exec_result, ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS); assert!(account_vault.add_asset(non_fungible_asset).is_err()); @@ -322,8 +322,8 @@ fn test_add_non_fungible_asset_fail_duplicate() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_remove_fungible_asset_success_no_balance_remaining() -> anyhow::Result<()> { +#[tokio::test] +async fn test_remove_fungible_asset_success_no_balance_remaining() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let mut account_vault = tx_context.account().vault().clone(); @@ -354,7 +354,7 @@ fn test_remove_fungible_asset_success_no_balance_remaining() -> anyhow::Result<( FUNGIBLE_ASSET = Word::from(remove_fungible_asset) ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_stack_word(0), @@ -369,8 +369,8 @@ fn test_remove_fungible_asset_success_no_balance_remaining() -> anyhow::Result<( Ok(()) } -#[test] -fn test_remove_fungible_asset_fail_remove_too_much() -> anyhow::Result<()> { +#[tokio::test] +async fn test_remove_fungible_asset_fail_remove_too_much() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(); let amount = FUNGIBLE_ASSET_AMOUNT + 1; @@ -396,7 +396,7 @@ fn test_remove_fungible_asset_fail_remove_too_much() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(remove_fungible_asset) ); - let exec_result = tx_context.execute_code_blocking(&code); + let exec_result = tx_context.execute_code(&code).await; assert_execution_error!( exec_result, @@ -406,8 +406,8 @@ fn test_remove_fungible_asset_fail_remove_too_much() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_remove_fungible_asset_success_balance_remaining() -> anyhow::Result<()> { +#[tokio::test] +async fn test_remove_fungible_asset_success_balance_remaining() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let mut account_vault = tx_context.account().vault().clone(); @@ -438,7 +438,7 @@ fn test_remove_fungible_asset_success_balance_remaining() -> anyhow::Result<()> FUNGIBLE_ASSET = Word::from(remove_fungible_asset) ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_stack_word(0), @@ -453,8 +453,8 @@ fn test_remove_fungible_asset_success_balance_remaining() -> anyhow::Result<()> Ok(()) } -#[test] -fn test_remove_inexisting_non_fungible_asset_fails() -> anyhow::Result<()> { +#[tokio::test] +async fn test_remove_inexisting_non_fungible_asset_fails() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1.try_into().unwrap(); let mut account_vault = tx_context.account().vault().clone(); @@ -484,7 +484,7 @@ fn test_remove_inexisting_non_fungible_asset_fails() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(non_existent_non_fungible_asset) ); - let exec_result = tx_context.execute_code_blocking(&code); + let exec_result = tx_context.execute_code(&code).await; assert_execution_error!(exec_result, ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND); assert_matches!( @@ -496,8 +496,8 @@ fn test_remove_inexisting_non_fungible_asset_fails() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_remove_non_fungible_asset_success() -> anyhow::Result<()> { +#[tokio::test] +async fn test_remove_non_fungible_asset_success() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet_id: AccountId = ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into().unwrap(); let mut account_vault = tx_context.account().vault().clone(); @@ -523,7 +523,7 @@ fn test_remove_non_fungible_asset_success() -> anyhow::Result<()> { FUNGIBLE_ASSET = Word::from(non_fungible_asset) ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_stack_word(0), diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 26321c012e..14c8d15a11 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -41,8 +41,8 @@ use crate::{ assert_transaction_executor_error, }; -#[test] -fn test_epilogue() -> anyhow::Result<()> { +#[tokio::test] +async fn test_epilogue() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let tx_context = { let output_note_1 = create_public_p2any_note( @@ -86,7 +86,7 @@ fn test_epilogue() -> anyhow::Result<()> { " ); - let exec_output = tx_context.execute_code_blocking(&code)?; + let exec_output = tx_context.execute_code(&code).await?; // The final account is the initial account with the nonce incremented by one. let mut final_account = account.clone(); @@ -143,8 +143,8 @@ fn test_epilogue() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_compute_output_note_id() -> anyhow::Result<()> { +#[tokio::test] +async fn test_compute_output_note_id() -> anyhow::Result<()> { let tx_context = { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); @@ -183,7 +183,7 @@ fn test_compute_output_note_id() -> anyhow::Result<()> { " ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( note.assets().commitment(), @@ -310,8 +310,8 @@ async fn epilogue_fails_when_num_input_assets_exceed_num_output_assets() -> anyh Ok(()) } -#[test] -fn test_block_expiration_height_monotonically_decreases() -> anyhow::Result<()> { +#[tokio::test] +async fn test_block_expiration_height_monotonically_decreases() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let test_pairs: [(u64, u64); 3] = [(9, 12), (18, 3), (20, 20)]; @@ -343,7 +343,7 @@ fn test_block_expiration_height_monotonically_decreases() -> anyhow::Result<()> .replace("{value_2}", &v2.to_string()) .replace("{min_value}", &v2.min(v1).to_string()); - let exec_output = &tx_context.execute_code_blocking(code)?; + let exec_output = &tx_context.execute_code(code).await?; // Expiry block should be set to transaction's block + the stored expiration delta // (which can only decrease, not increase) @@ -358,8 +358,8 @@ fn test_block_expiration_height_monotonically_decreases() -> anyhow::Result<()> Ok(()) } -#[test] -fn test_invalid_expiration_deltas() -> anyhow::Result<()> { +#[tokio::test] +async fn test_invalid_expiration_deltas() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let test_values = [0u64, u16::MAX as u64 + 1, u32::MAX as u64]; @@ -374,7 +374,7 @@ fn test_invalid_expiration_deltas() -> anyhow::Result<()> { for value in test_values { let code = &code_template.replace("{value_1}", &value.to_string()); - let exec_output = tx_context.execute_code_blocking(code); + let exec_output = tx_context.execute_code(code).await; assert_execution_error!(exec_output, ERR_TX_INVALID_EXPIRATION_DELTA); } @@ -382,8 +382,8 @@ fn test_invalid_expiration_deltas() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_no_expiration_delta_set() -> anyhow::Result<()> { +#[tokio::test] +async fn test_no_expiration_delta_set() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let code_template = " @@ -404,7 +404,7 @@ fn test_no_expiration_delta_set() -> anyhow::Result<()> { end "; - let exec_output = &tx_context.execute_code_blocking(code_template)?; + let exec_output = &tx_context.execute_code(code_template).await?; // Default value should be equal to u32::MAX, set in the prologue assert_eq!( @@ -415,8 +415,8 @@ fn test_no_expiration_delta_set() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_epilogue_increment_nonce_success() -> anyhow::Result<()> { +#[tokio::test] +async fn test_epilogue_increment_nonce_success() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let expected_nonce = ONE + ONE; @@ -447,7 +447,7 @@ fn test_epilogue_increment_nonce_success() -> anyhow::Result<()> { " ); - tx_context.execute_code_blocking(code.as_str())?; + tx_context.execute_code(code.as_str()).await?; Ok(()) } @@ -495,8 +495,8 @@ async fn test_epilogue_execute_empty_transaction() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_epilogue_empty_transaction_with_empty_output_note() -> anyhow::Result<()> { +#[tokio::test] +async fn test_epilogue_empty_transaction_with_empty_output_note() -> anyhow::Result<()> { let tag = NoteTag::from_account_id(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE.try_into()?); let aux = Felt::new(26); @@ -548,7 +548,7 @@ fn test_epilogue_empty_transaction_with_empty_output_note() -> anyhow::Result<() let tx_context = TransactionContextBuilder::with_noop_auth_account().build()?; - let result = tx_context.execute_code_blocking(&tx_script_source).map(|_| ()); + let result = tx_context.execute_code(&tx_script_source).await.map(|_| ()); // assert that even if the output note was created, the transaction is considered empty assert_execution_error!(result, ERR_EPILOGUE_EXECUTED_TRANSACTION_IS_EMPTY); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index 348a8cb343..04b46881ec 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -46,8 +46,8 @@ use crate::{TransactionContextBuilder, assert_execution_error, assert_transactio // FUNGIBLE FAUCET MINT TESTS // ================================================================================================ -#[test] -fn test_mint_fungible_asset_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn test_mint_fungible_asset_succeeds() -> anyhow::Result<()> { let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); let tx_context = TransactionContextBuilder::with_fungible_faucet( faucet_id.into(), @@ -83,7 +83,7 @@ fn test_mint_fungible_asset_succeeds() -> anyhow::Result<()> { suffix = faucet_id.suffix(), ); - let exec_output = &tx_context.execute_code_blocking(&code).unwrap(); + let exec_output = &tx_context.execute_code(&code).await.unwrap(); let expected_final_storage_amount = FUNGIBLE_FAUCET_INITIAL_BALANCE + FUNGIBLE_ASSET_AMOUNT; let faucet_reserved_slot_storage_location = @@ -125,8 +125,8 @@ async fn mint_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> Ok(()) } -#[test] -fn test_mint_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> { +#[tokio::test] +async fn test_mint_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_fungible_faucet( ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, 10u32.into(), @@ -147,7 +147,7 @@ fn test_mint_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> { asset = Word::from(FungibleAsset::mock(5)) ); - let exec_output = tx_context.execute_code_blocking(&code); + let exec_output = tx_context.execute_code(&code).await; assert_execution_error!(exec_output, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); Ok(()) @@ -187,8 +187,8 @@ async fn test_mint_fungible_asset_fails_saturate_max_amount() -> anyhow::Result< // NON-FUNGIBLE FAUCET MINT TESTS // ================================================================================================ -#[test] -fn test_mint_non_fungible_asset_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn test_mint_non_fungible_asset_succeeds() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_non_fungible_faucet(NonFungibleAsset::mock_issuer().into()) .build()?; @@ -236,13 +236,13 @@ fn test_mint_non_fungible_asset_succeeds() -> anyhow::Result<()> { asset_vault_key = StorageMap::hash_key(asset_vault_key), ); - tx_context.execute_code_blocking(&code)?; + tx_context.execute_code(&code).await?; Ok(()) } -#[test] -fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow::Result<()> { +#[tokio::test] +async fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_non_fungible_faucet( ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1, ) @@ -263,7 +263,7 @@ fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow::Result non_fungible_asset = Word::from(non_fungible_asset) ); - let exec_output = tx_context.execute_code_blocking(&code); + let exec_output = tx_context.execute_code(&code).await; assert_execution_error!(exec_output, ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); Ok(()) @@ -297,8 +297,8 @@ async fn mint_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result Ok(()) } -#[test] -fn test_mint_non_fungible_asset_fails_asset_already_exists() -> anyhow::Result<()> { +#[tokio::test] +async fn test_mint_non_fungible_asset_fails_asset_already_exists() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_non_fungible_faucet(NonFungibleAsset::mock_issuer().into()) .build()?; @@ -319,7 +319,7 @@ fn test_mint_non_fungible_asset_fails_asset_already_exists() -> anyhow::Result<( non_fungible_asset = Word::from(non_fungible_asset) ); - let exec_output = tx_context.execute_code_blocking(&code); + let exec_output = tx_context.execute_code(&code).await; assert_execution_error!(exec_output, ERR_FAUCET_NON_FUNGIBLE_ASSET_ALREADY_ISSUED); @@ -329,8 +329,8 @@ fn test_mint_non_fungible_asset_fails_asset_already_exists() -> anyhow::Result<( // FUNGIBLE FAUCET BURN TESTS // ================================================================================================ -#[test] -fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { let tx_context = { let account = Account::mock_fungible_faucet( ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, @@ -376,7 +376,7 @@ fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> { final_input_vault_asset_amount = CONSUMED_ASSET_1_AMOUNT - FUNGIBLE_ASSET_AMOUNT, ); - let exec_output = &tx_context.execute_code_blocking(&code).unwrap(); + let exec_output = &tx_context.execute_code(&code).await.unwrap(); let expected_final_storage_amount = FUNGIBLE_FAUCET_INITIAL_BALANCE - FUNGIBLE_ASSET_AMOUNT; let faucet_reserved_slot_storage_location = @@ -418,8 +418,8 @@ async fn burn_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result<()> Ok(()) } -#[test] -fn test_burn_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> { +#[tokio::test] +async fn test_burn_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_fungible_faucet( ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, Felt::try_from(FUNGIBLE_FAUCET_INITIAL_BALANCE).unwrap(), @@ -443,14 +443,14 @@ fn test_burn_fungible_asset_inconsistent_faucet_id() -> anyhow::Result<()> { suffix = faucet_id.suffix(), ); - let exec_output = tx_context.execute_code_blocking(&code); + let exec_output = tx_context.execute_code(&code).await; assert_execution_error!(exec_output, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); Ok(()) } -#[test] -fn test_burn_fungible_asset_insufficient_input_amount() -> anyhow::Result<()> { +#[tokio::test] +async fn test_burn_fungible_asset_insufficient_input_amount() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_fungible_faucet( ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, Felt::new(FUNGIBLE_FAUCET_INITIAL_BALANCE), @@ -475,7 +475,7 @@ fn test_burn_fungible_asset_insufficient_input_amount() -> anyhow::Result<()> { saturating_amount = CONSUMED_ASSET_1_AMOUNT + 1 ); - let exec_output = tx_context.execute_code_blocking(&code); + let exec_output = tx_context.execute_code(&code).await; assert_execution_error!( exec_output, @@ -487,8 +487,8 @@ fn test_burn_fungible_asset_insufficient_input_amount() -> anyhow::Result<()> { // NON-FUNGIBLE FAUCET BURN TESTS // ================================================================================================ -#[test] -fn test_burn_non_fungible_asset_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn test_burn_non_fungible_asset_succeeds() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_non_fungible_faucet(NonFungibleAsset::mock_issuer().into()) .build()?; @@ -552,12 +552,12 @@ fn test_burn_non_fungible_asset_succeeds() -> anyhow::Result<()> { burnt_asset_vault_key = burnt_asset_vault_key, ); - tx_context.execute_code_blocking(&code).unwrap(); + tx_context.execute_code(&code).await.unwrap(); Ok(()) } -#[test] -fn test_burn_non_fungible_asset_fails_does_not_exist() -> anyhow::Result<()> { +#[tokio::test] +async fn test_burn_non_fungible_asset_fails_does_not_exist() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_non_fungible_faucet(NonFungibleAsset::mock_issuer().into()) .build()?; @@ -579,7 +579,7 @@ fn test_burn_non_fungible_asset_fails_does_not_exist() -> anyhow::Result<()> { non_fungible_asset = Word::from(non_fungible_asset_burnt) ); - let exec_output = tx_context.execute_code_blocking(&code); + let exec_output = tx_context.execute_code(&code).await; assert_execution_error!(exec_output, ERR_FAUCET_NON_FUNGIBLE_ASSET_TO_BURN_NOT_FOUND); Ok(()) @@ -613,8 +613,8 @@ async fn burn_non_fungible_asset_fails_on_non_faucet_account() -> anyhow::Result Ok(()) } -#[test] -fn test_burn_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow::Result<()> { +#[tokio::test] +async fn test_burn_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow::Result<()> { let non_fungible_asset_burnt = NonFungibleAsset::mock(&[1, 2, 3]); // Run code from a different non-fungible asset issuer @@ -638,7 +638,7 @@ fn test_burn_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow::Result non_fungible_asset = Word::from(non_fungible_asset_burnt) ); - let exec_output = tx_context.execute_code_blocking(&code); + let exec_output = tx_context.execute_code(&code).await; assert_execution_error!(exec_output, ERR_FAUCET_NON_FUNGIBLE_ASSET_TO_BURN_NOT_FOUND); Ok(()) @@ -647,8 +647,8 @@ fn test_burn_non_fungible_asset_fails_inconsistent_faucet_id() -> anyhow::Result // IS NON FUNGIBLE ASSET ISSUED TESTS // ================================================================================================ -#[test] -fn test_is_non_fungible_asset_issued_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn test_is_non_fungible_asset_issued_succeeds() -> anyhow::Result<()> { // NON_FUNGIBLE_ASSET_DATA_2 is "issued" during the mock faucet creation, so it is already in // the map of issued assets. let tx_context = @@ -685,15 +685,15 @@ fn test_is_non_fungible_asset_issued_succeeds() -> anyhow::Result<()> { non_fungible_asset_2 = Word::from(non_fungible_asset_2), ); - tx_context.execute_code_blocking(&code).unwrap(); + tx_context.execute_code(&code).await.unwrap(); Ok(()) } // GET TOTAL ISSUANCE TESTS // ================================================================================================ -#[test] -fn test_get_total_issuance_succeeds() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_total_issuance_succeeds() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_fungible_faucet( ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, Felt::new(FUNGIBLE_FAUCET_INITIAL_BALANCE), @@ -719,7 +719,7 @@ fn test_get_total_issuance_succeeds() -> anyhow::Result<()> { "#, ); - tx_context.execute_code_blocking(&code).unwrap(); + tx_context.execute_code(&code).await.unwrap(); Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index 7088c0195f..0aa019ca27 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -56,8 +56,8 @@ use crate::{Auth, MockChainBuilder, assert_execution_error, assert_transaction_e // FOREIGN PROCEDURE INVOCATION TESTS // ================================================================================================ -#[test] -fn test_fpi_memory_single_account() -> anyhow::Result<()> { +#[tokio::test] +async fn test_fpi_memory_single_account() -> anyhow::Result<()> { // Prepare the test data let storage_slots = vec![AccountStorage::mock_item_0().slot, AccountStorage::mock_item_2().slot]; @@ -158,7 +158,7 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { get_item_foreign_hash = foreign_account.code().procedures()[1].mast_root(), ); - let exec_output = tx_context.execute_code_blocking(&code)?; + let exec_output = tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_stack_word(0), @@ -212,7 +212,7 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { get_map_item_foreign_hash = foreign_account.code().procedures()[2].mast_root(), ); - let exec_output = tx_context.execute_code_blocking(&code).unwrap(); + let exec_output = tx_context.execute_code(&code).await.unwrap(); assert_eq!( exec_output.get_stack_word(0), @@ -282,7 +282,7 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { get_item_foreign_hash = foreign_account.code().procedures()[1].mast_root(), ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; // Check that the second invocation of the foreign procedure from the same account does not load // the account data again: already loaded data should be reused. @@ -298,8 +298,8 @@ fn test_fpi_memory_single_account() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { +#[tokio::test] +async fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { // Prepare the test data let storage_slots_1 = vec![AccountStorage::mock_item_0().slot]; let storage_slots_2 = vec![AccountStorage::mock_item_1().slot]; @@ -464,7 +464,7 @@ fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { foreign_2_suffix = foreign_account_2.id().suffix(), ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; // Check the correctness of the memory layout after multiple foreign procedure invocations from // different foreign accounts diff --git a/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs b/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs index 449ac9d296..69979d73d3 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_link_map.rs @@ -16,8 +16,8 @@ use crate::TransactionContextBuilder; /// - Insertion after an existing entry. /// - Insertion in between two existing entries. /// - Insertion before an existing head. -#[test] -fn insertion() -> anyhow::Result<()> { +#[tokio::test] +async fn insertion() -> anyhow::Result<()> { let map_ptr = 8u32; // check that using an empty word as key is fine let entry0_key = Word::from([0, 0, 0, 0u32]); @@ -173,7 +173,7 @@ fn insertion() -> anyhow::Result<()> { ); let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - let exec_output = tx_context.execute_code_blocking(&code).context("failed to execute code")?; + let exec_output = tx_context.execute_code(&code).await.context("failed to execute code")?; let mem_viewer = MemoryViewer::ExecutionOutputs(&exec_output); let map = LinkMap::new(map_ptr.into(), &mem_viewer); @@ -216,8 +216,8 @@ fn insertion() -> anyhow::Result<()> { Ok(()) } -#[test] -fn insert_and_update() -> anyhow::Result<()> { +#[tokio::test] +async fn insert_and_update() -> anyhow::Result<()> { const MAP_PTR: u32 = 8; let value0 = Word::from([1, 2, 3, 4u32]); @@ -234,11 +234,11 @@ fn insert_and_update() -> anyhow::Result<()> { TestOperation::set(MAP_PTR, link_map_key([3, 0, 0, 0]), (value1, value2)), ]; - execute_link_map_test(operations) + execute_link_map_test(operations).await } -#[test] -fn insert_at_head() -> anyhow::Result<()> { +#[tokio::test] +async fn insert_at_head() -> anyhow::Result<()> { const MAP_PTR: u32 = 8; let key3 = link_map_key([3, 0, 0, 0]); @@ -258,12 +258,12 @@ fn insert_at_head() -> anyhow::Result<()> { TestOperation::get(MAP_PTR, key3), ]; - execute_link_map_test(operations) + execute_link_map_test(operations).await } /// Tests that a get before a set results in the expected returned values and behavior. -#[test] -fn get_before_set() -> anyhow::Result<()> { +#[tokio::test] +async fn get_before_set() -> anyhow::Result<()> { const MAP_PTR: u32 = 8; let key0 = link_map_key([3, 0, 0, 0]); @@ -276,11 +276,11 @@ fn get_before_set() -> anyhow::Result<()> { TestOperation::get(MAP_PTR, key0), ]; - execute_link_map_test(operations) + execute_link_map_test(operations).await } -#[test] -fn multiple_link_maps() -> anyhow::Result<()> { +#[tokio::test] +async fn multiple_link_maps() -> anyhow::Result<()> { const MAP_PTR0: u32 = 8; const MAP_PTR1: u32 = 12; @@ -305,11 +305,11 @@ fn multiple_link_maps() -> anyhow::Result<()> { TestOperation::get(MAP_PTR1, key3), ]; - execute_link_map_test(operations) + execute_link_map_test(operations).await } -#[test] -fn iteration() -> anyhow::Result<()> { +#[tokio::test] +async fn iteration() -> anyhow::Result<()> { const MAP_PTR: u32 = 12; let entries = generate_entries(100); @@ -324,11 +324,11 @@ fn iteration() -> anyhow::Result<()> { // Iterate the map. test_operations.push(TestOperation::iter(MAP_PTR)); - execute_link_map_test(test_operations) + execute_link_map_test(test_operations).await } -#[test] -fn set_update_get_random_entries() -> anyhow::Result<()> { +#[tokio::test] +async fn set_update_get_random_entries() -> anyhow::Result<()> { const MAP_PTR: u32 = 12; let entries = generate_entries(1000); @@ -356,7 +356,7 @@ fn set_update_get_random_entries() -> anyhow::Result<()> { test_operations.extend(get_ops2); test_operations.extend(get_ops3); - execute_link_map_test(test_operations) + execute_link_map_test(test_operations).await } // TEST HELPERS @@ -399,7 +399,7 @@ impl TestOperation { } } -fn execute_link_map_test(operations: Vec) -> anyhow::Result<()> { +async fn execute_link_map_test(operations: Vec) -> anyhow::Result<()> { let mut test_code = String::new(); let mut control_maps: BTreeMap> = BTreeMap::new(); @@ -542,7 +542,7 @@ fn execute_link_map_test(operations: Vec) -> anyhow::Result<()> { ); let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; - let exec_output = tx_context.execute_code_blocking(&code).context("failed to execute code")?; + let exec_output = tx_context.execute_code(&code).await.context("failed to execute code")?; let mem_viewer = MemoryViewer::ExecutionOutputs(&exec_output); for (map_ptr, control_map) in control_maps { diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index dc04799f6f..e41510d6b7 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -45,8 +45,8 @@ use crate::{ assert_transaction_executor_error, }; -#[test] -fn test_note_setup() -> anyhow::Result<()> { +#[tokio::test] +async fn test_note_setup() -> anyhow::Result<()> { let tx_context = { let mut builder = MockChain::builder(); let account = builder.add_existing_wallet(Auth::BasicAuth)?; @@ -80,15 +80,15 @@ fn test_note_setup() -> anyhow::Result<()> { end "; - let exec_output = tx_context.execute_code_blocking(code)?; + let exec_output = tx_context.execute_code(code).await?; note_setup_stack_assertions(&exec_output, &tx_context); note_setup_memory_assertions(&exec_output); Ok(()) } -#[test] -fn test_note_script_and_note_args() -> miette::Result<()> { +#[tokio::test] +async fn test_note_script_and_note_args() -> miette::Result<()> { let mut tx_context = { let mut builder = MockChain::builder(); let account = builder.add_existing_wallet(Auth::BasicAuth).map_err(|err| miette!(err))?; @@ -158,7 +158,7 @@ fn test_note_script_and_note_args() -> miette::Result<()> { .with_note_args(note_args_map); tx_context.set_tx_args(tx_args); - let exec_output = tx_context.execute_code_blocking(code).unwrap(); + let exec_output = tx_context.execute_code(code).await.unwrap(); assert_eq!(exec_output.get_stack_word(0), note_args[0]); assert_eq!(exec_output.get_stack_word(4), note_args[1]); @@ -186,8 +186,8 @@ fn note_setup_memory_assertions(exec_output: &ExecutionOutput) { ); } -#[test] -fn test_build_recipient() -> anyhow::Result<()> { +#[tokio::test] +async fn test_build_recipient() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; // Create test script and serial number @@ -251,7 +251,7 @@ fn test_build_recipient() -> anyhow::Result<()> { serial_num = serial_num, ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; // Create expected recipients and get their digests let note_inputs_4 = NoteInputs::new(word_1.to_vec())?; @@ -279,8 +279,8 @@ fn test_build_recipient() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_compute_inputs_commitment() -> anyhow::Result<()> { +#[tokio::test] +async fn test_compute_inputs_commitment() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; // Define test values as Words @@ -339,7 +339,7 @@ fn test_compute_inputs_commitment() -> anyhow::Result<()> { addr_3 = BASE_ADDR + 12, ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; let mut inputs_5 = word_1.to_vec(); inputs_5.push(word_2[0]); @@ -367,8 +367,8 @@ fn test_compute_inputs_commitment() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_build_metadata() -> miette::Result<()> { +#[tokio::test] +async fn test_build_metadata() -> miette::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); let sender = tx_context.account().id(); @@ -416,7 +416,7 @@ fn test_build_metadata() -> miette::Result<()> { tag = test_metadata.tag(), ); - let exec_output = tx_context.execute_code_blocking(&code).unwrap(); + let exec_output = tx_context.execute_code(&code).await.unwrap(); let metadata_word = exec_output.get_stack_word(0); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index b4f4eb5417..5272263bbf 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -51,8 +51,8 @@ use crate::kernel_tests::tx::ExecutionOutputExt; use crate::utils::create_public_p2any_note; use crate::{Auth, MockChain, TransactionContextBuilder, assert_execution_error}; -#[test] -fn test_create_note() -> anyhow::Result<()> { +#[tokio::test] +async fn test_create_note() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let account_id = tx_context.account().id(); @@ -87,7 +87,7 @@ fn test_create_note() -> anyhow::Result<()> { tag = tag, ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), @@ -124,18 +124,18 @@ fn test_create_note() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_create_note_with_invalid_tag() -> anyhow::Result<()> { +#[tokio::test] +async fn test_create_note_with_invalid_tag() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let invalid_tag = Felt::new((NoteType::Public as u64) << 62); let valid_tag: Felt = NoteTag::for_local_use_case(0, 0).unwrap().into(); // Test invalid tag - assert!(tx_context.execute_code_blocking(¬e_creation_script(invalid_tag)).is_err()); + assert!(tx_context.execute_code(¬e_creation_script(invalid_tag)).await.is_err()); // Test valid tag - assert!(tx_context.execute_code_blocking(¬e_creation_script(valid_tag)).is_ok()); + assert!(tx_context.execute_code(¬e_creation_script(valid_tag)).await.is_ok()); Ok(()) } @@ -168,8 +168,8 @@ fn note_creation_script(tag: Felt) -> String { ) } -#[test] -fn test_create_note_too_many_notes() -> anyhow::Result<()> { +#[tokio::test] +async fn test_create_note_too_many_notes() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let code = format!( @@ -200,14 +200,14 @@ fn test_create_note_too_many_notes() -> anyhow::Result<()> { aux = ZERO, ); - let exec_output = tx_context.execute_code_blocking(&code); + let exec_output = tx_context.execute_code(&code).await; assert_execution_error!(exec_output, ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT); Ok(()) } -#[test] -fn test_get_output_notes_commitment() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_output_notes_commitment() -> anyhow::Result<()> { let tx_context = { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); @@ -362,7 +362,7 @@ fn test_get_output_notes_commitment() -> anyhow::Result<()> { ), ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), @@ -391,8 +391,8 @@ fn test_get_output_notes_commitment() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_create_note_and_add_asset() -> anyhow::Result<()> { +#[tokio::test] +async fn test_create_note_and_add_asset() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; @@ -438,7 +438,7 @@ fn test_create_note_and_add_asset() -> anyhow::Result<()> { asset = asset, ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), @@ -454,8 +454,8 @@ fn test_create_note_and_add_asset() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { +#[tokio::test] +async fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; @@ -520,7 +520,7 @@ fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { nft = non_fungible_asset_encoded, ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), @@ -548,8 +548,8 @@ fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { +#[tokio::test] +async fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let recipient = Word::from([0, 1, 2, 3u32]); @@ -596,7 +596,7 @@ fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { nft = encoded, ); - let exec_output = tx_context.execute_code_blocking(&code); + let exec_output = tx_context.execute_code(&code).await; assert_execution_error!(exec_output, ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS); Ok(()) @@ -625,8 +625,8 @@ async fn creating_note_with_fungible_asset_amount_zero_works() -> anyhow::Result Ok(()) } -#[test] -fn test_build_recipient_hash() -> anyhow::Result<()> { +#[tokio::test] +async fn test_build_recipient_hash() -> anyhow::Result<()> { let tx_context = { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); @@ -694,7 +694,7 @@ fn test_build_recipient_hash() -> anyhow::Result<()> { aux = aux, ); - let exec_output = &tx_context.execute_code_blocking(&code)?; + let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( exec_output.get_kernel_mem_word(NUM_OUTPUT_NOTES_PTR), diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index 5f62fb0726..949aca5055 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -100,8 +100,8 @@ use crate::{ assert_transaction_executor_error, }; -#[test] -fn test_transaction_prologue() -> anyhow::Result<()> { +#[tokio::test] +async fn test_transaction_prologue() -> anyhow::Result<()> { let mut tx_context = { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); @@ -155,7 +155,7 @@ fn test_transaction_prologue() -> anyhow::Result<()> { .with_note_args(note_args_map); tx_context.set_tx_args(tx_args); - let exec_output = &tx_context.execute_code_blocking(code)?; + let exec_output = &tx_context.execute_code(code).await?; global_input_memory_assertions(exec_output, &tx_context); block_data_memory_assertions(exec_output, &tx_context); @@ -752,8 +752,8 @@ pub async fn create_account_invalid_seed() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_get_blk_version() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_blk_version() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let code = " use.$kernel::memory @@ -768,7 +768,7 @@ fn test_get_blk_version() -> anyhow::Result<()> { end "; - let exec_output = tx_context.execute_code_blocking(code)?; + let exec_output = tx_context.execute_code(code).await?; assert_eq!( exec_output.get_stack_element(0), @@ -778,8 +778,8 @@ fn test_get_blk_version() -> anyhow::Result<()> { Ok(()) } -#[test] -fn test_get_blk_timestamp() -> anyhow::Result<()> { +#[tokio::test] +async fn test_get_blk_timestamp() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let code = " use.$kernel::memory @@ -794,7 +794,7 @@ fn test_get_blk_timestamp() -> anyhow::Result<()> { end "; - let exec_output = tx_context.execute_code_blocking(code)?; + let exec_output = tx_context.execute_code(code).await?; assert_eq!( exec_output.get_stack_element(0), diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index d9efbbbf27..288ab33266 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -58,14 +58,6 @@ pub struct TransactionContext { } impl TransactionContext { - /// TODO: Remove. - pub fn execute_code_blocking(&self, code: &str) -> Result { - tokio::runtime::Builder::new_current_thread() - .build() - .unwrap() - .block_on(self.execute_code(code)) - } - /// Executes arbitrary code within the context of a mocked transaction environment and returns /// the resulting [`ExecutionOutput`]. /// From 0e4abf7a3629b9d0494685e58ea287128881d614 Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 13 Oct 2025 11:52:31 +0200 Subject: [PATCH 088/133] chore: use macro for extracting the procedure digest (#1989) * chore: use macro for extracting the procedure digest * chore: fmt * chore: avoid crate:: usage * chore: add miden-objects exports note to macro description --- crates/miden-lib/src/account/faucets/mod.rs | 33 ++++++----------- crates/miden-lib/src/account/mod.rs | 39 +++++++++++++++++++++ crates/miden-lib/src/account/wallets/mod.rs | 33 ++++++----------- 3 files changed, 61 insertions(+), 44 deletions(-) diff --git a/crates/miden-lib/src/account/faucets/mod.rs b/crates/miden-lib/src/account/faucets/mod.rs index 0e2a91030d..041728add9 100644 --- a/crates/miden-lib/src/account/faucets/mod.rs +++ b/crates/miden-lib/src/account/faucets/mod.rs @@ -9,9 +9,7 @@ use miden_objects::account::{ AccountType, StorageSlot, }; -use miden_objects::assembly::{ProcedureName, QualifiedProcedureName}; use miden_objects::asset::{FungibleAsset, TokenSymbol}; -use miden_objects::utils::sync::LazyLock; use miden_objects::{AccountError, Felt, FieldElement, TokenSymbolError, Word}; use thiserror::Error; @@ -23,34 +21,25 @@ use crate::account::auth::{ AuthRpoFalcon512Multisig, }; use crate::account::components::basic_fungible_faucet_library; +use crate::procedure_digest; use crate::transaction::memory::FAUCET_STORAGE_DATA_SLOT; // BASIC FUNGIBLE FAUCET ACCOUNT COMPONENT // ================================================================================================ // Initialize the digest of the `distribute` procedure of the Basic Fungible Faucet only once. -static BASIC_FUNGIBLE_FAUCET_DISTRIBUTE: LazyLock = LazyLock::new(|| { - let distribute_proc_name = QualifiedProcedureName::new( - Default::default(), - ProcedureName::new(BasicFungibleFaucet::DISTRIBUTE_PROC_NAME) - .expect("failed to create name for 'distribute' procedure"), - ); - basic_fungible_faucet_library() - .get_procedure_root_by_name(distribute_proc_name) - .expect("Basic Fungible Faucet should contain 'distribute' procedure") -}); +procedure_digest!( + BASIC_FUNGIBLE_FAUCET_DISTRIBUTE, + BasicFungibleFaucet::DISTRIBUTE_PROC_NAME, + basic_fungible_faucet_library +); // Initialize the digest of the `burn` procedure of the Basic Fungible Faucet only once. -static BASIC_FUNGIBLE_FAUCET_BURN: LazyLock = LazyLock::new(|| { - let burn_proc_name = QualifiedProcedureName::new( - Default::default(), - ProcedureName::new(BasicFungibleFaucet::BURN_PROC_NAME) - .expect("failed to create name for 'burn' procedure"), - ); - basic_fungible_faucet_library() - .get_procedure_root_by_name(burn_proc_name) - .expect("Basic Fungible Faucet should contain 'burn' procedure") -}); +procedure_digest!( + BASIC_FUNGIBLE_FAUCET_BURN, + BasicFungibleFaucet::BURN_PROC_NAME, + basic_fungible_faucet_library +); /// An [`AccountComponent`] implementing a basic fungible faucet. /// diff --git a/crates/miden-lib/src/account/mod.rs b/crates/miden-lib/src/account/mod.rs index ec362c6587..78afc9f8dd 100644 --- a/crates/miden-lib/src/account/mod.rs +++ b/crates/miden-lib/src/account/mod.rs @@ -5,3 +5,42 @@ pub mod components; pub mod faucets; pub mod interface; pub mod wallets; + +/// Macro to simplify the creation of static procedure digest constants. +/// +/// This macro generates a `LazyLock` static variable that lazily initializes +/// the digest of a procedure from a library. +/// +/// Note: This macro references exported types from `miden_objects`, so your crate must +/// include `miden-objects` as a dependency. +/// +/// # Arguments +/// * `$name` - The name of the static variable to create +/// * `$proc_name` - The string name of the procedure +/// * `$library_fn` - The function that returns the library containing the procedure +/// +/// # Example +/// ```ignore +/// procedure_digest!( +/// BASIC_WALLET_RECEIVE_ASSET, +/// BasicWallet::RECEIVE_ASSET_PROC_NAME, +/// basic_wallet_library +/// ); +/// ``` +#[macro_export] +macro_rules! procedure_digest { + ($name:ident, $proc_name:expr, $library_fn:expr) => { + static $name: miden_objects::utils::sync::LazyLock = + miden_objects::utils::sync::LazyLock::new(|| { + let qualified_name = miden_objects::assembly::QualifiedProcedureName::new( + ::core::default::Default::default(), + miden_objects::assembly::ProcedureName::new($proc_name).unwrap_or_else(|_| { + panic!("failed to create name for '{}' procedure", $proc_name) + }), + ); + $library_fn().get_procedure_root_by_name(qualified_name).unwrap_or_else(|| { + panic!("{} should contain '{}' procedure", stringify!($library_fn), $proc_name) + }) + }); + }; +} diff --git a/crates/miden-lib/src/account/wallets/mod.rs b/crates/miden-lib/src/account/wallets/mod.rs index 8fb0337ec5..7bf3b6d1ec 100644 --- a/crates/miden-lib/src/account/wallets/mod.rs +++ b/crates/miden-lib/src/account/wallets/mod.rs @@ -7,41 +7,30 @@ use miden_objects::account::{ AccountStorageMode, AccountType, }; -use miden_objects::assembly::{ProcedureName, QualifiedProcedureName}; -use miden_objects::utils::sync::LazyLock; use miden_objects::{AccountError, Word}; use thiserror::Error; use super::AuthScheme; use crate::account::auth::{AuthRpoFalcon512, AuthRpoFalcon512Multisig}; use crate::account::components::basic_wallet_library; +use crate::procedure_digest; // BASIC WALLET // ================================================================================================ // Initialize the digest of the `receive_asset` procedure of the Basic Wallet only once. -static BASIC_WALLET_RECEIVE_ASSET: LazyLock = LazyLock::new(|| { - let receive_asset_proc_name = QualifiedProcedureName::new( - Default::default(), - ProcedureName::new(BasicWallet::RECEIVE_ASSET_PROC_NAME) - .expect("failed to create name for 'receive_asset' procedure"), - ); - basic_wallet_library() - .get_procedure_root_by_name(receive_asset_proc_name) - .expect("Basic Wallet should contain 'receive_asset' procedure") -}); +procedure_digest!( + BASIC_WALLET_RECEIVE_ASSET, + BasicWallet::RECEIVE_ASSET_PROC_NAME, + basic_wallet_library +); // Initialize the digest of the `move_asset_to_note` procedure of the Basic Wallet only once. -static BASIC_WALLET_MOVE_ASSET_TO_NOTE: LazyLock = LazyLock::new(|| { - let move_asset_to_note_proc_name = QualifiedProcedureName::new( - Default::default(), - ProcedureName::new(BasicWallet::MOVE_ASSET_TO_NOTE_PROC_NAME) - .expect("failed to create name for 'move_asset_to_note' procedure"), - ); - basic_wallet_library() - .get_procedure_root_by_name(move_asset_to_note_proc_name) - .expect("Basic Wallet should contain 'move_asset_to_note' procedure") -}); +procedure_digest!( + BASIC_WALLET_MOVE_ASSET_TO_NOTE, + BasicWallet::MOVE_ASSET_TO_NOTE_PROC_NAME, + basic_wallet_library +); /// An [`AccountComponent`] implementing a basic wallet. /// From 7dbf475087f30b653e95f39d1f6cc2bc59ffebea Mon Sep 17 00:00:00 2001 From: Tomas Fabrizio Orsi Date: Thu, 16 Oct 2025 11:31:42 -0300 Subject: [PATCH 089/133] feat: re-export struct required to create Package (#1984) --- CHANGELOG.md | 11 ++++++----- Cargo.lock | 1 + Cargo.toml | 19 ++++++++++--------- crates/miden-objects/Cargo.toml | 17 +++++++++-------- crates/miden-objects/src/lib.rs | 3 ++- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3baa494ae..bff6e44272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,13 +21,14 @@ - Implement `SlotName` for named storage slots ([#1932](https://github.com/0xMiden/miden-base/issues/1932)). - [BREAKING] Removed `get_falcon_signature` from `miden-tx` crate ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Created a `Signature` wrapper to simplify the preparation of "native" signatures for use in the VM ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). -- Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933]https://github.com/0xMiden/miden-base/pull/1933). +- Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933](https://github.com/0xMiden/miden-base/pull/1933)). - Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937)). -- Added `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935]https://github.com/0xMiden/miden-base/pull/1935). -- Added `AssetVault::{num_assets, num_leaves, inner_nodes}` ([#1939]https://github.com/0xMiden/miden-base/pull/1939). +- Added `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935](https://github.com/0xMiden/miden-base/pull/1935)). +- Added `AssetVault::{num_assets, num_leaves, inner_nodes}` ([#1939](https://github.com/0xMiden/miden-base/pull/1939)). +- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973](https://github.com/0xMiden/miden-base/pull/1973)). - Added `account::get_initial_balance` procedure to `miden` lib ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). -- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). -- [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). +- [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963)). +- Added `MastArtifact`, `PackageExport`, `PackageManifest`, `AttributeSet` and `QualifiedProcedureName` to re-export section ([#1984](https://github.com/0xMiden/miden-base/pull/1984)). ### Changes diff --git a/Cargo.lock b/Cargo.lock index 678b3732bb..6f6755936e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1555,6 +1555,7 @@ dependencies = [ "log", "miden-air", "miden-assembly", + "miden-assembly-syntax", "miden-core", "miden-crypto", "miden-mast-package", diff --git a/Cargo.toml b/Cargo.toml index 0731ad906d..dedae10568 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,15 +48,16 @@ miden-tx = { default-features = false, path = "crates/miden-tx", ve miden-tx-batch-prover = { default-features = false, path = "crates/miden-tx-batch-prover", version = "0.12" } # Miden dependencies -miden-assembly = { default-features = false, version = "0.18" } -miden-core = { default-features = false, version = "0.18" } -miden-crypto = { default-features = false, version = "0.17" } -miden-mast-package = { default-features = false, version = "0.18" } -miden-processor = { default-features = false, version = "0.18" } -miden-prover = { default-features = false, version = "0.18" } -miden-stdlib = { default-features = false, version = "0.18" } -miden-utils-sync = { default-features = false, version = "0.18" } -miden-verifier = { default-features = false, version = "0.18" } +miden-assembly = { default-features = false, version = "0.18" } +miden-assembly-syntax = { default-features = false, version = "0.18" } +miden-core = { default-features = false, version = "0.18" } +miden-crypto = { default-features = false, version = "0.17" } +miden-mast-package = { default-features = false, version = "0.18" } +miden-processor = { default-features = false, version = "0.18" } +miden-prover = { default-features = false, version = "0.18" } +miden-stdlib = { default-features = false, version = "0.18" } +miden-utils-sync = { default-features = false, version = "0.18" } +miden-verifier = { default-features = false, version = "0.18" } # External dependencies anyhow = { default-features = false, features = ["backtrace", "std"], version = "1.0" } diff --git a/crates/miden-objects/Cargo.toml b/crates/miden-objects/Cargo.toml index d35e510402..58f1443e89 100644 --- a/crates/miden-objects/Cargo.toml +++ b/crates/miden-objects/Cargo.toml @@ -34,14 +34,15 @@ testing = ["dep:rand", "dep:rand_xoshiro", "dep:winter-rand-utils", "miden-air/t [dependencies] # Miden dependencies -miden-assembly = { workspace = true } -miden-core = { workspace = true } -miden-crypto = { workspace = true } -miden-mast-package = { workspace = true } -miden-processor = { workspace = true } -miden-utils-sync = { workspace = true } -miden-verifier = { workspace = true } -winter-rand-utils = { optional = true, version = "0.13" } +miden-assembly = { workspace = true } +miden-assembly-syntax = { workspace = true } +miden-core = { workspace = true } +miden-crypto = { workspace = true } +miden-mast-package = { workspace = true } +miden-processor = { workspace = true } +miden-utils-sync = { workspace = true } +miden-verifier = { workspace = true } +winter-rand-utils = { optional = true, version = "0.13" } # External dependencies bech32 = { default-features = false, features = ["alloc"], version = "0.11" } diff --git a/crates/miden-objects/src/lib.rs b/crates/miden-objects/src/lib.rs index 1eaa30338c..c7c734bff5 100644 --- a/crates/miden-objects/src/lib.rs +++ b/crates/miden-objects/src/lib.rs @@ -99,9 +99,10 @@ pub mod utils { } pub mod vm { + pub use miden_assembly_syntax::ast::{AttributeSet, QualifiedProcedureName}; pub use miden_core::sys_events::SystemEvent; pub use miden_core::{AdviceMap, Program, ProgramInfo}; - pub use miden_mast_package::Package; + pub use miden_mast_package::{MastArtifact, Package, PackageExport, PackageManifest}; pub use miden_processor::{AdviceInputs, FutureMaybeSend, RowIndex, StackInputs, StackOutputs}; pub use miden_verifier::ExecutionProof; } From 7a3e87b565e3d2008cb73512b19f9b2d41b70a91 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Thu, 16 Oct 2025 19:01:12 +0200 Subject: [PATCH 090/133] feat: new account tree mutation with reversions (#2002) --- CHANGELOG.md | 3 +++ Cargo.lock | 4 ++-- .../miden-objects/src/block/account_tree.rs | 20 +++++++++++++++++++ crates/miden-objects/src/block/mod.rs | 2 +- crates/miden-tx/src/prover/mod.rs | 3 +-- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bff6e44272..4d23b14bad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ - Added `account::get_initial_balance` procedure to `miden` lib ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963)). - Added `MastArtifact`, `PackageExport`, `PackageManifest`, `AttributeSet` and `QualifiedProcedureName` to re-export section ([#1984](https://github.com/0xMiden/miden-base/pull/1984)). +- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). +- [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). +- Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). ### Changes diff --git a/Cargo.lock b/Cargo.lock index 6f6755936e..6d82e86ae0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1417,9 +1417,9 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8c283ed3017d58d9b648d9b1099f26abff52b185ecbb7b91ff4fb406c286b7" +checksum = "87b287c7a76b95be7ef5588e98a9dbe1973395dbb19eb59dd34d82b06e47f02a" dependencies = [ "blake3", "cc", diff --git a/crates/miden-objects/src/block/account_tree.rs b/crates/miden-objects/src/block/account_tree.rs index 1427d13f5f..666fd066c5 100644 --- a/crates/miden-objects/src/block/account_tree.rs +++ b/crates/miden-objects/src/block/account_tree.rs @@ -268,6 +268,26 @@ impl AccountTree { .map_err(AccountTreeError::ApplyMutations) } + /// Applies the prospective mutations computed with [`Self::compute_mutations`] to this tree + /// and returns the reverse mutation set. + /// + /// Applying the reverse mutation sets to the updated tree will revert the changes. + /// + /// # Errors + /// + /// Returns an error if: + /// - `mutations` was computed on a tree with a different root than this one. + pub fn apply_mutations_with_reversion( + &mut self, + mutations: AccountMutationSet, + ) -> Result { + let reversion = self + .smt + .apply_mutations_with_reversion(mutations.into_mutation_set()) + .map_err(AccountTreeError::ApplyMutations)?; + Ok(AccountMutationSet::new(reversion)) + } + // HELPERS // -------------------------------------------------------------------------------------------- diff --git a/crates/miden-objects/src/block/mod.rs b/crates/miden-objects/src/block/mod.rs index f31329202d..7c3afa7c30 100644 --- a/crates/miden-objects/src/block/mod.rs +++ b/crates/miden-objects/src/block/mod.rs @@ -17,7 +17,7 @@ mod partial_account_tree; pub use partial_account_tree::PartialAccountTree; pub(super) mod account_tree; -pub use account_tree::AccountTree; +pub use account_tree::{AccountMutationSet, AccountTree}; mod nullifier_tree; pub use nullifier_tree::NullifierTree; diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index 3e0eadb6fa..8be7be0c06 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -18,7 +18,6 @@ use miden_objects::account::{ use miden_objects::asset::{Asset, AssetVault}; use miden_objects::block::BlockNumber; use miden_objects::transaction::{ - ExecutedTransaction, InputNote, InputNotes, OutputNote, @@ -241,7 +240,7 @@ fn partial_storage_map_to_storage_map( impl LocalTransactionProver { pub fn prove_dummy( &self, - executed_transaction: ExecutedTransaction, + executed_transaction: miden_objects::transaction::ExecutedTransaction, ) -> Result { let (tx_inputs, tx_outputs, account_delta, _) = executed_transaction.into_parts(); From 52625257b09beb7627d7e26cfe7c268f5b8e7a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20v?= <52646071+Peponks9@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:45:09 -0600 Subject: [PATCH 091/133] refactor: clarify distinction between `add_` and `create_` functions in `MockChainBuilder` (#1997) * add rng_mut method to MockChainBuilder for RNG access * add free-standing create_ functions * update test usages to use free-standing create_ functions * refactor: simplify create_ functions based on feedback * refactor: update imports to direct and simplify doc example in MockChainBuilder * docs: add CHANGELOG entry for MockChainBuilder API changes * Update crates/miden-testing/src/mock_chain/chain_builder.rs * fix: add missing import for create_p2any_note in chain_builder.rs --------- Co-authored-by: Philipp Gackstatter --- CHANGELOG.md | 1 + .../src/kernel_tests/batch/proposed_batch.rs | 5 +- .../block/proposed_block_errors.rs | 18 ++++-- .../block/proven_block_success.rs | 50 +++++++++++---- .../src/mock_chain/chain_builder.rs | 61 ++----------------- crates/miden-testing/src/utils.rs | 9 ++- 6 files changed, 67 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d23b14bad..16c8eb6c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Removed `create_p2id_note` and `create_p2any_note` methods from `MockChainBuilder`, users should use `add_p2id_note` and `add_p2any_note` instead ([#1990](https://github.com/0xMiden/miden-base/issues/1990)). - Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). - Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). - [BREAKING] Enabled lazy loading of storage map entries during transaction execution ([#1857](https://github.com/0xMiden/miden-base/pull/1857)). diff --git a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs index c09aac5386..8535e16315 100644 --- a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs +++ b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs @@ -17,6 +17,7 @@ use rand::rngs::SmallRng; use rand::{Rng, SeedableRng}; use super::proven_tx_builder::MockProvenTxBuilder; +use crate::utils::create_p2any_note; use crate::{AccountState, Auth, MockChain, MockChainBuilder}; fn mock_account_id(num: u8) -> AccountId { @@ -271,8 +272,8 @@ fn duplicate_output_notes() -> anyhow::Result<()> { async fn unauthenticated_note_converted_to_authenticated() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account1 = generate_account(&mut builder); - let note1 = builder.create_p2any_note(account1.id(), NoteType::Public, [])?; - let note2 = builder.create_p2any_note(account1.id(), NoteType::Public, [])?; + let note1 = create_p2any_note(account1.id(), NoteType::Public, [], builder.rng_mut()); + let note2 = create_p2any_note(account1.id(), NoteType::Public, [], builder.rng_mut()); let spawn_note = builder.add_spawn_note([¬e1, ¬e2])?; let mut chain = builder.build()?; diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs index 1aca71fe7c..cc7aabf71f 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_errors.rs @@ -3,15 +3,17 @@ use std::collections::BTreeMap; use std::vec::Vec; use assert_matches::assert_matches; +use miden_lib::note::create_p2id_note; use miden_objects::asset::FungibleAsset; use miden_objects::block::{BlockInputs, BlockNumber, ProposedBlock}; use miden_objects::crypto::merkle::SparseMerklePath; use miden_objects::note::{NoteInclusionProof, NoteType}; -use miden_objects::{MAX_BATCHES_PER_BLOCK, ProposedBlockError}; +use miden_objects::{MAX_BATCHES_PER_BLOCK, ProposedBlockError, ZERO}; use miden_processor::crypto::MerklePath; use miden_tx::LocalTransactionProver; use crate::kernel_tests::block::utils::MockChainBlockExt; +use crate::utils::create_p2any_note; use crate::{Auth, MockChain}; /// Tests that too many batches produce an error. @@ -306,7 +308,7 @@ async fn proposed_block_fails_on_duplicate_input_note() -> anyhow::Result<()> { async fn proposed_block_fails_on_duplicate_output_note() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let account = builder.add_existing_mock_account(Auth::IncrNonce)?; - let output_note = builder.create_p2any_note(account.id(), NoteType::Private, [])?; + let output_note = create_p2any_note(account.id(), NoteType::Private, [], builder.rng_mut()); // Create two different notes that will create the same output note. Their IDs will be different // due to having a different serial number generated from contained RNG. @@ -345,8 +347,14 @@ async fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_referen let mut builder = MockChain::builder(); let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; - let p2id_note = - builder.create_p2id_note(account0.id(), account1.id(), [], NoteType::Private)?; + let p2id_note = create_p2id_note( + account0.id(), + account1.id(), + vec![], + NoteType::Private, + ZERO, + builder.rng_mut(), + )?; let spawn_note = builder.add_spawn_note([&p2id_note])?; let mut chain = builder.build()?; @@ -437,7 +445,7 @@ async fn proposed_block_fails_on_missing_note_inclusion_proof() -> anyhow::Resul let account0 = builder.add_existing_mock_account(Auth::IncrNonce)?; let account1 = builder.add_existing_mock_account(Auth::IncrNonce)?; // Note that this note is not added to the chain state. - let note0 = builder.create_p2any_note(account0.id(), NoteType::Private, [])?; + let note0 = create_p2any_note(account0.id(), NoteType::Private, [], builder.rng_mut()); let chain = builder.build()?; let tx0 = chain diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs index 896039c285..80804a9ece 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs @@ -4,7 +4,7 @@ use std::vec::Vec; use anyhow::Context; use miden_block_prover::LocalBlockProver; -use miden_objects::MIN_PROOF_SECURITY_LEVEL; +use miden_lib::note::create_p2id_note; use miden_objects::asset::FungibleAsset; use miden_objects::batch::BatchNoteTree; use miden_objects::block::{ @@ -17,8 +17,10 @@ use miden_objects::block::{ use miden_objects::crypto::merkle::Smt; use miden_objects::note::NoteType; use miden_objects::transaction::InputNoteCommitment; +use miden_objects::{MIN_PROOF_SECURITY_LEVEL, ZERO}; use crate::kernel_tests::block::utils::MockChainBlockExt; +use crate::utils::create_p2any_note; use crate::{Auth, MockChain}; /// Tests the outputs of a proven block with transactions that consume notes, create output notes @@ -37,14 +39,38 @@ async fn proven_block_success() -> anyhow::Result<()> { let account2 = builder.add_existing_mock_account_with_assets(Auth::IncrNonce, [asset])?; let account3 = builder.add_existing_mock_account_with_assets(Auth::IncrNonce, [asset])?; - let output_note0 = - builder.create_p2id_note(account0.id(), account0.id(), [asset], NoteType::Private)?; - let output_note1 = - builder.create_p2id_note(account1.id(), account1.id(), [asset], NoteType::Private)?; - let output_note2 = - builder.create_p2id_note(account2.id(), account2.id(), [asset], NoteType::Private)?; - let output_note3 = - builder.create_p2id_note(account3.id(), account3.id(), [asset], NoteType::Private)?; + let output_note0 = create_p2id_note( + account0.id(), + account0.id(), + vec![asset], + NoteType::Private, + ZERO, + builder.rng_mut(), + )?; + let output_note1 = create_p2id_note( + account1.id(), + account1.id(), + vec![asset], + NoteType::Private, + ZERO, + builder.rng_mut(), + )?; + let output_note2 = create_p2id_note( + account2.id(), + account2.id(), + vec![asset], + NoteType::Private, + ZERO, + builder.rng_mut(), + )?; + let output_note3 = create_p2id_note( + account3.id(), + account3.id(), + vec![asset], + NoteType::Private, + ZERO, + builder.rng_mut(), + )?; let input_note0 = builder.add_spawn_note([&output_note0])?; let input_note1 = builder.add_spawn_note([&output_note1])?; @@ -215,9 +241,9 @@ async fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { // The builder will use an rng which randomizes the note IDs and therefore their position in the // output note batches. This is useful to test that the block note tree is correctly // computed no matter at what index the erased note ends up in. - let output_note0 = builder.create_p2any_note(account0.id(), NoteType::Private, [])?; - let output_note2 = builder.create_p2any_note(account2.id(), NoteType::Private, [])?; - let output_note3 = builder.create_p2any_note(account3.id(), NoteType::Private, [])?; + let output_note0 = create_p2any_note(account0.id(), NoteType::Private, [], builder.rng_mut()); + let output_note2 = create_p2any_note(account2.id(), NoteType::Private, [], builder.rng_mut()); + let output_note3 = create_p2any_note(account3.id(), NoteType::Private, [], builder.rng_mut()); // Sanity check that these notes have different IDs. assert_ne!(output_note0.id(), output_note2.id()); diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index eabab74d51..802dccd233 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -30,7 +30,6 @@ use miden_objects::block::{ OutputNoteBatch, ProvenBlock, }; -use miden_objects::crypto::rand::FeltRng; use miden_objects::note::{Note, NoteDetails, NoteType}; use miden_objects::testing::account_id::ACCOUNT_ID_NATIVE_ASSET_FAUCET; use miden_objects::transaction::{OrderedTransactionHeaders, OutputNote}; @@ -66,22 +65,15 @@ use crate::{AccountState, Auth, MockChain}; /// &[FungibleAsset::mock(100)], /// NoteType::Private, /// )?; -/// let new_note = builder.create_p2id_note( -/// existing_wallet.id(), -/// new_wallet.id(), -/// [FungibleAsset::mock(100)], -/// NoteType::Private, -/// )?; /// let chain = builder.build()?; /// /// // The existing wallet and note should be part of the chain state. /// assert!(chain.committed_account(existing_wallet.id()).is_ok()); /// assert!(chain.committed_notes().get(&existing_note.id()).is_some()); /// -/// // The new wallet and note should *not* be part of the chain state - they must be created in +/// // The new wallet should *not* be part of the chain state - it must be created in /// // a transaction first. /// assert!(chain.committed_account(new_wallet.id()).is_err()); -/// assert!(chain.committed_notes().get(&new_note.id()).is_none()); /// /// # Ok(()) /// # } @@ -463,7 +455,7 @@ impl MockChainBuilder { note_type: NoteType, assets: impl IntoIterator, ) -> anyhow::Result { - let note = self.create_p2any_note(sender_account_id, note_type, assets)?; + let note = create_p2any_note(sender_account_id, note_type, assets, &mut self.rng); self.add_output_note(OutputNote::Full(note.clone())); Ok(note) @@ -481,11 +473,13 @@ impl MockChainBuilder { asset: &[Asset], note_type: NoteType, ) -> Result { - let note = self.create_p2id_note( + let note = create_p2id_note( sender_account_id, target_account_id, - asset.iter().copied(), + asset.to_vec(), note_type, + Felt::ZERO, + &mut self.rng, )?; self.add_output_note(OutputNote::Full(note.clone())); @@ -591,49 +585,6 @@ impl MockChainBuilder { Ok(note) } - // NOTE CREATE METHODS - // ---------------------------------------------------------------------------------------- - - /// Creates a new P2ID note from the provided parameters. - /// - /// The note is _not_ added to the list of genesis notes. It must be created by the caller to - /// make it available in the mock chain, e.g. using [`Self::add_spawn_note`]. - /// - /// This is a convenience wrapper around [`create_p2id_note`]. - pub fn create_p2id_note( - &mut self, - sender_account_id: AccountId, - target_account_id: AccountId, - assets: impl IntoIterator, - note_type: NoteType, - ) -> Result { - let note = create_p2id_note( - sender_account_id, - target_account_id, - assets.into_iter().collect::>(), - note_type, - Default::default(), - &mut self.rng, - )?; - - Ok(note) - } - - /// Creates a new P2ANY note from the provided parameters. - /// - /// This note is similar to a P2ID note but can be consumed by any account. - /// - /// The note is _not_ added to the list of genesis notes. It must be created by the caller to - /// make it available in the mock chain, e.g. using [`Self::add_spawn_note`]. - pub fn create_p2any_note( - &mut self, - sender_account_id: AccountId, - note_type: NoteType, - assets: impl IntoIterator, - ) -> anyhow::Result { - Ok(create_p2any_note(sender_account_id, note_type, self.rng.draw_word(), assets)) - } - // HELPER FUNCTIONS // ---------------------------------------------------------------------------------------- diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index c5845bf418..bbf9020b7c 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -3,12 +3,13 @@ use alloc::vec::Vec; use miden_lib::testing::note::NoteBuilder; use miden_lib::transaction::TransactionKernel; -use miden_objects::Word; use miden_objects::account::AccountId; use miden_objects::asset::Asset; +use miden_objects::crypto::rand::FeltRng; use miden_objects::note::{Note, NoteType}; use miden_objects::testing::storage::prepare_assets; use miden_processor::Felt; +use miden_processor::crypto::RpoRandomCoin; use rand::SeedableRng; use rand::rngs::SmallRng; @@ -77,7 +78,8 @@ pub fn create_public_p2any_note( sender: AccountId, assets: impl IntoIterator, ) -> Note { - create_p2any_note(sender, NoteType::Public, Word::from([1, 2, 3, 4u32]), assets) + let mut rng = RpoRandomCoin::new(Default::default()); + create_p2any_note(sender, NoteType::Public, assets, &mut rng) } /// Creates a `P2ANY` note. @@ -89,9 +91,10 @@ pub fn create_public_p2any_note( pub fn create_p2any_note( sender: AccountId, note_type: NoteType, - serial_number: Word, assets: impl IntoIterator, + rng: &mut RpoRandomCoin, ) -> Note { + let serial_number = rng.draw_word(); let assets: Vec<_> = assets.into_iter().collect(); let mut code_body = String::new(); for i in 0..assets.len() { From 388e1ca9654dd100ac59dbe80b04aedf7d4e5f06 Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Fri, 17 Oct 2025 02:30:44 +0530 Subject: [PATCH 092/133] chore: update current impl state docs of `LocalBatchProver::prove` (#2004) * feat(docs): Reflect the current state of implementation in LocalBatchProver::prove * Apply suggestions from code review --------- Co-authored-by: Marti --- crates/miden-objects/src/batch/proven_batch.rs | 2 ++ crates/miden-tx-batch-prover/src/local_batch_prover.rs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/crates/miden-objects/src/batch/proven_batch.rs b/crates/miden-objects/src/batch/proven_batch.rs index c6217bb78f..97075a8736 100644 --- a/crates/miden-objects/src/batch/proven_batch.rs +++ b/crates/miden-objects/src/batch/proven_batch.rs @@ -12,6 +12,8 @@ use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, use crate::{MIN_PROOF_SECURITY_LEVEL, Word}; /// A transaction batch with an execution proof. +/// Currently, there is no proof attached. Future versions will extend this structure to include +/// a proof artifact once recursive proving is implemented. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProvenBatch { id: BatchId, diff --git a/crates/miden-tx-batch-prover/src/local_batch_prover.rs b/crates/miden-tx-batch-prover/src/local_batch_prover.rs index 6ad92a7f6f..3636f61ac5 100644 --- a/crates/miden-tx-batch-prover/src/local_batch_prover.rs +++ b/crates/miden-tx-batch-prover/src/local_batch_prover.rs @@ -22,6 +22,10 @@ impl LocalBatchProver { /// Attempts to prove the [`ProposedBatch`] into a [`ProvenBatch`]. /// + /// Currently we don't perform any recursive proving. For now, this function runs a native + /// verifier for each transaction separately, and outputs a `ProvenBatch` object if all of the + /// individual proofs verify. + /// /// # Errors /// /// Returns an error if: From 8b385718e4ccb4a7890bed4cbcaeb22d48875428 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 17 Oct 2025 01:43:37 +0200 Subject: [PATCH 093/133] fix: strip seed when incrementing account nonce (#2003) --- CHANGELOG.md | 2 +- crates/miden-objects/src/account/mod.rs | 34 +++++++++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16c8eb6c61..6ece3b6854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ - [BREAKING] Enabled lazy loading of foreign accounts during transaction execution ([#1873](https://github.com/0xMiden/miden-base/pull/1873)). - Added lazy loading of the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). - Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). -- [BREAKING] Move account seed into `PartialAccount` ([#1875](https://github.com/0xMiden/miden-base/pull/1875)). +- [BREAKING] Move account seed into `PartialAccount` ([#1875](https://github.com/0xMiden/miden-base/pull/1875), [#2003](https://github.com/0xMiden/miden-base/pull/2003)). - [BREAKING] Enabled lazy loading of assets and storage map items for foreign accounts during transaction execution ([#1888](https://github.com/0xMiden/miden-base/pull/1888)). - Added `get_initial_item` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). - Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)). diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index e7293af79a..4341e19ce2 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -361,14 +361,6 @@ impl Account { // update nonce self.increment_nonce(delta.nonce_delta())?; - // Maintain internal consistency of the account, i.e. the seed should not be present for - // existing accounts, where existing accounts are defined as having a nonce > 0. - // If we've incremented the nonce, then we should remove the seed (if it was present at - // all). - if delta.nonce_delta().as_int() > 0 { - self.seed = None; - } - Ok(()) } @@ -391,6 +383,14 @@ impl Account { self.nonce = new_nonce; + // Maintain internal consistency of the account, i.e. the seed should not be present for + // existing accounts, where existing accounts are defined as having a nonce > 0. + // If we've incremented the nonce, then we should remove the seed (if it was present at + // all). + if !self.is_new() { + self.seed = None; + } + Ok(()) } @@ -554,6 +554,7 @@ mod tests { AccountComponent, AccountIdVersion, AccountType, + PartialAccount, StorageMap, StorageMapDelta, StorageSlot, @@ -890,4 +891,21 @@ mod tests { Ok(()) } + + #[test] + fn incrementing_nonce_should_remove_seed() -> anyhow::Result<()> { + let mut account = AccountBuilder::new([5; 32]) + .with_auth_component(NoopAuthComponent) + .with_component(AddComponent) + .build()?; + account.increment_nonce(Felt::ONE)?; + + assert_matches!(account.seed(), None); + + // Sanity check: We should be able to convert the account into a partial account which will + // re-check the internal seed - nonce consistency. + let _partial_account = PartialAccount::from(&account); + + Ok(()) + } } From 1f7a8a9cf76e84fb484fd5a2f537848c6998de4b Mon Sep 17 00:00:00 2001 From: Marti Date: Fri, 17 Oct 2025 15:04:35 +0200 Subject: [PATCH 094/133] feat: per-procedure threshold approvals in multisig (require 1 approver to consume note) (#1968) * feat: add multisig config to the component * feat: complete single approver test, failing * chore: update storage layout table and descriptions chore: add description and constants for threshold proc maps chore: update multisig config layout * chore: simplify storage * chore: rename owner_public_key -> APPROVER_PUBLIC_KEY * chore: rename owner -> approver * feat: check proc thresholds * feat: expose num_procedures & get_proc_root in miden lib * feat: Add per-procedure threshold to multisig auth Co-authored-by: marti * Update CHANGELOG.md * Apply suggestions from (self) code review * chore: fix struct name in comments * chore: use u32gt instead of gt for efficiency * chore: refactor out compute_transaction_threshold helper proc * chore: consolidate multisig config * chore: add empty line between comment and code * fix: doc update proc information -> root * chore: more efficient stack padding * chore: use "default_threshold" where appropriate * chore: use "transaction_threshold" name * chore: use "proc_threshold" for the per-procedure threshold name * chore: add new procedure signatures to the table pf procedures * chore: use get_map_item_init to prevent bypasses * feat: extend the proc_threshold test to cover default_threshold case * fix: add tx script to emit a note * chore: add comments about steps in the loop * Apply suggestions from code review Co-authored-by: Philipp Gackstatter * chore: unify naming to "proc thresholds" * chore: use cdrop to select effective tx threshold * chore: use cdrop for updating tx threshold * chore: add more details about per-proc thresholds to rust type * Update CHANGELOG.md --------- Co-authored-by: Cursor Agent Co-authored-by: Philipp Gackstatter --- CHANGELOG.md | 1 + .../multisig_rpo_falcon_512.masm | 142 ++++++++++++-- .../asm/kernels/transaction/api.masm | 44 +++++ crates/miden-lib/asm/miden/account.masm | 57 ++++++ .../asm/miden/kernel_proc_offsets.masm | 80 +++++--- crates/miden-lib/src/account/auth/mod.rs | 2 +- .../account/auth/rpo_falcon_512_multisig.rs | 130 ++++++++++--- crates/miden-lib/src/account/faucets/mod.rs | 6 +- .../miden-lib/src/account/interface/test.rs | 13 +- crates/miden-lib/src/account/wallets/mod.rs | 13 +- .../src/transaction/kernel_procedures.rs | 6 +- crates/miden-testing/src/mock_chain/auth.rs | 14 +- crates/miden-testing/tests/auth/multisig.rs | 182 +++++++++++++++++- docs/src/protocol_library.md | 2 + 14 files changed, 594 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ece3b6854..701234fbdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Implement `SlotName` for named storage slots ([#1932](https://github.com/0xMiden/miden-base/issues/1932)). - [BREAKING] Removed `get_falcon_signature` from `miden-tx` crate ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Created a `Signature` wrapper to simplify the preparation of "native" signatures for use in the VM ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). + - Added per-procedure approval thresholds to `AuthRpoFalcon512Multisig` auth component ([#1968](https://github.com/0xMiden/miden-base/pull/1968)). - Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933](https://github.com/0xMiden/miden-base/pull/1933)). - Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937)). - Added `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935](https://github.com/0xMiden/miden-base/pull/1935)). diff --git a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm index 5221762692..6808a8e145 100644 --- a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm @@ -1,4 +1,6 @@ # The MASM code of the Multi-Signature RPO Falcon 512 Authentication Component. +# +# See the `AuthRpoFalcon512Multisig` Rust type's documentation for more details. use.miden::account use.miden::auth @@ -13,26 +15,31 @@ const.AUTH_UNAUTHORIZED_EVENT=event("miden::auth::unauthorized") # Storage Layout Constants # -# ┌─────────────────────────────┬──────────┬──────────────┐ -# │ THRESHOLD & APPROVERS │ PUB KEYS │ EXECUTED TXS │ -# │ (slot) │ (map) │ (map) │ -# ├─────────────────────────────┼──────────┼──────────────┤ -# │ 0 │ 1 │ 2 │ -# └─────────────────────────────┴──────────┴──────────────┘ - -# The slot in this component's storage layout where both the signature threshold -# and number of approvers are stored as [threshold, num_approvers, 0, 0]. +# ┌───────────────────────────────┬──────────┬──────────────┬───────────────────┐ +# │ THRESHOLD & APPROVERS CONFIG │ PUB KEYS │ EXECUTED TXS │ PROC THRESHOLDS │ +# │ (slot) │ (map) │ (map) │ (map) │ +# ├───────────────────────────────┼──────────┼──────────────┼───────────────────┤ +# │ 0 │ 1 │ 2 │ 3 │ +# └───────────────────────────────┴──────────┴──────────────┴───────────────────┘ + +# The slot in this component's storage layout where the default signature threshold and +# number of approvers are stored as: +# [default_threshold, num_approvers, 0, 0]. # The threshold is guaranteed to be less than or equal to num_approvers. const.THRESHOLD_CONFIG_SLOT=0 # The slot in this component's storage layout where the public keys map is stored. -# Map entries: [key_index, 0, 0, 0] => owner_public_key +# Map entries: [key_index, 0, 0, 0] => APPROVER_PUBLIC_KEY const.PUBLIC_KEYS_MAP_SLOT=1 # The slot in this component's storage layout where executed transactions are stored. # Map entries: transaction_message => [is_executed, 0, 0, 0] const.EXECUTED_TXS_SLOT=2 +# The slot in this component's storage layout where procedure thresholds are stored. +# Map entries: PROC_ROOT => [proc_threshold, 0, 0, 0] +const.PROC_THRESHOLD_ROOTS_SLOT=3 + # Executed Transaction Flag Constant const.IS_EXECUTED_FLAG=[1, 0, 0, 0] @@ -71,9 +78,9 @@ proc.assert_new_tx # => [] end -#! Remove old owner public keys from the owner public key mapping. +#! Remove old approver public keys from the approver public key mapping. #! -#! This procedure cleans up the storage by removing public keys of owners that are no longer +#! This procedure cleans up the storage by removing public keys of approvers that are no longer #! part of the multisig configuration. This procedure assumes that init_num_of_approvers and #! new_num_of_approvers are u32 values. #! @@ -121,7 +128,7 @@ proc.cleanup_pubkey_mapping # => [] end -#! Update threshold config and add / remove owners +#! Update threshold config and add / remove approvers #! #! Inputs: #! Operand stack: [MULTISIG_CONFIG_HASH, pad(12)] @@ -220,6 +227,96 @@ export.update_signers_and_threshold.2 # => [pad(12)] end +# Computes the effective transaction threshold based on called procedures and per-procedure +# overrides stored in PROC_THRESHOLD_ROOTS_SLOT. Falls back to default_threshold if no +# overrides apply. +# +#! Inputs: [default_threshold] +#! Outputs: [transaction_threshold] +proc.compute_transaction_threshold.1 + # 1. initialize transaction_threshold = 0 + # 2. iterate through all account procedures + # a. check if the procedure was called during the transaction + # b. if called, get the override threshold of that procedure from the config map + # c. if proc_threshold > transaction_threshold, set transaction_threshold = proc_threshold + # 3. if transaction_threshold == 0 at the end, revert to using default_threshold + + # store default_threshold for later + loc_store.0 + # => [] + + # 1. initialize transaction_threshold = 0 + push.0 + # => [transaction_threshold] + + # get the number of account procedures + exec.account::get_num_procedures + # => [num_procedures, transaction_threshold] + + # 2. iterate through all account procedures + dup neq.0 + # => [should_continue, num_procedures, transaction_threshold] + while.true + sub.1 dup + # => [num_procedures-1, num_procedures-1, transaction_threshold] + + # get procedure root of the procedure with index i + exec.account::get_procedure_root dupw + # => [PROC_ROOT, PROC_ROOT, num_procedures-1, transaction_threshold] + + # 2a. check if this procedure has been called in the transaction + exec.account::was_procedure_called + # => [was_called, PROC_ROOT, num_procedures-1, transaction_threshold] + + # if it has been called, get the override threshold of that procedure + if.true + # => [PROC_ROOT, num_procedures-1, transaction_threshold] + + push.PROC_THRESHOLD_ROOTS_SLOT + # => [PROC_THRESHOLD_ROOTS_SLOT, PROC_ROOT, num_procedures-1, transaction_threshold] + + # 2b. get the override proc_threshold of that procedure + # if the procedure has no override threshold, the returned map item will be [0, 0, 0, 0] + exec.account::get_initial_map_item + # => [[0, 0, 0, proc_threshold], num_procedures-1, transaction_threshold] + + drop drop drop dup dup.3 + # => [transaction_threshold, proc_threshold, proc_threshold, num_procedures-1, transaction_threshold] + + u32assert2.err="transaction threshold or procedure threshold are not u32" + u32gt + # => [is_gt, proc_threshold, num_procedures-1, transaction_threshold] + # 2c. if proc_threshold > transaction_threshold, update transaction_threshold + movup.2 movdn.3 + # => [is_gt, proc_threshold, transaction_threshold, num_procedures-1] + cdrop + # => [updated_transaction_threshold, num_procedures-1] + swap + # => [num_procedures-1, updated_transaction_threshold] + # if it has not been called during this transaction, nothing to do, move to the next procedure + else + dropw + # => [num_procedures-1, transaction_threshold] + end + + dup neq.0 + # => [should_continue, num_procedures-1, transaction_threshold] + end + + drop + # => [transaction_threshold] + + loc_load.0 + # => [default_threshold, transaction_threshold] + + # 3. if transaction_threshold == 0 at the end, revert to using default_threshold + dup.1 eq.0 + # => [is_zero, default_threshold, transaction_threshold] + + cdrop + # => [effective_transaction_threshold] +end + #! Authenticate a transaction using the Falcon signature scheme with multi-signature support. #! #! This procedure implements multi-signature authentication by: @@ -251,7 +348,7 @@ end #! - the same transaction has already been executed (replay protection). #! #! Invocation: call -export.auth_tx_rpo_falcon512_multisig +export.auth_tx_rpo_falcon512_multisig.1 exec.account::incr_nonce drop # => [SALT] @@ -268,30 +365,33 @@ export.auth_tx_rpo_falcon512_multisig exec.auth::hash_tx_summary # => [TX_SUMMARY_COMMITMENT] - # ------ Verifying owner signatures ------ + # ------ Verifying approver signatures ------ push.THRESHOLD_CONFIG_SLOT # => [index, TX_SUMMARY_COMMITMENT] exec.account::get_initial_item - # => [0, 0, num_of_approvers, threshold, TX_SUMMARY_COMMITMENT] + # => [0, 0, num_of_approvers, default_threshold, TX_SUMMARY_COMMITMENT] drop drop - # => [num_of_approvers, threshold, TX_SUMMARY_COMMITMENT] + # => [num_of_approvers, default_threshold, TX_SUMMARY_COMMITMENT] swap movdn.5 - # => [num_of_approvers, TX_SUMMARY_COMMITMENT, threshold] + # => [num_of_approvers, TX_SUMMARY_COMMITMENT, default_threshold] push.PUBLIC_KEYS_MAP_SLOT - # => [pub_key_slot_idx, num_of_approvers, TX_SUMMARY_COMMITMENT, threshold] + # => [pub_key_slot_idx, num_of_approvers, TX_SUMMARY_COMMITMENT, default_threshold] exec.::miden::auth::rpo_falcon512::verify_signatures - # => [num_verified_signatures, TX_SUMMARY_COMMITMENT, threshold] + # => [num_verified_signatures, TX_SUMMARY_COMMITMENT, default_threshold] # ------ Checking threshold is >= num_verified_signatures ------ movup.5 - # => [threshold, num_verified_signatures, TX_SUMMARY_COMMITMENT] + # => [default_threshold, num_verified_signatures, TX_SUMMARY_COMMITMENT] + + exec.compute_transaction_threshold + # => [transaction_threshold, num_verified_signatures, TX_SUMMARY_COMMITMENT] u32assert2 u32lt # => [is_unauthorized, TX_SUMMARY_COMMITMENT] diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 55c4d93f63..8295917fa3 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -1463,6 +1463,50 @@ export.account_was_procedure_called # => [was_called, pad(15)] end +#! Returns the number of procedures in the current account. +#! +#! Inputs: [pad(16)] +#! Outputs: [num_procedures, pad(15)] +#! +#! Where: +#! - num_procedures is the number of procedures in the current account. +#! +#! Invocation: dynexec +export.account_get_num_procedures + # get the number of procedures + exec.memory::get_num_account_procedures + # => [num_procedures, pad(16)] + + # truncate the stack + swap drop + # => [num_procedures, pad(15)] +end + +#! Returns the procedure root for the procedure at the specified index. +#! +#! Inputs: [index, pad(15)] +#! Outputs: [PROC_ROOT, pad(12)] +#! +#! Where: +#! - index is the index of the procedure. +#! - PROC_ROOT is the hash of the procedure. +#! +#! Panics if: +#! - the procedure index is out of bounds. +#! +#! Invocation: dynexec +export.account_get_procedure_root + # get the procedure information + exec.account::get_procedure_info + # => [PROC_ROOT, storage_offset, storage_size, pad(15)] + + swapw dropw + # => [PROC_ROOT, pad(13)] + + movup.4 drop + # => [PROC_ROOT, pad(12)] +end + #! Executes a kernel procedure specified by its offset. #! #! Inputs: [procedure_offset, , ] diff --git a/crates/miden-lib/asm/miden/account.masm b/crates/miden-lib/asm/miden/account.masm index d2b422df12..39ffe3dc7a 100644 --- a/crates/miden-lib/asm/miden/account.masm +++ b/crates/miden-lib/asm/miden/account.masm @@ -740,3 +740,60 @@ export.was_procedure_called swapdw dropw dropw swapw dropw movdn.3 drop drop drop # => [was_called] end + +#! Returns the number of procedures in the current account. +#! +#! Inputs: [] +#! Outputs: [num_procedures] +#! +#! Where: +#! - num_procedures is the number of procedures in the current account. +#! +#! Invocation: exec +export.get_num_procedures + # pad the stack + padw padw padw push.0.0.0 + # => [pad(15)] + + exec.kernel_proc_offsets::account_get_num_procedures_offset + # => [offset, pad(15)] + + syscall.exec_kernel_proc + # => [num_procedures, pad(15)] + + # clean the stack + swap.15 dropw dropw dropw drop drop drop + # => [num_procedures] +end + +#! Returns the procedure root for the procedure at the specified index. +#! +#! Inputs: [index] +#! Outputs: [PROC_ROOT] +#! +#! Where: +#! - index is the index of the procedure. +#! - PROC_ROOT is the hash of the procedure. +#! +#! Panics if: +#! - the procedure index is out of bounds. +#! +#! Invocation: exec +export.get_procedure_root + # => [index] + + push.0.0 movup.2 + exec.kernel_proc_offsets::account_get_procedure_root_offset + # => [offset, index, 0, 0] + + # pad the stack + padw swapw padw padw swapdw + # => [offset, index, pad(14)] + + syscall.exec_kernel_proc + # => [PROC_ROOT, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [PROC_ROOT] +end diff --git a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm index 90e630cef0..023224220c 100644 --- a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm +++ b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm @@ -42,53 +42,55 @@ const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=22 const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=23 # Procedure introspection -const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=24 +const.ACCOUNT_GET_NUM_PROCEDURES_OFFSET=24 +const.ACCOUNT_GET_PROCEDURE_ROOT_OFFSET=25 +const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=26 ### Faucet ###################################### -const.FAUCET_MINT_ASSET_OFFSET=25 -const.FAUCET_BURN_ASSET_OFFSET=26 -const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=27 -const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=28 +const.FAUCET_MINT_ASSET_OFFSET=27 +const.FAUCET_BURN_ASSET_OFFSET=28 +const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=29 +const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=30 ### Note ######################################## # input notes -const.INPUT_NOTE_GET_METADATA_OFFSET=29 -const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=30 -const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=31 -const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=32 -const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=33 -const.INPUT_NOTE_GET_RECIPIENT_OFFSET=34 +const.INPUT_NOTE_GET_METADATA_OFFSET=31 +const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=32 +const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=33 +const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=34 +const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=35 +const.INPUT_NOTE_GET_RECIPIENT_OFFSET=36 # output notes -const.OUTPUT_NOTE_CREATE_OFFSET=35 -const.OUTPUT_NOTE_GET_METADATA_OFFSET=36 -const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=37 -const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=38 -const.OUTPUT_NOTE_ADD_ASSET_OFFSET=39 +const.OUTPUT_NOTE_CREATE_OFFSET=37 +const.OUTPUT_NOTE_GET_METADATA_OFFSET=38 +const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=39 +const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=40 +const.OUTPUT_NOTE_ADD_ASSET_OFFSET=41 ### Tx ########################################## # input notes -const.TX_GET_NUM_INPUT_NOTES_OFFSET=40 -const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=41 +const.TX_GET_NUM_INPUT_NOTES_OFFSET=42 +const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=43 # output notes -const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=42 -const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=43 +const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=44 +const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=45 # block info -const.TX_GET_BLOCK_COMMITMENT_OFFSET=44 -const.TX_GET_BLOCK_NUMBER_OFFSET=45 -const.TX_GET_BLOCK_TIMESTAMP_OFFSET=46 +const.TX_GET_BLOCK_COMMITMENT_OFFSET=46 +const.TX_GET_BLOCK_NUMBER_OFFSET=47 +const.TX_GET_BLOCK_TIMESTAMP_OFFSET=48 # foreign context -const.TX_START_FOREIGN_CONTEXT_OFFSET=47 -const.TX_END_FOREIGN_CONTEXT_OFFSET=48 +const.TX_START_FOREIGN_CONTEXT_OFFSET=49 +const.TX_END_FOREIGN_CONTEXT_OFFSET=50 # expiration data -const.TX_GET_EXPIRATION_DELTA_OFFSET=49 # accessor -const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=50 # mutator +const.TX_GET_EXPIRATION_DELTA_OFFSET=51 # accessor +const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=52 # mutator # ACCESSORS # ------------------------------------------------------------------------------------------------- @@ -395,6 +397,30 @@ export.account_was_procedure_called_offset push.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET end +#! Returns the offset of the `account_get_num_procedures` kernel procedure. +#! +#! Inputs: [] +#! Outputs: [proc_offset] +#! +#! Where: +#! - proc_offset is the offset of the `account_get_num_procedures` kernel procedure required to +#! get the address where this procedure is stored. +export.account_get_num_procedures_offset + push.ACCOUNT_GET_NUM_PROCEDURES_OFFSET +end + +#! Returns the offset of the `account_get_procedure_root` kernel procedure. +#! +#! Inputs: [] +#! Outputs: [proc_offset] +#! +#! Where: +#! - proc_offset is the offset of the `account_get_procedure_root` kernel procedure required to +#! get the address where this procedure is stored. +export.account_get_procedure_root_offset + push.ACCOUNT_GET_PROCEDURE_ROOT_OFFSET +end + ### FAUCET ###################################### #! Returns the offset of the `faucet_mint_asset` kernel procedure. diff --git a/crates/miden-lib/src/account/auth/mod.rs b/crates/miden-lib/src/account/auth/mod.rs index 2861b784b0..f54e947405 100644 --- a/crates/miden-lib/src/account/auth/mod.rs +++ b/crates/miden-lib/src/account/auth/mod.rs @@ -8,4 +8,4 @@ mod rpo_falcon_512_acl; pub use rpo_falcon_512_acl::{AuthRpoFalcon512Acl, AuthRpoFalcon512AclConfig}; mod rpo_falcon_512_multisig; -pub use rpo_falcon_512_multisig::AuthRpoFalcon512Multisig; +pub use rpo_falcon_512_multisig::{AuthRpoFalcon512Multisig, AuthRpoFalcon512MultisigConfig}; diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs index f3842537be..692aad523c 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs @@ -8,40 +8,94 @@ use crate::account::components::rpo_falcon_512_multisig_library; // MULTISIG AUTHENTICATION COMPONENT // ================================================================================================ +/// Configuration for [`AuthRpoFalcon512Multisig`] component. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AuthRpoFalcon512MultisigConfig { + approvers: Vec, + default_threshold: u32, + proc_thresholds: Vec<(Word, u32)>, +} + +impl AuthRpoFalcon512MultisigConfig { + /// Creates a new configuration with the given approvers and a default threshold. + /// + /// The `default_threshold` must be at least 1 and at most the number of approvers. + pub fn new( + approvers: Vec, + default_threshold: u32, + ) -> Result { + if default_threshold == 0 { + return Err(AccountError::other("threshold must be at least 1")); + } + if default_threshold > approvers.len() as u32 { + return Err(AccountError::other( + "threshold cannot be greater than number of approvers", + )); + } + + Ok(Self { + approvers, + default_threshold, + proc_thresholds: vec![], + }) + } + + /// Attaches a per-procedure threshold map. Each procedure threshold must be at least 1 and + /// at most the number of approvers. + pub fn with_proc_thresholds( + mut self, + proc_thresholds: Vec<(Word, u32)>, + ) -> Result { + for (_, threshold) in &proc_thresholds { + if *threshold == 0 { + return Err(AccountError::other("procedure threshold must be at least 1")); + } + if *threshold > self.approvers.len() as u32 { + return Err(AccountError::other( + "procedure threshold cannot be greater than number of approvers", + )); + } + } + self.proc_thresholds = proc_thresholds; + Ok(self) + } + + pub fn approvers(&self) -> &[PublicKeyCommitment] { + &self.approvers + } + + pub fn default_threshold(&self) -> u32 { + self.default_threshold + } + + pub fn proc_thresholds(&self) -> &[(Word, u32)] { + &self.proc_thresholds + } +} + /// An [`AccountComponent`] implementing a multisig based on RpoFalcon512 signatures. /// -/// This component requires a threshold number of signatures from a set of approvers. +/// It enforces a threshold of approver signatures for every transaction, with optional +/// per-procedure thresholds overrides. Non-uniform thresholds (especially a threshold of one) +/// should be used with caution for private multisig accounts, as a single approver could withhold +/// the new state from other approvers, effectively locking them out. /// /// The storage layout is: /// - Slot 0(value): [threshold, num_approvers, 0, 0] /// - Slot 1(map): A map with approver public keys (index -> pubkey) /// - Slot 2(map): A map which stores executed transactions +/// - Slot 3(map): A map which stores procedure thresholds (PROC_ROOT -> threshold) /// /// This component supports all account types. #[derive(Debug)] pub struct AuthRpoFalcon512Multisig { - threshold: u32, - approvers: Vec, + config: AuthRpoFalcon512MultisigConfig, } impl AuthRpoFalcon512Multisig { - /// Creates a new [`AuthRpoFalcon512Multisig`] component with the given `threshold` and - /// list of approver public keys. - /// - /// # Errors - /// Returns an error if threshold is 0 or greater than the number of approvers. - pub fn new(threshold: u32, approvers: Vec) -> Result { - if threshold == 0 { - return Err(AccountError::other("threshold must be at least 1")); - } - - if threshold > approvers.len() as u32 { - return Err(AccountError::other( - "threshold cannot be greater than number of approvers", - )); - } - - Ok(Self { threshold, approvers }) + /// Creates a new [`AuthRpoFalcon512Multisig`] component from the provided configuration. + pub fn new(config: AuthRpoFalcon512MultisigConfig) -> Result { + Ok(Self { config }) } } @@ -50,9 +104,9 @@ impl From for AccountComponent { let mut storage_slots = Vec::with_capacity(3); // Slot 0: [threshold, num_approvers, 0, 0] - let num_approvers = multisig.approvers.len() as u32; + let num_approvers = multisig.config.approvers().len() as u32; storage_slots.push(StorageSlot::Value(Word::from([ - multisig.threshold, + multisig.config.default_threshold(), num_approvers, 0, 0, @@ -60,7 +114,8 @@ impl From for AccountComponent { // Slot 1: A map with approver public keys let map_entries = multisig - .approvers + .config + .approvers() .iter() .enumerate() .map(|(i, pub_key)| (Word::from([i as u32, 0, 0, 0]), (*pub_key).into())); @@ -72,6 +127,17 @@ impl From for AccountComponent { let executed_transactions = StorageMap::default(); storage_slots.push(StorageSlot::Map(executed_transactions)); + // Slot 3: A map which stores procedure thresholds (PROC_ROOT -> threshold) + let proc_threshold_roots = StorageMap::with_entries( + multisig + .config + .proc_thresholds() + .iter() + .map(|(proc_root, threshold)| (*proc_root, Word::from([*threshold, 0, 0, 0]))), + ) + .unwrap(); + storage_slots.push(StorageSlot::Map(proc_threshold_roots)); + AccountComponent::new(rpo_falcon_512_multisig_library(), storage_slots) .expect("Multisig auth component should satisfy the requirements of a valid account component") .with_supports_all_types() @@ -99,8 +165,11 @@ mod tests { let threshold = 2u32; // Create multisig component - let multisig_component = AuthRpoFalcon512Multisig::new(threshold, approvers.clone()) - .expect("multisig component creation failed"); + let multisig_component = AuthRpoFalcon512Multisig::new( + AuthRpoFalcon512MultisigConfig::new(approvers.clone(), threshold) + .expect("invalid multisig config"), + ) + .expect("multisig component creation failed"); // Build account with multisig component let account = AccountBuilder::new([0; 32]) @@ -130,8 +199,11 @@ mod tests { let approvers = vec![pub_key]; let threshold = 1u32; - let multisig_component = AuthRpoFalcon512Multisig::new(threshold, approvers.clone()) - .expect("multisig component creation failed"); + let multisig_component = AuthRpoFalcon512Multisig::new( + AuthRpoFalcon512MultisigConfig::new(approvers.clone(), threshold) + .expect("invalid multisig config"), + ) + .expect("multisig component creation failed"); let account = AccountBuilder::new([0; 32]) .with_auth_component(multisig_component) @@ -157,11 +229,11 @@ mod tests { let approvers = vec![pub_key]; // Test threshold = 0 (should fail) - let result = AuthRpoFalcon512Multisig::new(0, approvers.clone()); + let result = AuthRpoFalcon512MultisigConfig::new(approvers.clone(), 0); assert!(result.unwrap_err().to_string().contains("threshold must be at least 1")); // Test threshold > number of approvers (should fail) - let result = AuthRpoFalcon512Multisig::new(2, approvers); + let result = AuthRpoFalcon512MultisigConfig::new(approvers, 2); assert!( result .unwrap_err() diff --git a/crates/miden-lib/src/account/faucets/mod.rs b/crates/miden-lib/src/account/faucets/mod.rs index 041728add9..b2a129c62c 100644 --- a/crates/miden-lib/src/account/faucets/mod.rs +++ b/crates/miden-lib/src/account/faucets/mod.rs @@ -19,6 +19,7 @@ use crate::account::auth::{ AuthRpoFalcon512Acl, AuthRpoFalcon512AclConfig, AuthRpoFalcon512Multisig, + AuthRpoFalcon512MultisigConfig, }; use crate::account::components::basic_fungible_faucet_library; use crate::procedure_digest; @@ -283,7 +284,10 @@ pub fn create_basic_fungible_faucet( .map_err(FungibleFaucetError::AccountError)? .into(), AuthScheme::RpoFalcon512Multisig { threshold, pub_keys } => { - AuthRpoFalcon512Multisig::new(threshold, pub_keys) + // TODO this means burning the asset requires m/n approvals. We should address this. + let config = AuthRpoFalcon512MultisigConfig::new(pub_keys, threshold) + .map_err(FungibleFaucetError::AccountError)?; + AuthRpoFalcon512Multisig::new(config) .map_err(FungibleFaucetError::AccountError)? .into() }, diff --git a/crates/miden-lib/src/account/interface/test.rs b/crates/miden-lib/src/account/interface/test.rs index 18fef5f477..f8a44141e8 100644 --- a/crates/miden-lib/src/account/interface/test.rs +++ b/crates/miden-lib/src/account/interface/test.rs @@ -31,7 +31,12 @@ use miden_objects::testing::account_id::{ use miden_objects::{AccountError, Felt, NoteError, Word, ZERO}; use crate::AuthScheme; -use crate::account::auth::{AuthRpoFalcon512, AuthRpoFalcon512Multisig, NoAuth}; +use crate::account::auth::{ + AuthRpoFalcon512, + AuthRpoFalcon512Multisig, + AuthRpoFalcon512MultisigConfig, + NoAuth, +}; use crate::account::faucets::BasicFungibleFaucet; use crate::account::interface::{ AccountComponentInterface, @@ -899,8 +904,10 @@ fn test_public_key_extraction_multisig_account() { let threshold = 2u32; // Create multisig component - let multisig_component = AuthRpoFalcon512Multisig::new(threshold, approvers.clone()) - .expect("multisig component creation failed"); + let multisig_component = AuthRpoFalcon512Multisig::new( + AuthRpoFalcon512MultisigConfig::new(approvers.clone(), threshold).unwrap(), + ) + .expect("multisig component creation failed"); let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes(); let multisig_account = AccountBuilder::new(mock_seed) diff --git a/crates/miden-lib/src/account/wallets/mod.rs b/crates/miden-lib/src/account/wallets/mod.rs index 7bf3b6d1ec..fd59463367 100644 --- a/crates/miden-lib/src/account/wallets/mod.rs +++ b/crates/miden-lib/src/account/wallets/mod.rs @@ -11,7 +11,11 @@ use miden_objects::{AccountError, Word}; use thiserror::Error; use super::AuthScheme; -use crate::account::auth::{AuthRpoFalcon512, AuthRpoFalcon512Multisig}; +use crate::account::auth::{ + AuthRpoFalcon512, + AuthRpoFalcon512Multisig, + AuthRpoFalcon512MultisigConfig, +}; use crate::account::components::basic_wallet_library; use crate::procedure_digest; @@ -115,7 +119,12 @@ pub fn create_basic_wallet( let auth_component: AccountComponent = match auth_scheme { AuthScheme::RpoFalcon512 { pub_key } => AuthRpoFalcon512::new(pub_key).into(), AuthScheme::RpoFalcon512Multisig { threshold, pub_keys } => { - AuthRpoFalcon512Multisig::new(threshold, pub_keys) + let config = AuthRpoFalcon512MultisigConfig::new(pub_keys, threshold) + .and_then(|cfg| { + cfg.with_proc_thresholds(vec![(BasicWallet::receive_asset_digest(), 1)]) + }) + .map_err(BasicWalletError::AccountError)?; + AuthRpoFalcon512Multisig::new(config) .map_err(BasicWalletError::AccountError)? .into() }, diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index d8fd0272bb..95b09b15f2 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -6,7 +6,7 @@ use miden_objects::{Word, word}; // ================================================================================================ /// Hashes of all dynamically executed kernel procedures. -pub const KERNEL_PROCEDURES: [Word; 51] = [ +pub const KERNEL_PROCEDURES: [Word; 53] = [ // account_get_initial_commitment word!("0x920898348bacd6d98a399301eb308478fd32b32eab019a5a6ef7a6b44abb61f6"), // account_compute_current_commitment @@ -55,6 +55,10 @@ pub const KERNEL_PROCEDURES: [Word; 51] = [ word!("0xf975c799cffebf8565a8479475fe04c1832ef2a7484c4a2a42bbf7d8a340d649"), // account_compute_delta_commitment word!("0x57165e9bc287a6f7b96be5f20a3f6752129afc756f1a6ab6c55959f7e436d0e5"), + // account_get_num_procedures + word!("0x53b5ec38b7841948762c258010e6e07ad93963bcaac2d83813f8edb6710dc720"), + // account_get_procedure_root + word!("0x4d7b2e6083820088cd1139ed658b631cf391989b16de8af6741d7e17de9245cd"), // account_was_procedure_called word!("0x34f27a609f2f2b4fec454b17182552b0acc52524e507e134257a1f1ed30a57cd"), // faucet_mint_asset diff --git a/crates/miden-testing/src/mock_chain/auth.rs b/crates/miden-testing/src/mock_chain/auth.rs index 2cc2bf639e..6e379bad37 100644 --- a/crates/miden-testing/src/mock_chain/auth.rs +++ b/crates/miden-testing/src/mock_chain/auth.rs @@ -7,6 +7,7 @@ use miden_lib::account::auth::{ AuthRpoFalcon512Acl, AuthRpoFalcon512AclConfig, AuthRpoFalcon512Multisig, + AuthRpoFalcon512MultisigConfig, }; use miden_lib::testing::account_component::{ConditionalAuthComponent, IncrNonceAuthComponent}; use miden_objects::Word; @@ -25,7 +26,11 @@ pub enum Auth { BasicAuth, /// Multisig - Multisig { threshold: u32, approvers: Vec }, + Multisig { + threshold: u32, + approvers: Vec, + proc_threshold_map: Vec<(Word, u32)>, + }, /// Creates a [SecretKey] for the account, and creates a [BasicAuthenticator] used to /// authenticate the account with [AuthRpoFalcon512Acl]. Authentication will only be @@ -69,11 +74,14 @@ impl Auth { (component, Some(authenticator)) }, - Auth::Multisig { threshold, approvers } => { + Auth::Multisig { threshold, approvers, proc_threshold_map } => { let pub_keys: Vec<_> = approvers.iter().map(|word| PublicKeyCommitment::from(*word)).collect(); - let component = AuthRpoFalcon512Multisig::new(*threshold, pub_keys) + let config = AuthRpoFalcon512MultisigConfig::new(pub_keys, *threshold) + .and_then(|cfg| cfg.with_proc_thresholds(proc_threshold_map.clone())) + .expect("invalid multisig config"); + let component = AuthRpoFalcon512Multisig::new(config) .expect("multisig component creation failed") .into(); diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index cbff48ad82..371efb4088 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -1,5 +1,6 @@ use assert_matches::assert_matches; use miden_lib::account::components::rpo_falcon_512_multisig_library; +use miden_lib::account::interface::AccountInterface; use miden_lib::account::wallets::BasicWallet; use miden_lib::errors::tx_kernel_errors::ERR_TX_ALREADY_EXECUTED; use miden_lib::note::create_p2id_note; @@ -83,11 +84,12 @@ fn create_multisig_account( threshold: u32, public_keys: &[PublicKey], asset_amount: u64, + proc_threshold_map: Vec<(Word, u32)>, ) -> anyhow::Result { let approvers: Vec<_> = public_keys.iter().map(|pk| pk.to_commitment()).collect(); let multisig_account = AccountBuilder::new([0; 32]) - .with_auth_component(Auth::Multisig { threshold, approvers }) + .with_auth_component(Auth::Multisig { threshold, approvers, proc_threshold_map }) .with_component(BasicWallet) .account_type(AccountType::RegularAccountUpdatableCode) .storage_mode(AccountStorageMode::Public) @@ -117,7 +119,8 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { // Create multisig account let multisig_starting_balance = 10u64; - let mut multisig_account = create_multisig_account(2, &public_keys, multisig_starting_balance)?; + let mut multisig_account = + create_multisig_account(2, &public_keys, multisig_starting_balance, vec![])?; let output_note_asset = FungibleAsset::mock(0); @@ -202,7 +205,7 @@ async fn test_multisig_2_of_4_all_signer_combinations() -> anyhow::Result<()> { let (_secret_keys, public_keys, authenticators) = setup_keys_and_authenticators(4, 4)?; // Create multisig account with 4 approvers but threshold of 2 - let multisig_account = create_multisig_account(2, &public_keys, 10)?; + let multisig_account = create_multisig_account(2, &public_keys, 10, vec![])?; let mut mock_chain = MockChainBuilder::with_accounts([multisig_account.clone()]) .unwrap() @@ -280,7 +283,7 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { let (_secret_keys, public_keys, authenticators) = setup_keys_and_authenticators(3, 2)?; // Create 2/3 multisig account - let multisig_account = create_multisig_account(2, &public_keys, 20)?; + let multisig_account = create_multisig_account(2, &public_keys, 20, vec![])?; let mut mock_chain = MockChainBuilder::with_accounts([multisig_account.clone()]) .unwrap() @@ -356,7 +359,7 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { async fn test_multisig_update_signers() -> anyhow::Result<()> { let (_secret_keys, public_keys, authenticators) = setup_keys_and_authenticators(2, 2)?; - let multisig_account = create_multisig_account(2, &public_keys, 10)?; + let multisig_account = create_multisig_account(2, &public_keys, 10, vec![])?; // SECTION 1: Execute a transaction script to update signers and threshold // ================================================================================ @@ -626,7 +629,7 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { // Setup 5 original owners with threshold 4 let (_secret_keys, public_keys, authenticators) = setup_keys_and_authenticators(5, 5)?; - let multisig_account = create_multisig_account(4, &public_keys, 10)?; + let multisig_account = create_multisig_account(4, &public_keys, 10, vec![])?; // Build mock chain let mock_chain_builder = MockChainBuilder::with_accounts([multisig_account.clone()]).unwrap(); @@ -797,7 +800,7 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu let (_secret_keys, public_keys, _authenticators) = setup_keys_and_authenticators(2, 2)?; - let multisig_account = create_multisig_account(2, &public_keys, 10)?; + let multisig_account = create_multisig_account(2, &public_keys, 10, vec![])?; let mock_chain = MockChainBuilder::with_accounts([multisig_account.clone()]) .unwrap() @@ -919,7 +922,7 @@ async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { let (_secret_keys, public_keys, authenticators) = setup_keys_and_authenticators(2, 2)?; // Create multisig account - let multisig_account = create_multisig_account(2, &public_keys, 10)?; + let multisig_account = create_multisig_account(2, &public_keys, 10, vec![])?; let mut mock_chain_builder = MockChainBuilder::with_accounts([multisig_account.clone()]).unwrap(); @@ -930,7 +933,6 @@ async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { &[FungibleAsset::mock(1)], NoteType::Public, )?; - let mock_chain = mock_chain_builder.build().unwrap(); let salt = Word::from([Felt::new(1); 4]); @@ -969,7 +971,6 @@ async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, error => panic!("expected abort with tx effects: {error:?}"), }; - // Get signatures from both approvers let msg = tx_summary.as_ref().to_commitment(); let tx_summary = SigningInputs::TransactionSummary(tx_summary); @@ -1011,3 +1012,164 @@ async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { Ok(()) } + +/// Tests that 1-of-2 approvers can consume a note but 2-of-2 are required to send a note. +/// +/// This test verifies that a multisig account with 2 approvers and threshold 2, but a procedure +/// threshold of 1 for note consumption, can: +/// 1. Consume a note when only one approver signs the transaction +/// 2. Send a note only when both approvers sign the transaction (default threshold) +#[tokio::test] +async fn test_multisig_proc_threshold_overrides() -> anyhow::Result<()> { + // Setup keys and authenticators + let (_secret_keys, public_keys, authenticators) = setup_keys_and_authenticators(2, 2)?; + + let proc_threshold_map = vec![(BasicWallet::receive_asset_digest(), 1)]; + + // Create multisig account + let multisig_starting_balance = 10u64; + let mut multisig_account = + create_multisig_account(2, &public_keys, multisig_starting_balance, proc_threshold_map)?; + + // SECTION 1: Test note consumption with 1 signature + // ================================================================================ + + // 1. create a mock note from some random account + let mut mock_chain_builder = + MockChainBuilder::with_accounts([multisig_account.clone()]).unwrap(); + + let note = mock_chain_builder.add_p2id_note( + multisig_account.id(), + multisig_account.id(), + &[FungibleAsset::mock(1)], + NoteType::Public, + )?; + + let mut mock_chain = mock_chain_builder.build()?; + + // 2. consume without signatures + let salt = Word::from([Felt::new(1); 4]); + let tx_context = mock_chain + .build_tx_context(multisig_account.id(), &[note.id()], &[])? + .auth_args(salt) + .build()?; + + let tx_summary = match tx_context.execute().await.unwrap_err() { + TransactionExecutorError::Unauthorized(tx_summary) => tx_summary, + error => panic!("expected abort with tx summary: {error:?}"), + }; + + // 3. get signature from one approver + let msg = tx_summary.as_ref().to_commitment(); + let tx_summary_signing = SigningInputs::TransactionSummary(tx_summary.clone()); + let sig = authenticators[0] + .get_signature(public_keys[0].to_commitment().into(), &tx_summary_signing) + .await?; + + // 4. execute with signature + let tx_result = mock_chain + .build_tx_context(multisig_account.id(), &[note.id()], &[])? + .add_signature(public_keys[0].clone().into(), msg, sig) + .auth_args(salt) + .build()? + .execute() + .await; + + assert!(tx_result.is_ok(), "Note consumption with 1 signature should succeed"); + + // Apply the transaction to the account + multisig_account.apply_delta(tx_result.as_ref().unwrap().account_delta())?; + mock_chain.add_pending_executed_transaction(&tx_result.unwrap())?; + mock_chain.prove_next_block()?; + + // SECTION 2: Test note sending requires 2 signatures + // ================================================================================ + + let salt2 = Word::from([Felt::new(2); 4]); + + // Create output note to send 5 units from the account + let output_note = create_p2id_note( + multisig_account.id(), + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE.try_into().unwrap(), + vec![FungibleAsset::mock(5)], + NoteType::Public, + Default::default(), + &mut RpoRandomCoin::new(Word::from([Felt::new(42); 4])), + )?; + let multisig_account_interface = AccountInterface::from(&multisig_account); + let send_note_transaction_script = multisig_account_interface.build_send_notes_script( + &[output_note.clone().into()], + None, + false, + )?; + + // Execute transaction without signatures to get tx summary + let tx_context_init = mock_chain + .build_tx_context(multisig_account.id(), &[], &[])? + .extend_expected_output_notes(vec![OutputNote::Full(output_note.clone())]) + .tx_script(send_note_transaction_script.clone()) + .auth_args(salt2) + .build()?; + + let tx_summary2 = match tx_context_init.execute().await.unwrap_err() { + TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, + error => panic!("expected abort with tx effects: {error:?}"), + }; + // Get signature from only ONE approver + let msg2 = tx_summary2.as_ref().to_commitment(); + let tx_summary2_signing = SigningInputs::TransactionSummary(tx_summary2.clone()); + + let sig_1 = authenticators[0] + .get_signature(public_keys[0].to_commitment().into(), &tx_summary2_signing) + .await?; + + // Try to execute with only 1 signature - should FAIL + let tx_context_one_sig = mock_chain + .build_tx_context(multisig_account.id(), &[], &[])? + .extend_expected_output_notes(vec![OutputNote::Full(output_note.clone())]) + .add_signature(public_keys[0].clone().into(), msg2, sig_1) + .tx_script(send_note_transaction_script.clone()) + .auth_args(salt2) + .build()?; + + let result = tx_context_one_sig.execute().await; + match result { + Err(TransactionExecutorError::Unauthorized(_)) => { + // Expected: transaction should fail with insufficient signatures + }, + _ => panic!( + "Transaction should fail with Unauthorized error when only 1 signature provided for note sending" + ), + } + + // Now get signatures from BOTH approvers + let sig_1 = authenticators[0] + .get_signature(public_keys[0].to_commitment().into(), &tx_summary2_signing) + .await?; + let sig_2 = authenticators[1] + .get_signature(public_keys[1].to_commitment().into(), &tx_summary2_signing) + .await?; + + // Execute with 2 signatures - should SUCCEED + let result = mock_chain + .build_tx_context(multisig_account.id(), &[], &[])? + .extend_expected_output_notes(vec![OutputNote::Full(output_note)]) + .add_signature(public_keys[0].clone().into(), msg2, sig_1) + .add_signature(public_keys[1].clone().into(), msg2, sig_2) + .auth_args(salt2) + .tx_script(send_note_transaction_script) + .build()? + .execute() + .await; + + assert!(result.is_ok(), "Transaction should succeed with 2 signatures for note sending"); + + // Apply the transaction to the account + multisig_account.apply_delta(result.as_ref().unwrap().account_delta())?; + mock_chain.add_pending_executed_transaction(&result.unwrap())?; + mock_chain.prove_next_block()?; + + assert_eq!(multisig_account.vault().get_balance(FungibleAsset::mock_issuer())?, 6); + + Ok(()) +} diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index f792c10a36..83f92f9955 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -52,6 +52,8 @@ Account procedures can be used to read and write to account storage, add or remo | `get_initial_vault_root` | Returns the vault root of the native account at the beginning of the transaction.

Inputs: `[]`
Outputs: `[INIT_VAULT_ROOT]` | Any | | `get_vault_root` | Returns the vault root of the current account.

Inputs: `[]`
Outputs: `[VAULT_ROOT]` | Any | | `was_procedure_called` | Returns 1 if a procedure was called during transaction execution, and 0 otherwise.

Inputs: `[PROC_ROOT]`
Outputs: `[was_called]` | Any | +| `get_num_procedures` | Returns the number of procedures in the current account.

Inputs: `[]`
Outputs: `[num_procedures]` | Any | +| `get_procedure_root` | Returns the procedure root for the procedure at the specified index.

Inputs: `[index]`
Outputs: `[PROC_ROOT]` | Any | ## Active Note Procedures (`miden::active_note`) From f23bf31ffff5973969375cd5c379cb9815aa27ab Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 20 Oct 2025 11:01:38 +0200 Subject: [PATCH 095/133] feat: assert nonce is non-zero after the auth procedure (#1982) * feat: assert nonce is non zero after auth proc * chore: update changelog * feat: add test for zero starting nonce * Update crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm Co-authored-by: Philipp Gackstatter * chore: rebuild --------- Co-authored-by: Philipp Gackstatter --- CHANGELOG.md | 1 + .../asm/kernels/transaction/lib/epilogue.masm | 7 +++++++ .../miden-lib/src/errors/tx_kernel_errors.rs | 2 ++ .../src/kernel_tests/tx/test_epilogue.rs | 20 +++++++++++++++++++ 4 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 701234fbdf..d478810157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ - [BREAKING] Rename `TransactionInputs` to `TransactionExecutionInputs` and make a new `TransactionInputs` struct which does not contain `InputNotes` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). - [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). - [BREAKING] Refactor `TransactionInputs` and remove `TransactionWitness` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). +- [BREAKING] Assert nonce is non-zero after the auth procedure ([#1982](https://github.com/0xMiden/miden-base/pull/1982)). ## 0.11.5 (2025-10-02) diff --git a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm index bc98d2cd10..fa57015450 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -16,6 +16,8 @@ const.ERR_EPILOGUE_EXECUTED_TRANSACTION_IS_EMPTY="executed transaction neither c const.ERR_AUTH_PROCEDURE_CALLED_FROM_WRONG_CONTEXT="auth procedure had been called from outside the epilogue" +const.ERR_EPILOGUE_NONCE_CANNOT_BE_0="nonce cannot be 0 after an account-creating transaction" + # CONSTANTS # ================================================================================================= @@ -384,6 +386,11 @@ export.finalize_transaction.12 exec.execute_auth_procedure # => [] + # nonce cannot be 0 after the first transaction + exec.memory::get_native_account_nonce + # => [nonce] + eq.0 assertz.err=ERR_EPILOGUE_NONCE_CANNOT_BE_0 + # ------ Assert assets are preserved ------ # build the output vault representing the assets that were in the account at the end of the diff --git a/crates/miden-lib/src/errors/tx_kernel_errors.rs b/crates/miden-lib/src/errors/tx_kernel_errors.rs index 5c16058e08..290947b265 100644 --- a/crates/miden-lib/src/errors/tx_kernel_errors.rs +++ b/crates/miden-lib/src/errors/tx_kernel_errors.rs @@ -63,6 +63,8 @@ pub const ERR_ACCOUNT_TOO_MANY_STORAGE_SLOTS: MasmError = MasmError::from_static /// Error Message: "executed transaction neither changed the account state, nor consumed any notes" pub const ERR_EPILOGUE_EXECUTED_TRANSACTION_IS_EMPTY: MasmError = MasmError::from_static_str("executed transaction neither changed the account state, nor consumed any notes"); +/// Error Message: "nonce cannot be 0 after an account-creating transaction" +pub const ERR_EPILOGUE_NONCE_CANNOT_BE_0: MasmError = MasmError::from_static_str("nonce cannot be 0 after an account-creating transaction"); /// Error Message: "total number of assets in the account and all involved notes must stay the same" pub const ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME: MasmError = MasmError::from_static_str("total number of assets in the account and all involved notes must stay the same"); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 14c8d15a11..32ee7ff22d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -4,6 +4,7 @@ use alloc::vec::Vec; use miden_lib::errors::tx_kernel_errors::{ ERR_ACCOUNT_DELTA_NONCE_MUST_BE_INCREMENTED_IF_VAULT_OR_STORAGE_CHANGED, ERR_EPILOGUE_EXECUTED_TRANSACTION_IS_EMPTY, + ERR_EPILOGUE_NONCE_CANNOT_BE_0, ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME, ERR_TX_INVALID_EXPIRATION_DELTA, }; @@ -484,6 +485,25 @@ async fn epilogue_fails_on_account_state_change_without_nonce_increment() -> any Ok(()) } +#[tokio::test] +async fn epilogue_fails_when_nonce_not_incremented() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let account = builder.create_new_mock_account(Auth::Noop)?; + + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + let result = mock_chain + .build_tx_context(TxContextInput::Account(account), &[], &[])? + .build()? + .execute() + .await; + + assert_transaction_executor_error!(result, ERR_EPILOGUE_NONCE_CANNOT_BE_0); + + Ok(()) +} + #[tokio::test] async fn test_epilogue_execute_empty_transaction() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_noop_auth_account().build()?; From 289c2b300104b77bf14d82b13e9f9b8aaff01b63 Mon Sep 17 00:00:00 2001 From: keinberger Date: Mon, 20 Oct 2025 17:44:15 +0300 Subject: [PATCH 096/133] chore: migrate docs from mdbook to docusaurus - Migrated documentation from mdbook to docusaurus - Updated protocol_library.md with detailed formatting and resolved merge conflicts - Added proper markdown links and structure - Incorporated new procedures from next branch - Added build and deployment workflows for docs --- .github/workflows/book.yml | 75 - .github/workflows/trigger-deploy-docs.yml | 21 + docs/.gitignore | 4 +- docs/book.toml | 17 - docs/custom.css | 3 - docs/docusaurus.config.ts | 31 + docs/package-lock.json | 19469 ++++++++++++++++ docs/package.json | 15 + docs/sidebars.ts | 13 + docs/src/EXPORTED.md | 17 - docs/src/SUMMARY.md | 18 - docs/src/_category_.json | 5 + docs/src/account/_category_.json | 5 + docs/src/account/address.md | 31 +- docs/src/account/code.md | 19 +- .../account/{component.md => components.md} | 13 +- docs/src/account/id.md | 26 +- docs/src/account/{overview.md => index.md} | 28 +- docs/src/account/storage.md | 14 +- docs/src/asset.md | 37 +- docs/src/blockchain.md | 34 +- docs/src/fees.md | 5 + docs/src/index.md | 29 +- docs/src/note.md | 49 +- docs/src/protocol_library.md | 182 +- docs/src/state.md | 40 +- docs/src/transaction.md | 66 +- docs/tsconfig.json | 8 + 28 files changed, 19900 insertions(+), 374 deletions(-) delete mode 100644 .github/workflows/book.yml create mode 100644 .github/workflows/trigger-deploy-docs.yml delete mode 100644 docs/book.toml delete mode 100644 docs/custom.css create mode 100644 docs/docusaurus.config.ts create mode 100644 docs/package-lock.json create mode 100644 docs/package.json create mode 100644 docs/sidebars.ts delete mode 100644 docs/src/EXPORTED.md delete mode 100644 docs/src/SUMMARY.md create mode 100644 docs/src/_category_.json create mode 100644 docs/src/account/_category_.json rename docs/src/account/{component.md => components.md} (96%) rename docs/src/account/{overview.md => index.md} (82%) create mode 100644 docs/tsconfig.json diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml deleted file mode 100644 index 1cdd372b45..0000000000 --- a/.github/workflows/book.yml +++ /dev/null @@ -1,75 +0,0 @@ -# Performs checks, builds and deploys the documentation mdbook. -# -# Flow is based on the official github and mdbook documentation: -# -# https://github.com/actions/starter-workflows/blob/main/pages/mdbook.yml -# https://github.com/rust-lang/mdBook/wiki/Automated-Deployment%3A-GitHub-Actions - -name: book - -# Documentation should be built and tested on every pull-request, and additionally deployed on push onto next. -on: - workflow_dispatch: - pull_request: - path: ['docs/**'] - push: - branches: [next] - path: ['docs/**'] - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - # Always build and test the mdbook documentation whenever the docs folder is changed. - # - # The documentation is uploaded as a github artifact IFF it is required for deployment i.e. on push into next. - build: - name: Build documentation - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@main - - # Installation from source takes a fair while, so we install the binaries directly instead. - - name: Install mdbook and plugins - uses: taiki-e/install-action@v2 - with: - tool: mdbook, mdbook-linkcheck, mdbook-alerts, mdbook-katex - - - name: Build book - run: mdbook build docs/ - - # Only Upload documentation if we want to deploy (i.e. push to next). - - name: Setup Pages - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} - id: pages - uses: actions/configure-pages@v5 - - - name: Upload book artifact - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} - uses: actions/upload-pages-artifact@v3 - with: - # We specify multiple [output] sections in our book.toml which causes mdbook to create separate folders for each. This moves the generated `html` into its own `html` subdirectory. - path: ./docs/book/html - - # Deployment job only runs on push to next. - deploy: - name: Deploy documentation - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/trigger-deploy-docs.yml b/.github/workflows/trigger-deploy-docs.yml new file mode 100644 index 0000000000..d6b795817e --- /dev/null +++ b/.github/workflows/trigger-deploy-docs.yml @@ -0,0 +1,21 @@ +name: Trigger Aggregator Docs Rebuild + +on: + push: + branches: [next] + +jobs: + notify: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Send repository_dispatch to aggregator + uses: peter-evans/repository-dispatch@v3 + with: + # PAT that can access the central aggregator repository + token: ${{ secrets.DOCS_REPO_TOKEN }} + repository: ${{ vars.DOCS_AGGREGATOR_REPO }} + event-type: rebuild diff --git a/docs/.gitignore b/docs/.gitignore index 7585238efe..e4068b5b4b 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,3 @@ -book +.docusaurus/ +build/ +node_modules/ diff --git a/docs/book.toml b/docs/book.toml deleted file mode 100644 index fa063417bd..0000000000 --- a/docs/book.toml +++ /dev/null @@ -1,17 +0,0 @@ -[book] -authors = ["Miden contributors"] -description = "Description and core structures for the Miden protocol" -language = "en" -multilingual = false -title = "The Miden protocol" - -[output.html] -additional-css = ["custom.css"] -git-repository-url = "https://github.com/0xMiden/miden-base" - -[preprocessor.katex] -after = ["links"] - -[preprocessor.alerts] - -[output.linkcheck] diff --git a/docs/custom.css b/docs/custom.css deleted file mode 100644 index 3f6f556d6c..0000000000 --- a/docs/custom.css +++ /dev/null @@ -1,3 +0,0 @@ -:root { - --content-max-width: 1000px; -} diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts new file mode 100644 index 0000000000..ba607d3613 --- /dev/null +++ b/docs/docusaurus.config.ts @@ -0,0 +1,31 @@ +import type { Config } from "@docusaurus/types"; + +// If your content lives in docs/src, set DOCS_PATH='src'; else '.' +const DOCS_PATH = + process.env.DOCS_PATH || (require("fs").existsSync("src") ? "src" : "."); + +const config: Config = { + title: "Docs Dev Preview", + url: "http://localhost:3000", + baseUrl: "/", + trailingSlash: false, + + // Minimal classic preset: docs only, autogenerated sidebars, same math plugins as prod + presets: [ + [ + "classic", + { + docs: { + path: DOCS_PATH, // '../docs' is implied because we are already inside docs/ + routeBasePath: "/", // mount docs at root for quick preview + sidebarPath: "./sidebars.ts", + remarkPlugins: [require("remark-math")], + rehypePlugins: [require("rehype-katex")], + }, + blog: false, + pages: false, + }, + ], + ], +}; +export default config; diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 0000000000..2bedc86c6b --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,19469 @@ +{ + "name": "@miden/docs-dev", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@miden/docs-dev", + "devDependencies": { + "@docusaurus/core": "^3", + "@docusaurus/preset-classic": "^3", + "rehype-katex": "^7", + "remark-math": "^6" + } + }, + "node_modules/@ai-sdk/gateway": { + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-1.0.30.tgz", + "integrity": "sha512-QdrSUryr/CLcsCISokLHOImcHj1adGXk1yy4B3qipqLhcNc33Kj/O/3crI790Qp85oDx7sc4vm7R4raf9RA/kg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", + "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.10.tgz", + "integrity": "sha512-T1gZ76gEIwffep6MWI0QNy9jgoybUHE7TRaHB5k54K8mF91ciGFlbtCGxDYhMH3nCRergKwYFIDeFF0hJSIQHQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/react": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.54.tgz", + "integrity": "sha512-wbszephe+WR7y8DXwYN/SMr56pwU1N505/h3fOTz4NPHCW0sex6LzZ7DuhFR0J6ij3n3sLA11aEliFEa6B2FiA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "3.0.10", + "ai": "5.0.54", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.25.76 || ^4.1.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.4.0.tgz", + "integrity": "sha512-N0blWT/C0KOZ/OJ9GXBX66odJZlrYjMj3M+01y8ob1mjBFnBaBo7gOCyHBDQy60+H4pJXp3pSGlJOqJIueBH+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", + "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", + "@algolia/autocomplete-shared": "1.19.2" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", + "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.2" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", + "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.38.0.tgz", + "integrity": "sha512-15d6zv8vtj2l9pnnp/EH7Rhq3/snCCHRz56NnX6xIUPrbJl5gCsIYXAz8C2IEkwOpoDb0r5G6ArY2gKdVMNezw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.38.0.tgz", + "integrity": "sha512-jJIbYAhYvTG3+gEAP5Q5Dp6PFJfUR+atz5rsqm5KjAKK+faLFdHJbM2IbOo0xdyGd+SH259MzfQKLJ9mZZ27dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.38.0.tgz", + "integrity": "sha512-aMCXzVPGJTeQnVU3Sdf30TfMN2+QyWcjfPTCCHyqVVgjPipb6RnK40aISGoO+rlYjh9LunDsNVFLwv+JEIF8bQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.38.0.tgz", + "integrity": "sha512-4c3FbpMiJX+VcaAj0rYaQdTLS/CkrdOn4hW+5y1plPov7KC7iSHai/VBbirmHuAfW1hVPCIh1w/4erKKTKuo+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.38.0.tgz", + "integrity": "sha512-FzLs6c8TBL4FSgNfnH2NL7O33ktecGiaKO4ZFG51QYORUzD5d6YwB9UBteaIYu/sgFoEdY57diYU4vyBH8R6iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.38.0.tgz", + "integrity": "sha512-7apiahlgZLvOqrh0+hAYAp/UWjqz6AfSJrCwnsoQNzgIT09dLSPIKREelkuQeUrKy38vHWWpSQE3M0zWSp/YrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.38.0.tgz", + "integrity": "sha512-PTAFMJOpVtJweExEYYgdmSCC6n4V/R+ctDL3fRQy77ulZM/p+zMLIQC9c7HCQE1zqpauvVck3f2zYSejaUTtrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/ingestion": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.38.0.tgz", + "integrity": "sha512-qGSUGgceJHGyJLZ06bFLwVe2Tpf9KwabmoBjFvFscVmMmU5scKya6voCYd9bdX7V0Xy1qya9MGbmTm4zlLuveQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.38.0.tgz", + "integrity": "sha512-VnCtAUcHirvv/dDHg9jK1Z5oo4QOC5FKDxe40x8qloru2qDcjueT34jiAsB0gRos3VWf9v4iPSYTqMIFOcADpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.38.0.tgz", + "integrity": "sha512-fqgeU9GqxQorFUeGP4et1MyY28ccf9PCeciHwDPSbPYYiTqBItHdUIiytsNpjC5Dnc0RWtuXWCltLwSw9wN/bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.38.0.tgz", + "integrity": "sha512-nAUKbv4YQIXbpPi02AQvSPisD5FDDbT8XeYSh9HFoYP0Z3IpBLLDg7R4ahPvzd7gGsVKgEbXzRPWESXSji5yIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.38.0.tgz", + "integrity": "sha512-bkuAHaadC6OxJd3SVyQQnU1oJ9G/zdCqua7fwr1tJDrA/v7KzeS5np4/m6BuRUpTgVgFZHSewGnMcgj9DLBoaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.38.0.tgz", + "integrity": "sha512-yHDKZTnMPR3/4bY0CVC1/uRnnbAaJ+pctRuX7G/HflBkKOrnUBDEGtQQHzEfMz2FHZ/tbCL+Q9r6mvwTSGp8nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz", + "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", + "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", + "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", + "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.43.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", + "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/postcss-alpha-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", + "integrity": "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", + "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", + "integrity": "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-function-display-p3-linear": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", + "integrity": "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", + "integrity": "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", + "integrity": "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", + "integrity": "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-contrast-color-function": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", + "integrity": "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-exponential-functions": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", + "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", + "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", + "integrity": "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", + "integrity": "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", + "integrity": "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-initial": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", + "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", + "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", + "integrity": "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", + "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", + "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", + "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", + "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", + "integrity": "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", + "integrity": "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", + "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", + "integrity": "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", + "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", + "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", + "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", + "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", + "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.1.0.tgz", + "integrity": "sha512-nuNKGjHj/FQeWgE9t+i83QD/V67QiaAmGY7xS9TVCRUiCqSljOgIKlsLoQZKKVwEG8f+OWKdznzZkJxGZ7d06A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/react": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.1.0.tgz", + "integrity": "sha512-4GHI7TT3sJZ2Vs4Kjadv7vAkMrTsJqHvzvxO3JA7UT8iPRKaDottG5o5uNshPWhVVaBYPC35Ukf8bfCotGpjSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ai-sdk/react": "^2.0.30", + "@algolia/autocomplete-core": "1.19.2", + "@docsearch/css": "4.1.0", + "ai": "^5.0.30", + "algoliasearch": "^5.28.0", + "marked": "^16.3.0", + "zod": "^4.1.8" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@docusaurus/babel": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.9.0.tgz", + "integrity": "sha512-QcZ+Rey0OvlLK9SPN4/+VWL+ut/tuADVdunA1fmC96fytdYjatdJrcw1koYdp/c+3k6lVYlwg9DDVNDecyLCAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/runtime-corejs3": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.9.0", + "@docusaurus/utils": "3.9.0", + "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/bundler": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.9.0.tgz", + "integrity": "sha512-HaRLSmiwnJQ3uHBV3rd/BRDM9S/nHAshRk54djRZ+RX9ze4ONuFAovdD5es20ZDj7PRTjo38GVnBtHvuL/SwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.9.0", + "@docusaurus/cssnano-preset": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.3", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.11.0", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.2", + "null-loader": "^4.0.1", + "postcss": "^8.5.4", + "postcss-loader": "^7.3.4", + "postcss-preset-env": "^10.2.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^6.0.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.0.tgz", + "integrity": "sha512-sEJ4MW/zuh1MfPORCRbSwnW/PjsVmOigWwBU6clcxm221/CNdnI/XqgfBrl2jj/zocSdNoQM8E3IP2W8dygi6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/babel": "3.9.0", + "@docusaurus/bundler": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "core-js": "^3.31.1", + "detect-port": "^1.5.1", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "execa": "5.1.1", + "fs-extra": "^11.1.1", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.6.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "open": "^8.4.0", + "p-map": "^4.0.0", + "prompts": "^2.4.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.1", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.6", + "tinypool": "^1.0.2", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^5.2.2", + "webpack-merge": "^6.0.1" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@mdx-js/react": "^3.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/cssnano-preset": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.0.tgz", + "integrity": "sha512-prCJXUcoJZBlovJzSFkfnfWr1gXd53VZfE+17fIpUWS6Zioc7WE4FPoXPi5ldAGZ8brhXre5xQ8NWDE90XP9yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.5.4", + "postcss-sort-media-queries": "^5.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/logger": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.9.0.tgz", + "integrity": "sha512-lDtThsocWTF8ZrVF01ltfctA/xgtD/3oXWqEkKIDzF4fCWsWXH7hC4LCqT23xSuxZTIo8N+y02XSPvA/8DLInw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/mdx-loader": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.9.0.tgz", + "integrity": "sha512-9bfJYdkZFE+REwevkT4CYdTJ2f6ydgkbUFylkzTXrNGtBXtx25TRJGdn2cVzm3eVkeWdJrGkG/ypwrIWnbu5UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "@mdx-js/mdx": "^3.0.0", + "@slorber/remark-comment": "^1.0.0", + "escape-html": "^1.0.3", + "estree-util-value-to-estree": "^3.0.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "image-size": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-raw": "^7.0.0", + "remark-directive": "^3.0.0", + "remark-emoji": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "stringify-object": "^3.3.0", + "tslib": "^2.6.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", + "vfile": "^6.0.1", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/module-type-aliases": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.0.tgz", + "integrity": "sha512-0ucYr79FpTCebN+l3ZlKqoW7HbMqSKT8JdsEg6QoUtxD3C7trF6KZiK/X6Yh+xekO1w3zzXYgPcIYTF2DV81tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/types": "3.9.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@docusaurus/plugin-content-blog": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.0.tgz", + "integrity": "sha512-XZXJ/rQgi2jT0XWNXOnSKooJgtGHPzkjaBjww6K9PD+YevNMTP9U8Y5sA7cLA5Bwuqrpee4i8NO3tSrjhhDW5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/theme-common": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "cheerio": "1.0.0-rc.12", + "feed": "^4.2.2", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "srcset": "^4.0.0", + "tslib": "^2.6.0", + "unist-util-visit": "^5.0.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-docs": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.0.tgz", + "integrity": "sha512-PP+iDJg+lj4cn/7GbbmiguaQ8OX08YxnzQ17KqRC4ufJm11jdyXD33wA7vVtbeG/BkkgkiB/K7YyPHCPwmfVhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/module-type-aliases": "3.9.0", + "@docusaurus/theme-common": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "@types/react-router-config": "^5.0.7", + "combine-promises": "^1.1.0", + "fs-extra": "^11.1.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "tslib": "^2.6.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.0.tgz", + "integrity": "sha512-ngetCpAZuivlaHC0l8a5KoK6PQWGuZ8742VwK7dbXeIW0Y70P4xwuocBdsCIQ9J6nB9rlTXRYMpNVyYyCpD7/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-css-cascade-layers": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.0.tgz", + "integrity": "sha512-giPTCjEzeaamMn8EHY/oDvsPDxF5ei1/q5lPUFQLldbc65jFQ1k6pPwKjtOznYy3TSfClCF1F1DNpYWIx7B5LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/plugin-debug": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.9.0.tgz", + "integrity": "sha512-DuFOZya+bcrYiL54qBEn2rdKuoWWNyOV5IoHI2MURLzwuYaKu/J9Gi618XUsj3N3qvqG2uxWQiXZcs9ecfMadA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "fs-extra": "^11.1.1", + "react-json-view-lite": "^2.3.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-analytics": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.0.tgz", + "integrity": "sha512-mUXvpasTDR2pXdnkkhGxEgB9frVAvLGc+T3fp6SGT2F+YoEQtjcmz9D43zubQawLn+W1KEhoj+vPusYe+HAl+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-gtag": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.0.tgz", + "integrity": "sha512-L4tCKYnmcyLV6VQs7XWQ3r7YSllagAU2GylZzdvz7NRMcXE12uSW5MCC2aSltbk09MYlqrYv1Ntp+ESsMvptYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "@types/gtag.js": "^0.0.12", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-tag-manager": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.0.tgz", + "integrity": "sha512-+jWO3tkrvsMUKQ69KTIj9ZBf8sKY5kodLcP4yIaEkPzfWq9IEpE+ekQCtFWlrAmkJUtSxbjHK6HNZZkUNwwq7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-sitemap": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.0.tgz", + "integrity": "sha512-QOyLooWuF+On4q2RDGVZtKY0tlfdZwD9e/p7g1sJLUfOwN518V2Bo+kZtU82Or42SCKjyJ0lhSqAUOZfbeFhFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "fs-extra": "^11.1.1", + "sitemap": "^7.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-svgr": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.0.tgz", + "integrity": "sha512-pUZIfnhFtAYDmDwimFiBY+sxNUigyJnKbCwI9pTiXr3uGp43CsSsN8gwl/i8jBmqfsZzvNnGZNxc75wy9v6RXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "@svgr/core": "8.1.0", + "@svgr/webpack": "^8.1.0", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/preset-classic": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.9.0.tgz", + "integrity": "sha512-nLoiDxf8bDNNxDSZ28+pFfSfT+QRi08Pn2K0zIvbjkM/X/otMs4ho0K8+2FpoLOoGApifaSuNfJXpGYnQV3rGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/plugin-content-blog": "3.9.0", + "@docusaurus/plugin-content-docs": "3.9.0", + "@docusaurus/plugin-content-pages": "3.9.0", + "@docusaurus/plugin-css-cascade-layers": "3.9.0", + "@docusaurus/plugin-debug": "3.9.0", + "@docusaurus/plugin-google-analytics": "3.9.0", + "@docusaurus/plugin-google-gtag": "3.9.0", + "@docusaurus/plugin-google-tag-manager": "3.9.0", + "@docusaurus/plugin-sitemap": "3.9.0", + "@docusaurus/plugin-svgr": "3.9.0", + "@docusaurus/theme-classic": "3.9.0", + "@docusaurus/theme-common": "3.9.0", + "@docusaurus/theme-search-algolia": "3.9.0", + "@docusaurus/types": "3.9.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-classic": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.9.0.tgz", + "integrity": "sha512-RToUIabJOyX41nMIxkFn8LPeA+uHgySzyd6Ak/gsINqWHHTLDMoYPxBUmNm3S+okcfuMI54kNYvD6TY+6TIYDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/module-type-aliases": "3.9.0", + "@docusaurus/plugin-content-blog": "3.9.0", + "@docusaurus/plugin-content-docs": "3.9.0", + "@docusaurus/plugin-content-pages": "3.9.0", + "@docusaurus/theme-common": "3.9.0", + "@docusaurus/theme-translations": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "infima": "0.2.0-alpha.45", + "lodash": "^4.17.21", + "nprogress": "^0.2.0", + "postcss": "^8.5.4", + "prism-react-renderer": "^2.3.0", + "prismjs": "^1.29.0", + "react-router-dom": "^5.3.4", + "rtlcss": "^4.1.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-common": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.0.tgz", + "integrity": "sha512-pqNoQgttIpk7Ndm6N8OGbhi+1wBIQXQPYM7bPf1HDraXfvVpOzhcDty1yyK4coPWl0M7NxednZvKw4atfQ70Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/module-type-aliases": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "clsx": "^2.0.0", + "parse-numeric-range": "^1.3.0", + "prism-react-renderer": "^2.3.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-search-algolia": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.0.tgz", + "integrity": "sha512-nbY7ZJVA10kTiBLJtscxK1aECeYvYFz+Sno9PkCE9KeFXqRDr6omtNmLVkbvyl4b6xgz+6yOIBdO/idLPVDpWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "^3.9.0 || ^4.1.0", + "@docusaurus/core": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/plugin-content-docs": "3.9.0", + "@docusaurus/theme-common": "3.9.0", + "@docusaurus/theme-translations": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "algoliasearch": "^5.37.0", + "algoliasearch-helper": "^3.26.0", + "clsx": "^2.0.0", + "eta": "^2.2.0", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-translations": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.9.0.tgz", + "integrity": "sha512-4HUELBsE+rhtlnR1MsaNB9nJXPFZANeDQa5If1GfFVlis5mWUfdmXmbGangR7PfpK2tc56UETMtzjKrX5L5UWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/types": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.0.tgz", + "integrity": "sha512-0klJLhHFHqkYoxIVp1LD7dnU1ASRTfSX+HFDiELOdz+YQUkOSfuU5hDa46zD8bLxrYffCb8FtJI7Z6BWAmVodg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/mdast": "^4.0.2", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.95.0", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/types/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docusaurus/utils": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.9.0.tgz", + "integrity": "sha512-wpVRQbDhXxqbb1llhkpu++aD4UdHHQ5M7J8DmJELDphlwmpI44TdS5elQZOsjzPfGyITZyQLekcDXjyteJ0/bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "escape-string-regexp": "^4.0.0", + "execa": "5.1.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "github-slugger": "^1.5.0", + "globby": "^11.1.0", + "gray-matter": "^4.0.3", + "jiti": "^1.20.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "micromatch": "^4.0.5", + "p-queue": "^6.6.2", + "prompts": "^2.4.2", + "resolve-pathname": "^3.0.0", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/utils-common": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.9.0.tgz", + "integrity": "sha512-zpmzRn2mniMnrx8ZEYyyDsr0/7EksVgUXL9IuODp0DSK+R19nDGCY7w2NaMGRmGnrQQKsT3t0NDZzBk0V6N9Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/types": "3.9.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/utils-validation": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.9.0.tgz", + "integrity": "sha512-xpVLdFPpsE5dYuE7hOtghccCrRWRhM6tUQ4YpfSy5snCDWgROITG5Mj22fGstd/HBqTzKD8NFs7qPPs42qjgWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "fs-extra": "^11.2.0", + "joi": "^17.9.2", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.0.0.tgz", + "integrity": "sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.14.0.tgz", + "integrity": "sha512-LpWbYgVnKzphN5S6uss4M25jJ/9+m6q6UJoeN6zTkK4xAGhKsiBRPVeF7OYMWonn5repMQbE5vieRXcMUrKDKw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.1", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", + "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", + "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@slorber/remark-comment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", + "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.1.0", + "micromark-util-symbol": "^1.0.1" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/gtag.js": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz", + "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.16", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", + "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-config": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", + "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "^5.1.0" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ai": { + "version": "5.0.54", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.54.tgz", + "integrity": "sha512-eM3EH4VVCWRMfs17r8HF8RtCN/+vBdpWOQoHSVooIfB0BZerOHyrktrVoDP6G6xatUzGLTvJT3rMKLkbPTLPBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/gateway": "1.0.30", + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.10", + "@opentelemetry/api": "1.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/algoliasearch": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.38.0.tgz", + "integrity": "sha512-8VJKIzheeI9cjuVJhU1hYEVetOTe7LvA+CujAI7yqvYsPtZfVEvv1pg9AeFNtHBg/ZoSLGU5LPijhcY5l3Ea9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.4.0", + "@algolia/client-abtesting": "5.38.0", + "@algolia/client-analytics": "5.38.0", + "@algolia/client-common": "5.38.0", + "@algolia/client-insights": "5.38.0", + "@algolia/client-personalization": "5.38.0", + "@algolia/client-query-suggestions": "5.38.0", + "@algolia/client-search": "5.38.0", + "@algolia/ingestion": "1.38.0", + "@algolia/monitoring": "1.38.0", + "@algolia/recommend": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/algoliasearch-helper": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.26.0.tgz", + "integrity": "sha512-Rv2x3GXleQ3ygwhkhJubhhYGsICmShLAiqtUuJTUkr9uOCOXyF2E71LVT4XDnVffbknv8XgScP4U0Oxtgm+hIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/events": "^4.0.1" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 6" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "dev": true, + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.7.tgz", + "integrity": "sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001745", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz", + "integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/combine-promises": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", + "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compressible/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", + "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.45.1.tgz", + "integrity": "sha512-OHnWFKgTUshEU8MK+lOs1H8kC8GkTi9Z1tvNkxrCcw9wl3MJIO7q2ld77wjWn4/xuGrVu2X+nME1iIIPBSdyEQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz", + "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", + "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.4.2.tgz", + "integrity": "sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-advanced": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", + "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.0", + "cssnano-preset-default": "^6.1.2", + "postcss-discard-unused": "^6.0.5", + "postcss-merge-idents": "^6.0.3", + "postcss-reduce-idents": "^6.0.3", + "postcss-zindex": "^6.0.2" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-port": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", + "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.224", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.224.tgz", + "integrity": "sha512-kWAoUu/bwzvnhpdZSIc6KUyvkI1rbRXMT0Eq8pKReyOyaPZcctMli+EgvcN1PAvwVc7Tdo4Fxi2PsLNDU05mdg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/emoticon": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", + "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-value-to-estree": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.0.tgz", + "integrity": "sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", + "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eval": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", + "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "require-like": ">= 0.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/feed": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", + "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-js": "^1.6.11" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/file-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/file-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.0.1.tgz", + "integrity": "sha512-CG/iEvgQqfzoVsMUbxSJcwbG2JwyZ3naEqPkeltwl0BSS8Bp83k3xlGms+0QdWFUAwV+uvo80wNswKF6FWEkKg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", + "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz", + "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/html-webpack-plugin/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "dev": true, + "license": "MIT", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infima": { + "version": "0.2.0-alpha.45", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", + "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-network-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", + "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-yarn-global": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", + "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/launch-editor": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", + "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.3.0.tgz", + "integrity": "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.46.1.tgz", + "integrity": "sha512-2wjHDg7IjP+ufAqqqSxjiNePFDrvWviA79ajUwG9lkHhk3HzZOLBzzoUG8cx9vCagj6VvBQD7oXuLuFz5LaAOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-space/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", + "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/null-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", + "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/null-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/null-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/null-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/null-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "dev": true, + "license": "MIT", + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", + "integrity": "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-custom-media": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", + "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-properties": { + "version": "14.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", + "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", + "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-unused": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", + "integrity": "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-focus-within": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-image-set-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-lab-function": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", + "integrity": "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-loader": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", + "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.3.5", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", + "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-merge-idents": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", + "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nesting": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", + "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-resolve-nested": "^3.1.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", + "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "dev": true, + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", + "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-preset-env": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.4.0.tgz", + "integrity": "sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-alpha-function": "^1.0.1", + "@csstools/postcss-cascade-layers": "^5.0.2", + "@csstools/postcss-color-function": "^4.0.12", + "@csstools/postcss-color-function-display-p3-linear": "^1.0.1", + "@csstools/postcss-color-mix-function": "^3.0.12", + "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.2", + "@csstools/postcss-content-alt-text": "^2.0.8", + "@csstools/postcss-contrast-color-function": "^2.0.12", + "@csstools/postcss-exponential-functions": "^2.0.9", + "@csstools/postcss-font-format-keywords": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.11", + "@csstools/postcss-gradients-interpolation-method": "^5.0.12", + "@csstools/postcss-hwb-function": "^4.0.12", + "@csstools/postcss-ic-unit": "^4.0.4", + "@csstools/postcss-initial": "^2.0.1", + "@csstools/postcss-is-pseudo-class": "^5.0.3", + "@csstools/postcss-light-dark-function": "^2.0.11", + "@csstools/postcss-logical-float-and-clear": "^3.0.0", + "@csstools/postcss-logical-overflow": "^2.0.0", + "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", + "@csstools/postcss-logical-resize": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.4", + "@csstools/postcss-media-minmax": "^2.0.9", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", + "@csstools/postcss-nested-calc": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.0", + "@csstools/postcss-oklab-function": "^4.0.12", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/postcss-random-function": "^2.0.1", + "@csstools/postcss-relative-color-syntax": "^3.0.12", + "@csstools/postcss-scope-pseudo-class": "^4.0.1", + "@csstools/postcss-sign-functions": "^1.1.4", + "@csstools/postcss-stepped-value-functions": "^4.0.9", + "@csstools/postcss-text-decoration-shorthand": "^4.0.3", + "@csstools/postcss-trigonometric-functions": "^4.0.9", + "@csstools/postcss-unset-value": "^4.0.0", + "autoprefixer": "^10.4.21", + "browserslist": "^4.26.0", + "css-blank-pseudo": "^7.0.1", + "css-has-pseudo": "^7.0.3", + "css-prefers-color-scheme": "^10.0.0", + "cssdb": "^8.4.2", + "postcss-attribute-case-insensitive": "^7.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^7.0.12", + "postcss-color-hex-alpha": "^10.0.0", + "postcss-color-rebeccapurple": "^10.0.0", + "postcss-custom-media": "^11.0.6", + "postcss-custom-properties": "^14.0.6", + "postcss-custom-selectors": "^8.0.5", + "postcss-dir-pseudo-class": "^9.0.1", + "postcss-double-position-gradients": "^6.0.4", + "postcss-focus-visible": "^10.0.1", + "postcss-focus-within": "^9.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^6.0.0", + "postcss-image-set-function": "^7.0.0", + "postcss-lab-function": "^7.0.12", + "postcss-logical": "^8.1.0", + "postcss-nesting": "^13.0.2", + "postcss-opacity-percentage": "^3.0.0", + "postcss-overflow-shorthand": "^6.0.0", + "postcss-page-break": "^3.0.4", + "postcss-place": "^10.0.0", + "postcss-pseudo-class-any-link": "^10.0.1", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^8.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-reduce-idents": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", + "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-sort-media-queries": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", + "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sort-css-media-queries": "2.2.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.4.23" + } + }, + "node_modules/postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-zindex": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", + "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", + "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-helmet-async": { + "name": "@slorber/react-helmet-async", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz", + "integrity": "sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.2.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-json-view-lite": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", + "integrity": "sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-loadable": { + "name": "@docusaurus/react-loadable", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", + "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-loadable-ssr-addon-v5-slorber": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", + "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.3" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "react-loadable": "*", + "webpack": ">=4.41.1 || 5.x" + } + }, + "node_modules/react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-config": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", + "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2" + }, + "peerDependencies": { + "react": ">=15", + "react-router": ">=5" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/registry-auth-token": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", + "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/rehype-katex": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remark-directive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-emoji": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", + "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.2", + "emoticon": "^4.0.1", + "mdast-util-find-and-replace": "^3.0.1", + "node-emoji": "^2.1.0", + "unified": "^11.0.4" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/remark-frontmatter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", + "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-frontmatter": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/renderkid/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rtlcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", + "dev": true, + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0", + "postcss": "^8.4.21", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "license": "ISC" + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/schema-dts": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", + "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-handler": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", + "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=5.6.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sort-css-media-queries": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", + "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.3.0" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/srcset": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", + "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-js": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", + "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.9" + } + }, + "node_modules/style-to-object": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/swr": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", + "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/tapable": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/thingies": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", + "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-notifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/boxen": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", + "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, + "node_modules/url-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/url-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/url-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/url-loader/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpack": { + "version": "5.101.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", + "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", + "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.21.2", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpackbar": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", + "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "consola": "^3.2.3", + "figures": "^3.2.0", + "markdown-table": "^2.0.0", + "pretty-time": "^1.1.0", + "std-env": "^3.7.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "webpack": "3 || 4 || 5" + } + }, + "node_modules/webpackbar/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpackbar/node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpackbar/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpackbar/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", + "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000000..14b45b175d --- /dev/null +++ b/docs/package.json @@ -0,0 +1,15 @@ +{ + "name": "@miden/docs-dev", + "private": true, + "scripts": { + "start:dev": "docusaurus start --port 3000 --host 0.0.0.0", + "build:dev": "docusaurus build", + "serve:dev": "docusaurus serve build" + }, + "devDependencies": { + "@docusaurus/core": "^3", + "@docusaurus/preset-classic": "^3", + "remark-math": "^6", + "rehype-katex": "^7" + } +} diff --git a/docs/sidebars.ts b/docs/sidebars.ts new file mode 100644 index 0000000000..da531529d6 --- /dev/null +++ b/docs/sidebars.ts @@ -0,0 +1,13 @@ +import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; + +/** + * Autogenerate entire sidebar tree from /docs + * This will include: + * - Static docs from this aggregator repo + * - All vendor repos' docs synced into subfolders + */ +const sidebars: SidebarsConfig = { + docs: [{ type: "autogenerated", dirName: "." }], +}; + +export default sidebars; diff --git a/docs/src/EXPORTED.md b/docs/src/EXPORTED.md deleted file mode 100644 index 32ab7d5b2e..0000000000 --- a/docs/src/EXPORTED.md +++ /dev/null @@ -1,17 +0,0 @@ - - -# Summary - -- [Protocol](./index.md) - - [Accounts](./account/overview.md) - - [ID](./account/id.md) - - [Address](./account/address.md) - - [Code](./account/code.md) - - [Storage](./account/storage.md) - - [Component](./account/component.md) - - [Note](./note.md) - - [Asset](./asset.md) - - [Transaction](./transaction.md) - - [State](./state.md) - - [Blockchain](./blockchain.md) - - [Miden Protocol Library](./protocol_library.md) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md deleted file mode 100644 index 8017d07623..0000000000 --- a/docs/src/SUMMARY.md +++ /dev/null @@ -1,18 +0,0 @@ - - -# Summary - -- [Introduction](./index.md) -- [Accounts](./account/overview.md) - - [ID](./account/id.md) - - [Address](./account/address.md) - - [Code](./account/code.md) - - [Storage](./account/storage.md) - - [Component](./account/component.md) -- [Notes](./note.md) -- [Assets](./asset.md) -- [Transactions](./transaction.md) - - [Fees](./fees.md) -- [State](./state.md) -- [Blockchain](./blockchain.md) -- [Miden Protocol Library](./protocol_library.md) diff --git a/docs/src/_category_.json b/docs/src/_category_.json new file mode 100644 index 0000000000..3212502e6c --- /dev/null +++ b/docs/src/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Protocol", + "position": 3, + "collapsed": true +} diff --git a/docs/src/account/_category_.json b/docs/src/account/_category_.json new file mode 100644 index 0000000000..bc91c53b6d --- /dev/null +++ b/docs/src/account/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Accounts", + "position": 2, + "collapsed": true +} diff --git a/docs/src/account/address.md b/docs/src/account/address.md index b2ee2b135b..08ecb059ad 100644 --- a/docs/src/account/address.md +++ b/docs/src/account/address.md @@ -1,12 +1,17 @@ +--- +sidebar_position: 3 +--- + # Address -> [!Note] -> A human-readable identifier for `Account`s or public keys. +:::note +A human-readable identifier for `Account`s or public keys. +::: ## Purpose -An address is an identifier that facilitates sending and receiving of [notes](../note.md). It serves three main purposes explained in this section. +An address is an identifier that facilitates sending and receiving of [notes](../note). It serves three main purposes explained in this section. ### Communicating receiver information @@ -16,7 +21,7 @@ The receiver can choose to disclose various pieces of information that control h Consider a few examples that use different address mechanisms: -- The [Pay-to-ID note](../note.md#p2id-pay-to-id): the note itself can only be consumed if the account ID encoded in the note details matches the ID of the account that tries to consume it. To receive a P2ID note, the receiver should communicate an `Address::AccountId` type to the sender. +- The [Pay-to-ID note](../note#p2id-pay-to-id): the note itself can only be consumed if the account ID encoded in the note details matches the ID of the account that tries to consume it. To receive a P2ID note, the receiver should communicate an `Address::AccountId` type to the sender. - A "Pay-to-PoW" note that can only be consumed if the receiver can provide a valid seed such that the hash of the seed results in a value with n leading zero bits. The receiver communicates an `Address::PoW` type to the sender, which encodes the target number of leading zero bits (and a salt to avoid re-use of the same seed).* - A "Pay-to-Public-Key" note that stores a public (signature) key and checks if the receiver can provide a valid cryptographic signature for that key. The `Address::PublicKey` type must encode the public key.* @@ -25,8 +30,9 @@ These different address mechanisms provide different levels of privacy and secur - `Address::PoW`: the receiver is not revealed publicly, but potentially many entities can consume the note. The receiver has an advantage by specifying the salt. - `Address::PublicKey`: the receiver `AccountId` is not revealed publicly, only their public key. A fresh `Address::PublicKey` can be used for receiving each note, resulting in increased privacy. -> [!Note] -> The "Pay-to-PoW" and "Pay-to-Public-Key" notes and the corresponding address types are for illustration purposes only. They are not part of the Miden library. +:::note +The "Pay-to-PoW" and "Pay-to-Public-Key" notes and the corresponding address types are for illustration purposes only. They are not part of the Miden library. +::: ### Communicating channel information @@ -34,11 +40,11 @@ For notes which are sent privately, the sender needs to communicate the full not Instead, our Miden client connects to a _Note Transport Layer_, which stores encrypted note details together with the associated public metadata for each note. The receiver can query the Note Transport Layer for `NoteTag`s they are interested in. Typically, a `NoteTag` encodes a few leading bits (14 by default) of the receiver's `AccountId`. Querying the Note Transport Layer for 14-bit `NoteTag`s reduces the receiver's privacy, but at the same time allows them to perform less work downloading and trial-decrypting the notes than if fewer bits were encoded. -With an `Address`, e.g. the [`Address::AccountId`](./address.md#addressaccountid) variant, the receiver could specify how many bits of their `AccountId` they want to disclose to the sender and thus choose their level of privacy. +With an `Address`, e.g. the [`Address::AccountId`](./address#addressaccountid) variant, the receiver could specify how many bits of their `AccountId` they want to disclose to the sender and thus choose their level of privacy. ### Account interface discovery -An address allows the sender of the note to easily discover the interface of the receiving account. As explained in the [account interface](./code.md#interface) section, every account can have a different set of procedures that note scripts can call, which is the _interface_ of the account. In order for the sender of a note to create a note that the receiver can consume, the sender needs to know the interface of the receiving account. This can be communicated via the address, which encodes a mapping of standard interfaces like the basic wallet. +An address allows the sender of the note to easily discover the interface of the receiving account. As explained in the [account interface](./code#interface) section, every account can have a different set of procedures that note scripts can call, which is the _interface_ of the account. In order for the sender of a note to create a note that the receiver can consume, the sender needs to know the interface of the receiving account. This can be communicated via the address, which encodes a mapping of standard interfaces like the basic wallet. If a sender wants to create a note, it is up to them to check whether the receiver account has an interface that it compatible with that note. The notion of an address doesn't exist at protocol level and so it is up to wallets or clients to implement this interface compatibility check. @@ -52,19 +58,20 @@ An address encodes an address type and an address interface: - The type determines what the address fundamentally points to, e.g. an account ID or, in the future, a public key. - The interface informs the sender of the capabilities of the receiver's account. -> [!Note] -> Adding a public key-based address type is planned. +:::note +Adding a public key-based address type is planned. +::: The currently supported **address types** are: - `Address::AccountId` (type `0`): An address pointing to an account ID. The currently supported **address interfaces** are: - `Unspecified` (type `0`): No interface is specified. Used for addresses where the interface is unknown. -- `BasicWallet` (type `1`): The standard basic wallet interface. See the [account code](./code.md#interface) docs for details. +- `BasicWallet` (type `1`): The standard basic wallet interface. See the [account code](./code#interface) docs for details. ### `Address::AccountId` -The account ID address points to an account ID and also allows specifying the [note tag](../note.md#note-discovery) length. This tag length preference determines how many bits of the account ID are encoded into note tags of notes targeted to this address. This lets the owner of the account choose their level of privacy. A higher tag length makes the account more uniquely identifiable and reduces privacy, while a shorter length increases privacy at the cost of matching more notes published onchain. +The account ID address points to an account ID and also allows specifying the [note tag](../note#note-discovery) length. This tag length preference determines how many bits of the account ID are encoded into note tags of notes targeted to this address. This lets the owner of the account choose their level of privacy. A higher tag length makes the account more uniquely identifiable and reduces privacy, while a shorter length increases privacy at the cost of matching more notes published onchain. ## Encoding diff --git a/docs/src/account/code.md b/docs/src/account/code.md index 0a047d74c9..21996b9b56 100644 --- a/docs/src/account/code.md +++ b/docs/src/account/code.md @@ -1,9 +1,15 @@ +--- +sidebar_position: 4 +title: "Code" +--- + # Account Code -> [!Note] -> A collection of procedures defining the `Account`'s programmable interface. +:::note +A collection of procedures defining the `Account`'s programmable interface. +::: -Every Miden `Account` is essentially a smart contract. The `Code` defines the account's procedures, which can be invoked through both [note scripts](../note.md#script) and [transaction scripts](../transaction.md#inputs). Key characteristics include: +Every Miden `Account` is essentially a smart contract. The `Code` defines the account's procedures, which can be invoked through both [note scripts](../note#script) and [transaction scripts](../transaction#inputs). Key characteristics include: - **Mutable access:** Only the `Account`'s own procedures can modify its storage and vault. All state changes — such as updating storage slots or transferring assets — must occur through these procedures. - **Function commitment:** Each function can be called by its [MAST](https://0xMiden.github.io/miden-vm/user_docs/assembly/main.html) root. The root represents the underlying code tree as a 32-byte commitment. This ensures integrity which means a function's behavior cannot change without changing the MAST root. @@ -11,13 +17,14 @@ Every Miden `Account` is essentially a smart contract. The `Code` defines the ac ## Interface -An account's code is typically the result of merging multiple [account components](./component.md). This results in a set of procedures that make up the _interface_ of the account. As an example, a typical wallet uses the so-called _basic wallet_ interface, which is defined in `miden::contracts::wallets::basic`. It consists of the `receive_asset` and `move_asset_to_note` procedures. If an account has this interface, i.e. this set of procedures, it can consume standard [P2ID notes](../note.md#p2id-pay-to-id). If it doesn't, it can't consume this type of note. So, adhering to standard interfaces such as the basic wallet will generally make an account more interoperable. +An account's code is typically the result of merging multiple [account components](./component). This results in a set of procedures that make up the _interface_ of the account. As an example, a typical wallet uses the so-called _basic wallet_ interface, which is defined in `miden::contracts::wallets::basic`. It consists of the `receive_asset` and `move_asset_to_note` procedures. If an account has this interface, i.e. this set of procedures, it can consume standard [P2ID notes](../note#p2id-pay-to-id). If it doesn't, it can't consume this type of note. So, adhering to standard interfaces such as the basic wallet will generally make an account more interoperable. ## Authentication Authenticating a transaction, and therefore the changes to the account, is done with an _authentication procedure_. Every account's code must provide exactly one authentication procedure. It is automatically called during the transaction epilogue, i.e. after all note scripts and the transaction script have been executed. Such an authentication procedure typically inspects the transaction and then decides whether a signature is required to authenticate the changes. It does this by: + - checking which account procedures have been called - Example: Authentication is required if the `distribute` procedure was called but not if `burn` was called. - inspecting the account delta. @@ -26,8 +33,8 @@ Such an authentication procedure typically inspects the transaction and then dec - checking whether notes have been consumed. - checking whether notes have been created. -Recall that an [account's nonce](overview.md#nonce) must be incremented whenever its state changes. Only authentication procedures are allowed to do so, to prevent accidental or unintended authorization of state changes. +Recall that an [account's nonce](overview#nonce) must be incremented whenever its state changes. Only authentication procedures are allowed to do so, to prevent accidental or unintended authorization of state changes. ### Procedure tracking -The authentication procedure can base its authentication decision on whether a specific account procedure was called during the transaction (procedure tracking). A procedure is considered "tracked" only if it invokes account-restricted kernel APIs (procedures that are only allowed to be called from the account context, e.g. `exec.faucet::mint`). Procedures that execute only local instructions (e.g., a noop `push.0 drop`) will not be marked as tracked. \ No newline at end of file +The authentication procedure can base its authentication decision on whether a specific account procedure was called during the transaction (procedure tracking). A procedure is considered "tracked" only if it invokes account-restricted kernel APIs (procedures that are only allowed to be called from the account context, e.g. `exec.faucet::mint`). Procedures that execute only local instructions (e.g., a noop `push.0 drop`) will not be marked as tracked. diff --git a/docs/src/account/component.md b/docs/src/account/components.md similarity index 96% rename from docs/src/account/component.md rename to docs/src/account/components.md index 7a5c620c56..297c0ff310 100644 --- a/docs/src/account/component.md +++ b/docs/src/account/components.md @@ -1,6 +1,11 @@ +--- +sidebar_position: 6 +title: "Components" +--- + # Account Components -Account components are reusable units of functionality that define a part of an account's code and storage. Multiple account components can be merged together to form an account's final [code](./code.md) and [storage](./storage.md). +Account components are reusable units of functionality that define a part of an account's code and storage. Multiple account components can be merged together to form an account's final [code](./code) and [storage](./storage). As an example, consider a typical wallet account, capable of holding a user's assets and requiring authentication whenever assets are added or removed. Such an account can be created by merging a `BasicWallet` component with an `RpoFalcon512` authentication component. The basic wallet does not need any storage, but contains the code to move assets in and out of the account vault. The authentication component holds a user's public key in storage and additionally contains the code to verify a signature against that public key. Together, these components form a fully functional wallet account. @@ -30,7 +35,7 @@ The component metadata can be defined using TOML. Below is an example specificat ```toml name = "Fungible Faucet" -description = "This component showcases the component template format, and the different ways of +description = "This component showcases the component template format, and the different ways of providing valid values to it." version = "1.0.0" supported-types = ["FungibleFaucet"] @@ -72,7 +77,7 @@ values = [ #### Specifying values and their types -In the TOML format, any value that is one word long can be written as a single value, or as exactly four field elements. In turn, a field element is a number within Miden's finite field. +In the TOML format, any value that is one word long can be written as a single value, or as exactly four field elements. In turn, a field element is a number within Miden's finite field. A word can be written as a hexadecimal value, and field elements can be written either as hexadecimal or decimal numbers. In all cases, numbers should be input as strings. @@ -127,7 +132,7 @@ In the above example, the first and second storage entries are single-slot value ##### Storage map entries -[Storage maps](./storage.md#map-slots) consist of key-value pairs, where both keys and values are single words. +[Storage maps](./storage#map-slots) consist of key-value pairs, where both keys and values are single words. Storage map entries can specify the following fields: diff --git a/docs/src/account/id.md b/docs/src/account/id.md index 56ebefff07..84a62aace0 100644 --- a/docs/src/account/id.md +++ b/docs/src/account/id.md @@ -1,7 +1,13 @@ +--- +sidebar_position: 2 +title: "ID" +--- + # Account ID -> [!Note] -> An immutable and unique identifier for the `Account`. +:::note +An immutable and unique identifier for the `Account`. +::: The `Account` ID is a 120-bit long number. This identifier is designed to contain the metadata of an account. The metadata includes the [account type](#account-type), [account storage mode](#account-storage-mode) and the version of the `Account`. This metadata is included in the ID to ensure it can be read without needing the full account state. @@ -15,13 +21,14 @@ There are two main categories of accounts in Miden: **basic accounts** and **fau - **Basic Accounts:** Basic Accounts may be either mutable or immutable: - - *Mutable:* Code can be changed after deployment. - - *Immutable:* Code cannot be changed once deployed. + + - _Mutable:_ Code can be changed after deployment. + - _Immutable:_ Code cannot be changed once deployed. - **Faucets:** Faucets are always immutable and can be specialized by the type of assets they issue: - - *Fungible Faucet:* Can issue fungible [assets](../asset.md). - - *Non-fungible Faucet:* Can issue non-fungible [assets](../asset.md). + - _Fungible Faucet:_ Can issue fungible [assets](../asset). + - _Non-fungible Faucet:_ Can issue non-fungible [assets](../asset). ### Account storage mode @@ -40,9 +47,10 @@ Users can choose whether their accounts are stored publicly or privately. The pr An `Account` ID can be encoded in different formats: -1. [**Address**](./address.md#types--interfaces): - - Used when sending or receiving notes or assets. - - Used to communicate the [account interface](./code.md#interface) between sender and receiver. +1. [**Address**](./address#types--interfaces): + + - Used when sending or receiving notes or assets. + - Used to communicate the [account interface](./code#interface) between sender and receiver. 2. **Hexadecimal**: - Example: `0xd345c9766a2d5e606477a5676b049a` diff --git a/docs/src/account/overview.md b/docs/src/account/index.md similarity index 82% rename from docs/src/account/overview.md rename to docs/src/account/index.md index 2e1df1014a..015b81a1c6 100644 --- a/docs/src/account/overview.md +++ b/docs/src/account/index.md @@ -1,6 +1,10 @@ +--- +sidebar_position: 1 +--- + # Accounts / Smart Contracts -An `Account` represents the primary entity in Miden. It is capable of holding assets, storing data, and executing custom code. Each `Account` is a smart contract with a programmable interface through which note and transaction scripts can interact with the account's state and assets. By executing [transactions](../transaction.md) against an account, its state can be modified. +An `Account` represents the primary entity in Miden. It is capable of holding assets, storing data, and executing custom code. Each `Account` is a smart contract with a programmable interface through which note and transaction scripts can interact with the account's state and assets. By executing [transactions](../transaction) against an account, its state can be modified. ## What is the purpose of an account? @@ -10,29 +14,31 @@ In Miden's hybrid UTXO- and account-based model, accounts enable the creation of An `Account` is composed of several core parts, illustrated below: -

- Account diagram +

+ Account diagram

These parts are: -1. [ID](id.md) -2. [Code](code.md) -3. [Storage](storage.md) +1. [ID](id) +2. [Code](code) +3. [Storage](storage) 4. [Vault](#vault) 5. [Nonce](#nonce) ### Vault -> [!Note] -> A collection of [assets](../asset.md) stored by the `Account`. +:::note +A collection of [assets](../asset) stored by the `Account`. +::: Large amounts of fungible and non-fungible assets can be stored in the account's vault. ### Nonce -> [!Note] -> A counter incremented with each state update to the `Account`. +:::note +A counter incremented with each state update to the `Account`. +::: The nonce ensures that an account has a unique _commitment_ (or "hash") after every transaction, even if it contains the same assets and has the same storage state. That in turn allows ordering of transactions and prevents replay attacks. Whenever the state of an account changes in a transaction, its nonce must be incremented and it can only be incremented exactly once per transaction. @@ -40,7 +46,7 @@ Note that a transaction does not always change the state of an account. For inst ## Account creation -For an `Account` to be recognized by the network, it must exist in the [account database](../state.md#account-database) maintained by Miden node(s). +For an `Account` to be recognized by the network, it must exist in the [account database](../state#account-database) maintained by Miden node(s). However, a user can locally create a new `Account` ID before it's recognized network-wide. The typical process might be: diff --git a/docs/src/account/storage.md b/docs/src/account/storage.md index 0dac97a6a3..9a69437d45 100644 --- a/docs/src/account/storage.md +++ b/docs/src/account/storage.md @@ -1,18 +1,24 @@ +--- +sidebar_position: 5 +title: "Storage" +--- + # Account Storage -> [!Note] -> A flexible, arbitrary data store within the `Account`. +:::note +A flexible, arbitrary data store within the `Account`. +::: The [storage](https://docs.rs/miden-objects/latest/miden_objects/account/struct.AccountStorage.html) is divided into a maximum of 255 indexed [storage slots](https://docs.rs/miden-objects/latest/miden_objects/account/enum.StorageSlot.html). Each slot can either store a 32-byte value or serve as the cryptographic root to a key-value store with the capacity to store large amounts of data. - **Value slots:** Contains 32 bytes of arbitrary data. - **Map slots:** Contains a [StorageMap](#storagemap), a key-value store where both keys and values are 32 bytes. The slot's value is a commitment to the entire map. -An account's storage is typically the result of merging multiple [account components](./component.md). +An account's storage is typically the result of merging multiple [account components](./component). ## Value Slots -A value slot can be used whenever 32 bytes of data is enough, e.g. for storing a single public key for use in [authentication procedures](code.md#authentication). +A value slot can be used whenever 32 bytes of data is enough, e.g. for storing a single public key for use in [authentication procedures](code#authentication). ## Map Slots diff --git a/docs/src/asset.md b/docs/src/asset.md index e57cb672af..d9177eaf61 100644 --- a/docs/src/asset.md +++ b/docs/src/asset.md @@ -1,10 +1,14 @@ +--- +sidebar_position: 4 +--- + # Assets -An `Asset` is a unit of value that can be transferred from one [account](account/overview.md) to another using [notes](note.md). +An `Asset` is a unit of value that can be transferred from one [account](account/overview) to another using [notes](note). ## What is the purpose of an asset? -In Miden, assets serve as the primary means of expressing and transferring value between [accounts](account/overview.md) through [notes](note.md). They are designed with four key principles in mind: +In Miden, assets serve as the primary means of expressing and transferring value between [accounts](account/overview) through [notes](note). They are designed with four key principles in mind: 1. **Parallelizable exchange:** By managing ownership and transfers directly at the account level instead of relying on global structures like ERC20 contracts, accounts can exchange assets concurrently, boosting scalability and efficiency. @@ -16,24 +20,26 @@ In Miden, assets serve as the primary means of expressing and transferring value Users can transact freely and privately with no single contract or entity controlling `Asset` transfers. This reduces the risk of censored transactions, resulting in a more open and resilient system. 4. **Fee payment in native asset:** - Transaction fees are paid in the chain’s native asset as defined by the current reference block’s fee parameters. See [Fees](./fees.md). + Transaction fees are paid in the chain's native asset as defined by the current reference block's fee parameters. See [Fees](./fees). ## Native asset -> [!Note] -> All data structures following the Miden asset model that can be exchanged. +:::note +All data structures following the Miden asset model that can be exchanged. +::: -Native assets adhere to the Miden `Asset` model (encoding, issuance, storage). Every native `Asset` is encoded using 32 bytes, including both the [ID](account/id.md) of the issuing account and the `Asset` details. +Native assets adhere to the Miden `Asset` model (encoding, issuance, storage). Every native `Asset` is encoded using 32 bytes, including both the [ID](account/id) of the issuing account and the `Asset` details. ### Issuance -> [!Note] -> Only [faucet](account/id.md#account-type) accounts can issue assets. +:::note +Only [faucet](account/id#account-type) accounts can issue assets. +::: Faucets can issue either fungible or non-fungible assets as defined at account creation. The faucet's code specifies the `Asset` minting conditions: i.e., how, when, and by whom these assets can be minted. Once minted, they can be transferred to other accounts using notes. -

- Asset issuance +

+ Asset issuance

### Type @@ -48,10 +54,10 @@ Non-fungible assets are encoded by hashing the `Asset` data into 32 bytes and pl ### Storage -[Accounts](account/overview.md) and [notes](note.md) have vaults used to store assets. Accounts use a sparse Merkle tree as a vault while notes use a simple list. This enables an account to store a practically unlimited number of assets while a note can only store 255 assets. +[Accounts](account/overview) and [notes](note) have vaults used to store assets. Accounts use a sparse Merkle tree as a vault while notes use a simple list. This enables an account to store a practically unlimited number of assets while a note can only store 255 assets. -

- Asset storage +

+ Asset storage

### Burning @@ -60,7 +66,8 @@ Assets in Miden can be burned through various methods, such as rendering them un ## Alternative asset models -> [!Note] -> All data structures not following the Miden asset model that can be exchanged. +:::note +All data structures not following the Miden asset model that can be exchanged. +::: Miden is flexible enough to support other `Asset` models. For example, developers can replicate Ethereum’s ERC20 pattern, where fungible `Asset` ownership is recorded in a single account. To transact, users send a note to that account, triggering updates in the global hashmap state. diff --git a/docs/src/blockchain.md b/docs/src/blockchain.md index 7db24cb4b1..e6b616886c 100644 --- a/docs/src/blockchain.md +++ b/docs/src/blockchain.md @@ -1,22 +1,26 @@ +--- +sidebar_position: 7 +--- + # Blockchain -The Miden blockchain protocol describes how the [state](state.md) progresses through blocks, which are containers that aggregate account state changes and their proofs, together with created and consumed notes. Blocks represent the delta of the global state between two time periods, and each is accompanied by a corresponding proof that attests to the correctness of all state transitions it contains. The current global state can be derived by applying all the blocks to the genesis state. +The Miden blockchain protocol describes how the [state](state) progresses through blocks, which are containers that aggregate account state changes and their proofs, together with created and consumed notes. Blocks represent the delta of the global state between two time periods, and each is accompanied by a corresponding proof that attests to the correctness of all state transitions it contains. The current global state can be derived by applying all the blocks to the genesis state. Miden's blockchain protocol aims for the following: - **Proven transactions**: All included transactions have already been proven and verified when they reach the block. - **Fast genesis syncing**: New nodes can efficiently sync to the tip of the chain. -

- Execution diagram +

+ Execution diagram

## Batch production To reduce the required space on the blockchain, transaction proofs are not directly put into blocks. First, they are batched together by verifying them in the batch producer. The purpose of the batch producer is to generate a single proof that some number of proven transactions have been verified. This involves recursively verifying individual transaction proofs inside the Miden VM. As with any program that runs in the Miden VM, there is a proof of correct execution running the Miden verifier to verify transaction proofs. This results into a single batch proof. -

- Batch diagram +

+ Batch diagram

The batch producer aggregates transactions sequentially by verifying that their proofs and state transitions are correct. More specifically, the batch producer ensures: @@ -42,7 +46,7 @@ The block producer ensures: 8. **Note erasure of erasable notes**: If an erasable note is created and consumed in different batches, it is erased now. If, however, an erasable note is consumed but not created within the block, the batch it contains is rejected. The Miden operator's mempool should preemptively filter such transactions. In final `Block` contains: -- The commitments to the current global [state](state.md). +- The commitments to the current global [state](state). - The newly created nullifiers. - The commitments to newly created notes. - The new state commitments for affected private accounts. @@ -50,16 +54,18 @@ In final `Block` contains: The `Block` proof attests to the correct state transition from the previous `Block` commitment to the next, and therefore to the change in Miden's global state. -

- Block diagram +

+ Block diagram

-> [!Tip] -> -> **Block Contents:** -> - **State updates**: Contains only the hashes of updated elements. For example, for each updated account, a tuple is recorded as `([account id], [new account hash])`. -> - **ZK Proof**: This proof attests that, given a state commitment from the previous `Block`, a set of valid batches was executed that resulted in the new state commitment. -> - The `Block` also includes the full account and note data for public accounts and notes. For example, if account `123` is a public account that has been updated, you would see a record in the **state updates** section as `(123, 0x456..)`, and the full new state of this account (which should hash to `0x456..`) would be included in a separate section. +:::tip + +**Block Contents:** +- **State updates**: Contains only the hashes of updated elements. For example, for each updated account, a tuple is recorded as `([account id], [new account hash])`. +- **ZK Proof**: This proof attests that, given a state commitment from the previous `Block`, a set of valid batches was executed that resulted in the new state commitment. +- The `Block` also includes the full account and note data for public accounts and notes. For example, if account `123` is a public account that has been updated, you would see a record in the **state updates** section as `(123, 0x456..)`, and the full new state of this account (which should hash to `0x456..`) would be included in a separate section. + +::: ## Verifying blocks diff --git a/docs/src/fees.md b/docs/src/fees.md index c054685f93..ba38a75553 100644 --- a/docs/src/fees.md +++ b/docs/src/fees.md @@ -1,3 +1,8 @@ +--- +sidebar_position: 5.1 +draft: true +--- + # Fees Miden transactions pay a fee that is computed and charged automatically by the transaction kernel during the epilogue. diff --git a/docs/src/index.md b/docs/src/index.md index 1c08f60b8b..8f399707c7 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,4 +1,9 @@ -# Miden architecture overview +--- +sidebar_position: 1 +title: Overview +--- + +# Miden Architecture Overview Miden’s architecture departs considerably from typical blockchain designs to support privacy and parallel transaction execution. @@ -8,9 +13,9 @@ However, user generated zero-knowledge proofs allow state transitions, e.g. tran ## Miden design goals -* High throughput: The ability to process a high number of transactions (state changes) over a given time interval. -* Privacy: The ability to keep data known to one’s self and anonymous while processing and/or storing it. -* Asset safety: Maintaining a low risk of mistakes or malicious behavior leading to asset loss. +- High throughput: The ability to process a high number of transactions (state changes) over a given time interval. +- Privacy: The ability to keep data known to one’s self and anonymous while processing and/or storing it. +- Asset safety: Maintaining a low risk of mistakes or malicious behavior leading to asset loss. ## Actor model @@ -24,23 +29,23 @@ Miden uses _accounts_ and _notes_, both of which hold assets. Accounts consume a ### Accounts -An [Account](account/overview.md) can hold assets and define rules how assets can be transferred. Accounts can represent users or autonomous smart contracts. The [account chapter](account/overview.md) describes the design of an account, its storage types, and creating an account. +An [Account](account/overview) can hold assets and define rules how assets can be transferred. Accounts can represent users or autonomous smart contracts. The [account chapter](account/overview) describes the design of an account, its storage types, and creating an account. ### Notes -A [Note](note.md) is a message that accounts send to each other. A note stores assets and a script that defines how the note can be consumed. The [note chapter](note.md) describes the design, the storage types, and the creation of a note. +A [Note](note) is a message that accounts send to each other. A note stores assets and a script that defines how the note can be consumed. The [note chapter](note) describes the design, the storage types, and the creation of a note. ### Assets -An [Asset](asset.md) can be fungible and non-fungible. They are stored in the owner’s account itself or in a note. The [asset chapter](asset.md) describes asset issuance, customization, and storage. +An [Asset](asset) can be fungible and non-fungible. They are stored in the owner's account itself or in a note. The [asset chapter](asset) describes asset issuance, customization, and storage. ### Transactions -A [Transactions](transaction.md) describe the production and consumption of notes by a single account. +A [Transactions](transaction) describe the production and consumption of notes by a single account. -Executing a transaction always results in a STARK proof. +Executing a transaction always results in a STARK proof. -The [transaction chapter](transaction.md) describes the transaction design and implementation, including an in-depth discussion of how transaction execution happens in the transaction kernel program. +The [transaction chapter](transaction) describes the transaction design and implementation, including an in-depth discussion of how transaction execution happens in the transaction kernel program. #### Accounts produce and consume notes to communicate @@ -54,11 +59,11 @@ Miden's state model captures the individual states of all accounts and notes, an ### State model -[State](state.md) describes everything that is the case at a certain point in time. Individual states of accounts or notes can be stored on-chain and off-chain. This chapter describes the three different state databases in Miden. +[State](state) describes everything that is the case at a certain point in time. Individual states of accounts or notes can be stored on-chain and off-chain. This chapter describes the three different state databases in Miden. ### Blockchain -The [Blockchain](blockchain.md) defines how state progresses as aggregated-state-updates in batches, blocks, and epochs. The [blockchain chapter](blockchain.md) describes the execution model and how blocks are built. +The [Blockchain](blockchain) defines how state progresses as aggregated-state-updates in batches, blocks, and epochs. The [blockchain chapter](blockchain) describes the execution model and how blocks are built. ##### Operators capture and progress state diff --git a/docs/src/note.md b/docs/src/note.md index 9745d1dcb2..9fbf5eb7af 100644 --- a/docs/src/note.md +++ b/docs/src/note.md @@ -1,6 +1,10 @@ +--- +sidebar_position: 3 +--- + # Notes -A `Note` is the medium through which [Accounts](account/overview.md) communicate. A `Note` holds assets and defines how they can be consumed. +A `Note` is the medium through which [Accounts](account/overview) communicate. A `Note` holds assets and defines how they can be consumed. ## What is the purpose of a note? @@ -10,8 +14,8 @@ In Miden's hybrid UTXO and account-based model notes represent UTXO's which enab A `Note` is composed of several core components, illustrated below: -

- Note diagram +

+ Note diagram

These components are: @@ -24,43 +28,48 @@ These components are: ### Assets -> [!Note] -> An [asset](asset.md) container for a `Note`. +:::note +An [asset](asset) container for a `Note`. +::: A `Note` can contain from 0 up to 256 different assets. These assets represent fungible or non-fungible tokens, enabling flexible asset transfers. ### Script -> [!Note] -> The code executed when the `Note` is consumed. +:::note +The code executed when the `Note` is consumed. +::: Each `Note` has a script that defines the conditions under which it can be consumed. When accounts consume notes in transactions, `Note` scripts call the account’s interface functions. This enables all sorts of operations beyond simple asset transfers. The Miden VM’s Turing completeness allows for arbitrary logic, making `Note` scripts highly versatile. There is no limit to the amount of code a `Note` can hold. ### Inputs -> [!Note] -> Arguments passed to the `Note` script during execution. +:::note +Arguments passed to the `Note` script during execution. +::: A `Note` can have up to 128 input values, which adds up to a maximum of 1 KB of data. The `Note` script can access these inputs. They can convey arbitrary parameters for `Note` consumption. ### Serial number -> [!Note] -> A unique and immutable identifier for the `Note`. +:::note +A unique and immutable identifier for the `Note`. +::: The serial number has two main purposes. Firstly by adding some randomness to the `Note` it ensures it's uniqueness, secondly in private notes it helps prevent linkability between the note's hash and its nullifier. The serial number should be a random 32 bytes number chosen by the user. If leaked, the note’s nullifier can be easily computed, potentially compromising privacy. ### Metadata -> [!Note] -> Additional `Note` information. +:::note +Additional `Note` information. +::: Notes include metadata such as the sender’s account ID and a [tag](#note-discovery) that aids in discovery. Regardless of [storage mode](#note-storage-mode), these metadata fields remain public. ## Note Lifecycle -

- Note lifecycle +

+ Note lifecycle

The `Note` lifecycle proceeds through four primary phases: **creation**, **validation**, **discovery**, and **consumption**. Creation and consumption requires two separate transactions. Throughout this process, notes function as secure, privacy-preserving vehicles for asset transfers and logic execution. @@ -74,9 +83,9 @@ Accounts can create notes in a transaction. The `Note` exists if it is included #### Note storage mode -As with [accounts](account/overview.md), notes can be stored either publicly or privately: +As with [accounts](account/overview), notes can be stored either publicly or privately: -- **Public mode:** The `Note` data is stored in the [note database](state.md#note-database), making it fully visible on-chain. +- **Public mode:** The `Note` data is stored in the [note database](state#note-database), making it fully visible on-chain. - **Private mode:** Only the `Note`’s hash is stored publicly. The `Note`’s actual data remains off-chain, enhancing privacy. ### Note validation @@ -110,7 +119,7 @@ hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment) Only those who know the RECIPIENT’s pre-image can consume the `Note`. For private notes, this ensures an additional layer of control and privacy, as only parties with the correct data can claim the `Note`. -The [transaction prologue](transaction.md) requires all necessary data to compute the `Note` hash. This setup allows scenario-specific restrictions on who may consume a `Note`. +The [transaction prologue](transaction) requires all necessary data to compute the `Note` hash. This setup allows scenario-specific restrictions on who may consume a `Note`. For a practical example, refer to the [SWAP note script](https://github.com/0xMiden/miden-base/blob/next/crates/miden-lib/asm/note_scripts/SWAP.masm), where the RECIPIENT ensures that only a defined target can consume the swapped asset. @@ -131,8 +140,8 @@ This achieves the following properties: That means if a `Note` is private and the operator stores only the note's hash, only those with the `Note` details know if this `Note` has been consumed already. Zcash first [introduced](https://zcash.github.io/orchard/design/nullifiers.html#nullifiers) this approach. -

- Nullifier diagram +

+ Nullifier diagram

## Standard Note Types diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index 83f92f9955..eae86cf401 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 8 +--- + # Miden Protocol Library The Miden protocol library provides a set of procedures that wrap transaction kernel procedures to provide a more convenient interface for common operations. These can be invoked by account code, note scripts, and transaction scripts, though some have restriction from where they can be called. The procedures are organized into modules corresponding to different functional areas. @@ -25,127 +29,127 @@ The procedures maintain the same security and context restrictions as the underl Account procedures can be used to read and write to account storage, add or remove assets from the vault and fetch or compute commitments. -| Procedure | Description | Context | -| --- | --- | --- | -| `get_id` | Returns the ID of the current account.

Inputs: `[]`
Outputs: `[account_id_prefix, account_id_suffix]` | Any | -| `get_native_id` | Returns the ID of the native account of the transaction.

Inputs: `[]`
Outputs: `[account_id_prefix, account_id_suffix]` | Any | -| `get_nonce` | Returns the nonce of the current account. Always returns the initial nonce as it can only be incremented in auth procedures.

Inputs: `[]`
Outputs: `[nonce]` | Any | -| `get_native_nonce` | Returns the nonce of the native account of the transaction.

Inputs: `[]`
Outputs: `[nonce]` | Any | -| `incr_nonce` | Increments the account nonce by one and returns the new nonce. Can only be called from auth procedures.

Inputs: `[]`
Outputs: `[final_nonce]` | Auth | -| `get_initial_commitment` | Returns the native account commitment at the beginning of the transaction.

Inputs: `[]`
Outputs: `[INIT_COMMITMENT]` | Any | -| `compute_current_commitment` | Computes and returns the account commitment from account data stored in memory.

Inputs: `[]`
Outputs: `[ACCOUNT_COMMITMENT]` | Any | -| `compute_delta_commitment` | Computes the commitment to the native account's delta. Can only be called from auth procedures.

Inputs: `[]`
Outputs: `[DELTA_COMMITMENT]` | Auth | -| `get_item` | Gets an item from the account storage.

Inputs: `[index]`
Outputs: `[VALUE]` | Account | -| `get_initial_item` | Gets the initial item from the account storage slot as it was at the beginning of the transaction.

Inputs: `[index]`
Outputs: `[VALUE]` | Account | -| `set_item` | Sets an item in the account storage.

Inputs: `[index, VALUE]`
Outputs: `[OLD_VALUE]` | Native & Account | -| `get_map_item` | Returns the VALUE located under the specified KEY within the map contained in the given account storage slot.

Inputs: `[index, KEY]`
Outputs: `[VALUE]` | Account | -| `get_initial_map_item` | Gets the initial VALUE from the account storage map as it was at the beginning of the transaction.

Inputs: `[index, KEY]`
Outputs: `[VALUE]` | Account | -| `set_map_item` | Sets VALUE under the specified KEY within the map contained in the given account storage slot.

Inputs: `[index, KEY, VALUE]`
Outputs: `[OLD_MAP_ROOT, OLD_MAP_VALUE]` | Native & Account | -| `get_code_commitment` | Gets the account code commitment of the current account.

Inputs: `[]`
Outputs: `[CODE_COMMITMENT]` | Account | -| `get_initial_storage_commitment` | Returns the storage commitment of the native account at the beginning of the transaction.

Inputs: `[]`
Outputs: `[INIT_STORAGE_COMMITMENT]` | Any | -| `compute_storage_commitment` | Computes the latest account storage commitment of the current account.

Inputs: `[]`
Outputs: `[STORAGE_COMMITMENT]` | Account | -| `get_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault.

Inputs: `[faucet_id_prefix, faucet_id_suffix]`
Outputs: `[balance]` | Any | -| `get_initial_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault at the beginning of the transaction.

Inputs: `[faucet_id_prefix, faucet_id_suffix]`
Outputs: `[init_balance]` | Any | -| `has_non_fungible_asset` | Returns a boolean indicating whether the non-fungible asset is present in the current account's vault.

Inputs: `[ASSET]`
Outputs: `[has_asset]` | Any | -| `add_asset` | Adds the specified asset to the vault. For fungible assets, returns the total after addition.

Inputs: `[ASSET]`
Outputs: `[ASSET']` | Native & Account | -| `remove_asset` | Removes the specified asset from the vault.

Inputs: `[ASSET]`
Outputs: `[ASSET]` | Native & Account | -| `get_initial_vault_root` | Returns the vault root of the native account at the beginning of the transaction.

Inputs: `[]`
Outputs: `[INIT_VAULT_ROOT]` | Any | -| `get_vault_root` | Returns the vault root of the current account.

Inputs: `[]`
Outputs: `[VAULT_ROOT]` | Any | -| `was_procedure_called` | Returns 1 if a procedure was called during transaction execution, and 0 otherwise.

Inputs: `[PROC_ROOT]`
Outputs: `[was_called]` | Any | -| `get_num_procedures` | Returns the number of procedures in the current account.

Inputs: `[]`
Outputs: `[num_procedures]` | Any | -| `get_procedure_root` | Returns the procedure root for the procedure at the specified index.

Inputs: `[index]`
Outputs: `[PROC_ROOT]` | Any | +| Procedure | Description | Context | +| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `get_id` | Returns the ID of the current account.

**Inputs:** `[]`
**Outputs:** `[account_id_prefix, account_id_suffix]` | Any | +| `get_native_id` | Returns the ID of the native account of the transaction.

**Inputs:** `[]`
**Outputs:** `[account_id_prefix, account_id_suffix]` | Any | +| `get_nonce` | Returns the nonce of the current account. Always returns the initial nonce as it can only be incremented in auth procedures.

**Inputs:** `[]`
**Outputs:** `[nonce]` | Any | +| `get_native_nonce` | Returns the nonce of the native account of the transaction.

**Inputs:** `[]`
**Outputs:** `[nonce]` | Any | +| `incr_nonce` | Increments the account nonce by one and returns the new nonce. Can only be called from auth procedures.

**Inputs:** `[]`
**Outputs:** `[final_nonce]` | Auth | +| `get_initial_commitment` | Returns the native account commitment at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_COMMITMENT]` | Any | +| `compute_current_commitment` | Computes and returns the account commitment from account data stored in memory.

**Inputs:** `[]`
**Outputs:** `[ACCOUNT_COMMITMENT]` | Any | +| `compute_delta_commitment` | Computes the commitment to the native account's delta. Can only be called from auth procedures.

**Inputs:** `[]`
**Outputs:** `[DELTA_COMMITMENT]` | Auth | +| `get_item` | Gets an item from the account storage.

**Inputs:** `[index]`
**Outputs:** `[VALUE]` | Account | +| `get_initial_item` | Gets the initial item from the account storage slot as it was at the beginning of the transaction.

**Inputs:** `[index]`
**Outputs:** `[VALUE]` | Account | +| `set_item` | Sets an item in the account storage.

**Inputs:** `[index, VALUE]`
**Outputs:** `[OLD_VALUE]` | Native & Account | +| `get_map_item` | Returns the VALUE located under the specified KEY within the map contained in the given account storage slot.

**Inputs:** `[index, KEY]`
**Outputs:** `[VALUE]` | Account | +| `get_initial_map_item` | Gets the initial VALUE from the account storage map as it was at the beginning of the transaction.

**Inputs:** `[index, KEY]`
**Outputs:** `[VALUE]` | Account | +| `set_map_item` | Sets VALUE under the specified KEY within the map contained in the given account storage slot.

**Inputs:** `[index, KEY, VALUE]`
**Outputs:** `[OLD_MAP_ROOT, OLD_MAP_VALUE]` | Native & Account | +| `get_code_commitment` | Gets the account code commitment of the current account.

**Inputs:** `[]`
**Outputs:** `[CODE_COMMITMENT]` | Account | +| `get_initial_storage_commitment` | Returns the storage commitment of the native account at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_STORAGE_COMMITMENT]` | Any | +| `compute_storage_commitment` | Computes the latest account storage commitment of the current account.

**Inputs:** `[]`
**Outputs:** `[STORAGE_COMMITMENT]` | Account | +| `get_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[balance]` | Any | +| `get_initial_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault at the beginning of the transaction.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[init_balance]` | Any | +| `has_non_fungible_asset` | Returns a boolean indicating whether the non-fungible asset is present in the current account's vault.

**Inputs:** `[ASSET]`
**Outputs:** `[has_asset]` | Any | +| `add_asset` | Adds the specified asset to the vault. For fungible assets, returns the total after addition.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET']` | Native & Account | +| `remove_asset` | Removes the specified asset from the vault.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account | +| `get_initial_vault_root` | Returns the vault root of the native account at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_VAULT_ROOT]` | Any | +| `get_vault_root` | Returns the vault root of the current account.

**Inputs:** `[]`
**Outputs:** `[VAULT_ROOT]` | Any | +| `was_procedure_called` | Returns 1 if a procedure was called during transaction execution, and 0 otherwise.

**Inputs:** `[PROC_ROOT]`
**Outputs:** `[was_called]` | Any | +| `get_num_procedures` | Returns the number of procedures in the current account.

**Inputs:** `[]`
**Outputs:** `[num_procedures]` | Any | +| `get_procedure_root` | Returns the procedure root for the procedure at the specified index.

**Inputs:** `[index]`
**Outputs:** `[PROC_ROOT]` | Any | ## Active Note Procedures (`miden::active_note`) Active note procedures can be used to fetch data from the note that is currently being processed by the transaction kernel. -| Procedure | Description | Context | -| --- | --- | --- | -| `get_assets` | Writes the [assets](note.md#assets) of the active note into memory starting at the specified address.

Inputs: `[dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Note | -| `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the active note.

Inputs: `[]`
Outputs: `[RECIPIENT]` | Note | -| `get_inputs` | Writes the note's [inputs](note.md#inputs) to the specified memory address.

Inputs: `[dest_ptr]`
Outputs: `[num_inputs, dest_ptr]` | Note | -| `get_metadata` | Returns the [metadata](note.md#metadata) of the active note.

Inputs: `[]`
Outputs: `[METADATA]` | Note | -| `get_sender` | Returns the sender of the active note.

Inputs: `[]`
Outputs: `[sender_id_prefix, sender_id_suffix]` | Note | -| `get_serial_number` | Returns the [serial number](note.md#serial-number) of the active note.

Inputs: `[]`
Outputs: `[SERIAL_NUMBER]` | Note | -| `get_script_root` | Returns the [script root](note.md#script) of the active note.

Inputs: `[]`
Outputs: `[SCRIPT_ROOT]` | Note | -| `add_assets_to_account` | Adds all assets from the active note to the account vault.

Inputs: `[]`
Outputs: `[]` | Note | +| Procedure | Description | Context | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `get_assets` | Writes the [assets](note.md#assets) of the active note into memory starting at the specified address.

**Inputs:** `[dest_ptr]`
**Outputs:** `[num_assets, dest_ptr]` | Note | +| `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the active note.

**Inputs:** `[]`
**Outputs:** `[RECIPIENT]` | Note | +| `get_inputs` | Writes the note's [inputs](note.md#inputs) to the specified memory address.

**Inputs:** `[dest_ptr]`
**Outputs:** `[num_inputs, dest_ptr]` | Note | +| `get_metadata` | Returns the [metadata](note.md#metadata) of the active note.

**Inputs:** `[]`
**Outputs:** `[METADATA]` | Note | +| `get_sender` | Returns the sender of the active note.

**Inputs:** `[]`
**Outputs:** `[sender_id_prefix, sender_id_suffix]` | Note | +| `get_serial_number` | Returns the [serial number](note.md#serial-number) of the active note.

**Inputs:** `[]`
**Outputs:** `[SERIAL_NUMBER]` | Note | +| `get_script_root` | Returns the [script root](note.md#script) of the active note.

**Inputs:** `[]`
**Outputs:** `[SCRIPT_ROOT]` | Note | +| `add_assets_to_account` | Adds all assets from the active note to the account vault.

**Inputs:** `[]`
**Outputs:** `[]` | Note | ## Input Note Procedures (`miden::input_note`) Input note procedures can be used to fetch data on input notes consumed by the transaction. -| Procedure | Description | Context | -| --- | --- | --- | -| `get_assets_info` | Returns the information about [assets](note.md#assets) in the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[ASSETS_COMMITMENT, num_assets]` | Any | -| `get_assets` | Writes the [assets](note.md#assets) of the input note with the specified index into memory starting at the specified address.

Inputs: `[dest_ptr, note_index]`
Outputs: `[num_assets, dest_ptr, note_index]` | Any | -| `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[RECIPIENT]` | Any | -| `get_metadata` | Returns the [metadata](note.md#metadata) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[METADATA]` | Any | -| `get_sender` | Returns the sender of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[sender_id_prefix, sender_id_suffix]` | Any | -| `get_inputs_info` | Returns the [inputs](note.md#inputs) commitment and length of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[NOTE_INPUTS_COMMITMENT, num_inputs]` | Any | -| `get_script_root` | Returns the [script root](note.md#script) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[SCRIPT_ROOT]` | Any | -| `get_serial_number` | Returns the [serial number](note.md#serial-number) of the input note with the specified index.

Inputs: `[note_index]`
Outputs: `[SERIAL_NUMBER]` | Any | +| Procedure | Description | Context | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | +| `get_assets_info` | Returns the information about [assets](note.md#assets) in the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[ASSETS_COMMITMENT, num_assets]` | Any | +| `get_assets` | Writes the [assets](note.md#assets) of the input note with the specified index into memory starting at the specified address.

**Inputs:** `[dest_ptr, note_index]`
**Outputs:** `[num_assets, dest_ptr, note_index]` | Any | +| `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[RECIPIENT]` | Any | +| `get_metadata` | Returns the [metadata](note.md#metadata) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[METADATA]` | Any | +| `get_sender` | Returns the sender of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[sender_id_prefix, sender_id_suffix]` | Any | +| `get_inputs_info` | Returns the [inputs](note.md#inputs) commitment and length of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[NOTE_INPUTS_COMMITMENT, num_inputs]` | Any | +| `get_script_root` | Returns the [script root](note.md#script) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[SCRIPT_ROOT]` | Any | +| `get_serial_number` | Returns the [serial number](note.md#serial-number) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[SERIAL_NUMBER]` | Any | ## Output Note Procedures (`miden::output_note`) Output note procedures can be used to fetch data on output notes created by the transaction. -| Procedure | Description | Context | -| --- | --- | --- | -| `create` | Creates a new output note and returns its index.

Inputs: `[tag, aux, note_type, execution_hint, RECIPIENT]`
Outputs: `[note_idx]` | Native & Account | -| `get_assets_info` | Returns the information about assets in the output note with the specified index.

Inputs: `[note_index]`
Outputs: `[ASSETS_COMMITMENT, num_assets]` | Any | -| `get_assets` | Writes the assets of the output note with the specified index into memory starting at the specified address.

Inputs: `[dest_ptr, note_index]`
Outputs: `[num_assets, dest_ptr, note_index]` | Any | -| `add_asset` | Adds the `ASSET` to the output note specified by the index.

Inputs: `[ASSET, note_idx]`
Outputs: `[ASSET, note_idx]` | Native | -| `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the output note with the specified index.

Inputs: `[note_index]`
Outputs: `[RECIPIENT]` | Any | -| `get_metadata` | Returns the [metadata](note.md#metadata) of the output note with the specified index.

Inputs: `[note_index]`
Outputs: `[METADATA]` | Any | +| Procedure | Description | Context | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `create` | Creates a new output note and returns its index.

**Inputs:** `[tag, aux, note_type, execution_hint, RECIPIENT]`
**Outputs:** `[note_idx]` | Native & Account | +| `get_assets_info` | Returns the information about assets in the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[ASSETS_COMMITMENT, num_assets]` | Any | +| `get_assets` | Writes the assets of the output note with the specified index into memory starting at the specified address.

**Inputs:** `[dest_ptr, note_index]`
**Outputs:** `[num_assets, dest_ptr, note_index]` | Any | +| `add_asset` | Adds the `ASSET` to the output note specified by the index.

**Inputs:** `[ASSET, note_idx]`
**Outputs:** `[ASSET, note_idx]` | Native | +| `get_recipient` | Returns the [recipient](note#note-recipient-restricting-consumption) of the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[RECIPIENT]` | Any | +| `get_metadata` | Returns the [metadata](note#metadata) of the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[METADATA]` | Any | ## Note Utility Procedures (`miden::note`) Note utility procedures can be used to compute the required utility data or write note data to memory. -| Procedure | Description | Context | -| --- | --- | --- | -| `compute_inputs_commitment` | Computes the commitment to the output note inputs starting at the specified memory address.

Inputs: `[inputs_ptr, num_inputs]`
Outputs: `[INPUTS_COMMITMENT]` | Any | -| `get_max_inputs_per_note` | Returns the max allowed number of input values per note.

Inputs: `[]`
Outputs: `[max_inputs_per_note]` | Any | -| `write_assets_to_memory` | Writes the assets data stored in the advice map to the memory specified by the provided destination pointer.

Inputs: `[ASSETS_COMMITMENT, num_assets, dest_ptr]`
Outputs: `[num_assets, dest_ptr]` | Any | -| `build_recipient_hash` | Returns the `RECIPIENT` for a specified `SERIAL_NUM`, `SCRIPT_ROOT`, and inputs commitment.

Inputs: `[SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT]`
Outputs: `[RECIPIENT]` | Any | -| `build_recipient` | Builds the recipient hash from note inputs, script root, and serial number.

Inputs: `[inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]`
Outputs: `[RECIPIENT]` | Any | -| `extract_sender_from_metadata` | Extracts the sender ID from the provided metadata word.

Inputs: `[METADATA]`
Outputs: `[sender_id_prefix, sender_id_suffix]` | Any | +| Procedure | Description | Context | +| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `compute_inputs_commitment` | Computes the commitment to the output note inputs starting at the specified memory address.

**Inputs:** `[inputs_ptr, num_inputs]`
**Outputs:** `[INPUTS_COMMITMENT]` | Any | +| `get_max_inputs_per_note` | Returns the max allowed number of input values per note.

**Inputs:** `[]`
**Outputs:** `[max_inputs_per_note]` | Any | +| `write_assets_to_memory` | Writes the assets data stored in the advice map to the memory specified by the provided destination pointer.

**Inputs:** `[ASSETS_COMMITMENT, num_assets, dest_ptr]`
**Outputs:** `[num_assets, dest_ptr]` | Any | +| `build_recipient_hash` | Returns the `RECIPIENT` for a specified `SERIAL_NUM`, `SCRIPT_ROOT`, and inputs commitment.

**Inputs:** `[SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT]`
**Outputs:** `[RECIPIENT]` | Any | +| `build_recipient` | Builds the recipient hash from note inputs, script root, and serial number.

**Inputs:** `[inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]`
**Outputs:** `[RECIPIENT]` | Any | +| `extract_sender_from_metadata` | Extracts the sender ID from the provided metadata word.

**Inputs:** `[METADATA]`
**Outputs:** `[sender_id_prefix, sender_id_suffix]` | Any | ## Transaction Procedures (`miden::tx`) Transaction procedures manage transaction-level operations including note creation, context switching, and reading transaction metadata. -| Procedure | Description | Context | -| --- | --- | --- | -| `get_block_number` | Returns the block number of the transaction reference block.

Inputs: `[]`
Outputs: `[num]` | Any | -| `get_block_commitment` | Returns the block commitment of the reference block.

Inputs: `[]`
Outputs: `[BLOCK_COMMITMENT]` | Any | -| `get_block_timestamp` | Returns the timestamp of the reference block for this transaction.

Inputs: `[]`
Outputs: `[timestamp]` | Any | -| `get_input_notes_commitment` | Returns the input notes commitment hash.

Inputs: `[]`
Outputs: `[INPUT_NOTES_COMMITMENT]` | Any | -| `get_output_notes_commitment` | Returns the output notes commitment hash.

Inputs: `[]`
Outputs: `[OUTPUT_NOTES_COMMITMENT]` | Any | -| `get_num_input_notes` | Returns the total number of input notes consumed by this transaction.

Inputs: `[]`
Outputs: `[num_input_notes]` | Any | -| `get_num_output_notes` | Returns the current number of output notes created in this transaction.

Inputs: `[]`
Outputs: `[num_output_notes]` | Any | -| `execute_foreign_procedure` | Executes the provided procedure against the foreign account.

Inputs: `[foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, , pad(n)]`
Outputs: `[]` | Any | -| `get_expiration_block_delta` | Returns the transaction expiration delta, or 0 if not set.

Inputs: `[]`
Outputs: `[block_height_delta]` | Any | -| `update_expiration_block_delta` | Updates the transaction expiration delta.

Inputs: `[block_height_delta]`
Outputs: `[]` | Any | +| Procedure | Description | Context | +| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `get_block_number` | Returns the block number of the transaction reference block.

**Inputs:** `[]`
**Outputs:** `[num]` | Any | +| `get_block_commitment` | Returns the block commitment of the reference block.

**Inputs:** `[]`
**Outputs:** `[BLOCK_COMMITMENT]` | Any | +| `get_block_timestamp` | Returns the timestamp of the reference block for this transaction.

**Inputs:** `[]`
**Outputs:** `[timestamp]` | Any | +| `get_input_notes_commitment` | Returns the input notes commitment hash.

**Inputs:** `[]`
**Outputs:** `[INPUT_NOTES_COMMITMENT]` | Any | +| `get_output_notes_commitment` | Returns the output notes commitment hash.

**Inputs:** `[]`
**Outputs:** `[OUTPUT_NOTES_COMMITMENT]` | Any | +| `get_num_input_notes` | Returns the total number of input notes consumed by this transaction.

**Inputs:** `[]`
**Outputs:** `[num_input_notes]` | Any | +| `get_num_output_notes` | Returns the current number of output notes created in this transaction.

**Inputs:** `[]`
**Outputs:** `[num_output_notes]` | Any | +| `execute_foreign_procedure` | Executes the provided procedure against the foreign account.

**Inputs:** `[foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, , pad(n)]`
**Outputs:** `[]` | Any | +| `get_expiration_block_delta` | Returns the transaction expiration delta, or 0 if not set.

**Inputs:** `[]`
**Outputs:** `[block_height_delta]` | Any | +| `update_expiration_block_delta` | Updates the transaction expiration delta.

**Inputs:** `[block_height_delta]`
**Outputs:** `[]` | Any | ## Faucet Procedures (`miden::faucet`) Faucet procedures allow reading and writing to faucet accounts to mint and burn assets. -| Procedure | Description | Context | -| --- | --- | --- | -| `create_fungible_asset` | Creates a fungible asset for the faucet the transaction is being executed against.

Inputs: `[amount]`
Outputs: `[ASSET]` | Faucet | -| `create_non_fungible_asset` | Creates a non-fungible asset for the faucet the transaction is being executed against.

Inputs: `[DATA_HASH]`
Outputs: `[ASSET]` | Faucet | -| `mint` | Mint an asset from the faucet the transaction is being executed against.

Inputs: `[ASSET]`
Outputs: `[ASSET]` | Native & Account & Faucet | -| `burn` | Burn an asset from the faucet the transaction is being executed against.

Inputs: `[ASSET]`
Outputs: `[ASSET]` | Native & Account & Faucet | -| `get_total_issuance` | Returns the total issuance of the fungible faucet the transaction is being executed against.

Inputs: `[]`
Outputs: `[total_issuance]` | Faucet | -| `is_non_fungible_asset_issued` | Returns a boolean indicating whether the provided non-fungible asset has been already issued by this faucet.

Inputs: `[ASSET]`
Outputs: `[is_issued]` | Faucet | +| Procedure | Description | Context | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `create_fungible_asset` | Creates a fungible asset for the faucet the transaction is being executed against.

**Inputs:** `[amount]`
**Outputs:** `[ASSET]` | Faucet | +| `create_non_fungible_asset` | Creates a non-fungible asset for the faucet the transaction is being executed against.

**Inputs:** `[DATA_HASH]`
**Outputs:** `[ASSET]` | Faucet | +| `mint` | Mint an asset from the faucet the transaction is being executed against.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account & Faucet | +| `burn` | Burn an asset from the faucet the transaction is being executed against.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account & Faucet | +| `get_total_issuance` | Returns the total issuance of the fungible faucet the transaction is being executed against.

**Inputs:** `[]`
**Outputs:** `[total_issuance]` | Faucet | +| `is_non_fungible_asset_issued` | Returns a boolean indicating whether the provided non-fungible asset has been already issued by this faucet.

**Inputs:** `[ASSET]`
**Outputs:** `[is_issued]` | Faucet | ## Asset Procedures (`miden::asset`) Asset procedures provide utilities for creating fungible and non-fungible assets. -| Procedure | Description | Context | -| --- | --- | --- | -| `build_fungible_asset` | Builds a fungible asset for the specified fungible faucet and amount.

Inputs: `[faucet_id_prefix, faucet_id_suffix, amount]`
Outputs: `[ASSET]` | Any | -| `build_non_fungible_asset` | Builds a non-fungible asset for the specified non-fungible faucet and data hash.

Inputs: `[faucet_id_prefix, DATA_HASH]`
Outputs: `[ASSET]` | Any | +| Procedure | Description | Context | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `build_fungible_asset` | Builds a fungible asset for the specified fungible faucet and amount.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix, amount]`
**Outputs:** `[ASSET]` | Any | +| `build_non_fungible_asset` | Builds a non-fungible asset for the specified non-fungible faucet and data hash.

**Inputs:** `[faucet_id_prefix, DATA_HASH]`
**Outputs:** `[ASSET]` | Any | diff --git a/docs/src/state.md b/docs/src/state.md index 10dd4818f3..6257959f54 100644 --- a/docs/src/state.md +++ b/docs/src/state.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 6 +--- + # State The `State` describes the current condition of all accounts, notes, nullifiers and their statuses. Reflecting the "current reality" of the protocol at any given time. @@ -25,8 +29,8 @@ The Miden node maintains three databases to describe `State`: 2. Notes 3. Nullifiers -

- State +

+ State

### Account database @@ -38,11 +42,11 @@ The accounts database has two main purposes: This is done using an authenticated data structure, a sparse Merkle tree. -

- Account DB +

+ Account DB

-As described in the [account ID section](account/id.md#account-storage-mode), accounts can have different storage modes: +As described in the [account ID section](account/id#account-storage-mode), accounts can have different storage modes: - **Public & Network accounts:** where all account data is stored on-chain. - **Private accounts:** where only the commitments to the account is stored on-chain. @@ -51,12 +55,13 @@ Private accounts significantly reduce storage overhead. A private account contri The storage contribution of a public account depends on the amount of data it stores. -> [!Warning] -> In Miden, when the user is the custodian of their account `State` (in the case of a private account), losing this `State` amounts to losing their funds, similar to losing a private key. +:::warning +In Miden, when the user is the custodian of their account `State` (in the case of a private account), losing this `State` amounts to losing their funds, similar to losing a private key. +::: ### Note database -As described in the [notes section](note.md), there are two types of notes: +As described in the [notes section](note), there are two types of notes: - **Public notes:** where the entire note content is stored on-chain. - **Private notes:** where only the note’s commitment is stored on-chain. @@ -72,21 +77,22 @@ Using a Merkle Mountain Range (append-only accumulator) is important for two rea Both of these properties are needed for supporting local transactions using client-side proofs and privacy. In an append-only data structure, witness data does not become stale when the data structure is updated. That means users can generate valid proofs even if they don’t have the latest `State` of this database; so there is no need to query the operator on a constantly changing `State`. -

- Note DB +

+ Note DB

### Nullifier database -Each [note](note.md) has an associated nullifier which enables the tracking of whether its associated note has been consumed or not, preventing double-spending. +Each [note](note) has an associated nullifier which enables the tracking of whether its associated note has been consumed or not, preventing double-spending. To prove that a note has not been consumed, the operator must provide a Merkle path to the corresponding node and show that the node’s value is 0. Since nullifiers are 32 bytes each, the sparse Merkle tree height must be sufficient to represent all possible nullifiers. Operators must maintain the entire nullifier set to compute the new tree root after inserting new nullifiers. For each nullifier we also record the block in which it was created. This way "unconsumed" nullifiers have block 0, but all consumed nullifiers have a non-zero block. -> [!Note] -> Nullifiers in Miden break linkability between privately stored notes and their consumption details. To know the [note’s nullifier](note.md#note-nullifier-ensuring-private-consumption), one must know the note’s data. +:::note +Nullifiers in Miden break linkability between privately stored notes and their consumption details. To know the [note's nullifier](note#note-nullifier-ensuring-private-consumption), one must know the note's data. +::: -

- Nullifier DB +

+ Nullifier DB

## Additional information @@ -95,8 +101,8 @@ To prove that a note has not been consumed, the operator must provide a Merkle p In most blockchains, most smart contracts and decentralized applications (e.g., AAVE, Uniswap) need public shared `State`. Public shared `State` is also available on Miden and can be represented as in the following example: -

- Example: AMM transactions +

+ Example: AMM transactions

In this diagram, multiple participants interact with a common, publicly accessible `State` (the AMM in the center). The figure illustrates how notes are created and consumed: diff --git a/docs/src/transaction.md b/docs/src/transaction.md index e9da19ad0e..d137fcb4dd 100644 --- a/docs/src/transaction.md +++ b/docs/src/transaction.md @@ -1,14 +1,18 @@ +--- +sidebar_position: 5 +--- + # Transactions -A `Transaction` in Miden is the state transition of a single account. A `Transaction` takes as input a single [account](account/overview.md) and zero or more [notes](note.md), and outputs the same account with an updated state, together with zero or more notes. Transactions in Miden are Miden VM programs, their execution resulting in the generation of a zero-knowledge proof. +A `Transaction` in Miden is the state transition of a single account. A `Transaction` takes as input a single [account](account/overview) and zero or more [notes](note), and outputs the same account with an updated state, together with zero or more notes. Transactions in Miden are Miden VM programs, their execution resulting in the generation of a zero-knowledge proof. Miden's `Transaction` model aims for the following: - **Parallel transaction execution**: Accounts can update their state independently from each other and in parallel. - **Private transaction execution**: Client-side `Transaction` proving allows the network to verify transactions validity with zero-knowledge. -

- Transaction diagram +

+ Transaction diagram

Compared to most blockchains, where a `Transaction` typically involves more than one account (e.g., sender and receiver), a `Transaction` in Miden involves a single account. To illustrate, Alice sends 5 ETH to Bob. In Miden, sending 5 ETH from Alice to Bob takes two transactions, one in which Alice creates a note containing 5 ETH and one in which Bob consumes that note and receives the 5 ETH. This model removes the need for a global lock on the blockchain's state, enabling Miden to process transactions in parallel. @@ -21,8 +25,8 @@ A simple transaction currently takes about 1-2 seconds on a MacBook Pro. It take Every `Transaction` describes the process of an account changing its state. This process is described as a Miden VM program, resulting in the generation of a zero-knowledge proof. Transactions are being executed in a specified sequence, in which several notes and a transaction script can interact with an account. -

- Transaction program +

+ Transaction program

### Inputs @@ -39,15 +43,15 @@ A `Transaction` requires several inputs: ### Flow 1. **Prologue** - Executes at the beginning of a transaction. It validates on-chain commitments against the provided data. This is to ensure that the transaction executes against a valid on-chain recorded state of the account and to be consumed notes. Notes to be consumed must be registered on-chain — except for [erasable notes](note.md) which can be consumed without block inclusion. + Executes at the beginning of a transaction. It validates on-chain commitments against the provided data. This is to ensure that the transaction executes against a valid on-chain recorded state of the account and to be consumed notes. Notes to be consumed must be registered on-chain — except for [erasable notes](note) which can be consumed without block inclusion. 2. **Note processing** - Notes are executed sequentially against the account, following a sequence defined by the executor. To execute a note means processing the note script that calls methods exposed on the account interface. Notes must be consumed fully, which means that all assets must be transferred into the account or into other created notes. [Note scripts](note.md#script) can invoke the account interface during execution. They can push assets into the account's vault, create new notes, set a transaction expiration, and read from or write to the account’s storage. Any method they call must be explicitly exposed by the account interface. Note scripts can also invoke methods of foreign accounts to read their state. + Notes are executed sequentially against the account, following a sequence defined by the executor. To execute a note means processing the note script that calls methods exposed on the account interface. Notes must be consumed fully, which means that all assets must be transferred into the account or into other created notes. [Note scripts](note#script) can invoke the account interface during execution. They can push assets into the account's vault, create new notes, set a transaction expiration, and read from or write to the account's storage. Any method they call must be explicitly exposed by the account interface. Note scripts can also invoke methods of foreign accounts to read their state. 3. **Transaction script processing** `Transaction` scripts are an optional piece of code defined by the executor which interacts with account methods after all notes have been executed. For example, `Transaction` scripts can be used to sign the `Transaction` (e.g., sign the transaction by incrementing the nonce of the account, without which, the transaction would fail), to mint tokens from a faucet, create notes, or modify account storage. `Transaction` scripts can also invoke methods of foreign accounts to read their state. 4. **Epilogue** Completes the execution, resulting in an updated account state and a generated zero-knowledge proof. The validity of the resulting transaction is ensured by a combination of user-defined and protocol-defined checks: - - The account's [authentication procedure](account/code.md#authentication) is called to authorize the transaction. - - The transaction fee is computed and removed from the account's vault in the chain's native asset. See [Fees](./fees.md). + - The account's [authentication procedure](account/code#authentication) is called to authorize the transaction. + - The transaction fee is computed and removed from the account's vault in the chain's native asset. See [Fees](./fees). - The account's state must have changed, or at least one input note must have been consumed to make the transaction non-empty. - If the account's state has changed, the `nonce` must have been incremented to prevent replay attacks. - Additionally, the sum of all input assets must be equal to the sum of all output assets (if the account is not a faucet). @@ -60,7 +64,7 @@ To illustrate the `Transaction` protocol, we provide two examples for a basic `T ### Creating a P2ID note -Let's assume account A wants to create a P2ID note. P2ID notes are pay-to-ID notes that can only be consumed by a specified target account ID. Note creators can provide the target account ID using the [note inputs](note.md#inputs). +Let's assume account A wants to create a P2ID note. P2ID notes are pay-to-ID notes that can only be consumed by a specified target account ID. Note creators can provide the target account ID using the [note inputs](note#inputs). In this example, account A uses the basic wallet and the authentication component provided by `miden-lib`. The basic wallet component defines the methods `wallets::basic::create_note` and `wallets::basic::move_asset_to_note` to create notes with assets, and `wallets::basic::receive_asset` to receive assets. The authentication component exposes `auth::basic::auth_tx_rpo_falcon512` which allows for signing a transaction. Some account methods like `account::get_id` are always exposed. @@ -114,23 +118,25 @@ The ability to facilitate both, local and network transactions, **is one of the --- -> [!Tip] -> -> - Usually, notes that are consumed in a `Transaction` must be recorded on-chain in order for the `Transaction` to succeed. However, Miden supports **erasable notes** which are notes that can be consumed in a `Transaction` before being registered on-chain. For example, one can build a sub-second order book by allowing its traders to build faster transactions that depend on each other and are being validated or erased in batches. -> -> - There is no nullifier check during a `Transaction`. Nullifiers are checked by the Miden operator during `Transaction` verification. So at the local level, there is "double spending." If a note was already spent, i.e. there exists a nullifier for that note, the block producer would never include the `Transaction` as it would make the block invalid. -> -> - One of the main reasons for separating execution and proving steps is to allow _stateless provers_; i.e., the executed `Transaction` has all the data it needs to re-execute and prove a `Transaction` without database access. This supports easier proof-generation distribution. -> -> - Not all transactions require notes. For example, the owner of a faucet can mint new tokens using only a `Transaction` script, without interacting with external notes. -> -> - In Miden executors can choose arbitrary reference blocks to execute against their state. Hence it is possible to set `Transaction` expiration heights and in doing so, to define a block height until a `Transaction` should be included into a block. If the `Transaction` is expired, the resulting account state change is not valid and the `Transaction` cannot be verified anymore. -> -> - Note and `Transaction` scripts can read the state of foreign accounts during execution. This is called foreign procedure invocation. For example, the price of an asset for the **Swap** script might depend on a certain value stored in the oracle account. -> -> - An example of the right usage of `Transaction` arguments is the consumption of a **Swap** note. Those notes allow asset exchange based on predefined conditions. Example: -> - The note's consumption condition is defined as "anyone can consume this note to take `X` units of asset A if they simultaneously create a note sending Y units of asset B back to the creator." If an executor wants to buy only a fraction `(X-m)` of asset A, they provide this amount via transaction arguments. The executor would provide the value `m`. The note script then enforces the correct transfer: -> - A new note is created returning `Y-((m*Y)/X)` of asset B to the sender. -> - A second note is created, holding the remaining `(X-m)` of asset A for future consumption. -> -> - When executing a `Transaction` the max number of VM cycles is **$2^{30}$**. +:::tip + +- Usually, notes that are consumed in a `Transaction` must be recorded on-chain in order for the `Transaction` to succeed. However, Miden supports **erasable notes** which are notes that can be consumed in a `Transaction` before being registered on-chain. For example, one can build a sub-second order book by allowing its traders to build faster transactions that depend on each other and are being validated or erased in batches. + +- There is no nullifier check during a `Transaction`. Nullifiers are checked by the Miden operator during `Transaction` verification. So at the local level, there is "double spending." If a note was already spent, i.e. there exists a nullifier for that note, the block producer would never include the `Transaction` as it would make the block invalid. + +- One of the main reasons for separating execution and proving steps is to allow _stateless provers_; i.e., the executed `Transaction` has all the data it needs to re-execute and prove a `Transaction` without database access. This supports easier proof-generation distribution. + +- Not all transactions require notes. For example, the owner of a faucet can mint new tokens using only a `Transaction` script, without interacting with external notes. + +- In Miden executors can choose arbitrary reference blocks to execute against their state. Hence it is possible to set `Transaction` expiration heights and in doing so, to define a block height until a `Transaction` should be included into a block. If the `Transaction` is expired, the resulting account state change is not valid and the `Transaction` cannot be verified anymore. + +- Note and `Transaction` scripts can read the state of foreign accounts during execution. This is called foreign procedure invocation. For example, the price of an asset for the **Swap** script might depend on a certain value stored in the oracle account. + +- An example of the right usage of `Transaction` arguments is the consumption of a **Swap** note. Those notes allow asset exchange based on predefined conditions. Example: + - The note's consumption condition is defined as "anyone can consume this note to take `X` units of asset A if they simultaneously create a note sending Y units of asset B back to the creator." If an executor wants to buy only a fraction `(X-m)` of asset A, they provide this amount via transaction arguments. The executor would provide the value `m`. The note script then enforces the correct transfer: + - A new note is created returning `Y-((m*Y)/X)` of asset B to the sender. + - A second note is created, holding the remaining `(X-m)` of asset A for future consumption. + +- When executing a `Transaction` the max number of VM cycles is **$2^{30}$**. + +::: diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 0000000000..920d7a6523 --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,8 @@ +{ + // This file is not used in compilation. It is here just for a nice editor experience. + "extends": "@docusaurus/tsconfig", + "compilerOptions": { + "baseUrl": "." + }, + "exclude": [".docusaurus", "build"] +} From 9334c2917a9422c95e51055bd0f6f809f101e730 Mon Sep 17 00:00:00 2001 From: keinberger Date: Tue, 21 Oct 2025 15:08:22 +0300 Subject: [PATCH 097/133] chore(Makefile): add serve-docs command and implement checking for NPM dependency --- Makefile | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index b88d2d05da..2af8b9b5ab 100644 --- a/Makefile +++ b/Makefile @@ -68,9 +68,9 @@ doc: ## Generates & checks documentation $(WARNINGS) cargo doc --all-features --keep-going --release -.PHONY: book -book: ## Builds the book & serves documentation site - mdbook serve --open docs +.PHONY: serve-docs +serve-docs: ## Serves the docs + cd docs && npm run start:dev # --- testing ------------------------------------------------------------------------------------- @@ -139,7 +139,7 @@ bench-note-checker: ## Run note checker benchmarks .PHONY: check-tools check-tools: ## Checks if development tools are installed @echo "Checking development tools..." - @command -v mdbook >/dev/null 2>&1 && echo "[OK] mdbook is installed" || echo "[MISSING] mdbook is not installed (run: make install-tools)" + @command -v npm >/dev/null 2>&1 && echo "[OK] npm is installed" || echo "[MISSING] npm is not installed (run: make install-tools)" @command -v typos >/dev/null 2>&1 && echo "[OK] typos is installed" || echo "[MISSING] typos is not installed (run: make install-tools)" @command -v cargo nextest >/dev/null 2>&1 && echo "[OK] cargo-nextest is installed" || echo "[MISSING] cargo-nextest is not installed (run: make install-tools)" @command -v taplo >/dev/null 2>&1 && echo "[OK] taplo is installed" || echo "[MISSING] taplo is not installed (run: make install-tools)" @@ -147,8 +147,14 @@ check-tools: ## Checks if development tools are installed .PHONY: install-tools install-tools: ## Installs development tools required by the Makefile (mdbook, typos, nextest, taplo) - @echo "Installing development tools..." - cargo install mdbook --locked + @echo "Installing development tools..."" + @if ! command -v node >/dev/null 2>&1; then \ + echo "Node.js not found. Please install Node.js from https://nodejs.org/ or using your package manager"; \ + echo "On macOS: brew install node"; \ + echo "On Ubuntu/Debian: sudo apt install nodejs npm"; \ + echo "On Windows: Download from https://nodejs.org/"; \ + exit 1; \ + fi cargo install typos-cli --locked cargo install cargo-nextest --locked cargo install taplo-cli --locked From 30f35013651004fad0b73383718d2e81b8ddb45f Mon Sep 17 00:00:00 2001 From: keinberger Date: Tue, 21 Oct 2025 15:08:35 +0300 Subject: [PATCH 098/133] docs(README): add section explaining external documentation --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 32734f3135..60ccca9ae9 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ Miden is a zero-knowledge rollup for high-throughput and private applications. M If you want to join the technical discussion or learn more about the project, please check out -* the [Documentation](https://0xMiden.github.io/miden-docs). -* the [Telegram](https://t.me/BuildOnMiden) -* the [Repo](https://github.com/0xMiden) -* the [Roadmap](https://miden.xyz/roadmap) +- the [Documentation](https://0xMiden.github.io/miden-docs). +- the [Telegram](https://t.me/BuildOnMiden) +- the [Repo](https://github.com/0xMiden) +- the [Roadmap](https://miden.xyz/roadmap) ## Status and features @@ -44,12 +44,12 @@ Miden is currently on release v0.12. This is an early version of the protocol an ## Project structure -| Crate | Description | -|----------------------------------------------------------------|-------------------------------------------------------------------------------------| -| [objects](crates/miden-objects) | Contains core components defining the Miden rollup protocol. | -| [miden-lib](crates/miden-lib) | Contains the code of the Miden rollup kernels and standardized smart contracts. | -| [miden-tx](crates/miden-tx) | Contains tool for creating, executing, and proving Miden rollup transaction. | -| [bench-tx](bin/bench-tx) | Contains transaction execution and proving benchmarks. | +| Crate | Description | +| ------------------------------- | ------------------------------------------------------------------------------- | +| [objects](crates/miden-objects) | Contains core components defining the Miden rollup protocol. | +| [miden-lib](crates/miden-lib) | Contains the code of the Miden rollup kernels and standardized smart contracts. | +| [miden-tx](crates/miden-tx) | Contains tool for creating, executing, and proving Miden rollup transaction. | +| [bench-tx](bin/bench-tx) | Contains transaction execution and proving benchmarks. | ## Make commands @@ -69,6 +69,10 @@ make test Some of the functions in this project are computationally intensive and may take a significant amount of time to compile and complete during testing. To ensure optimal results we use the `make test` command. It enables the running of tests in release mode and using specific configurations replicates the test conditions of the development mode and verifies all debug assertions. +## Documentation + +The documentation in the `docs/` folder is built using Docusaurus and is automatically absorbed into the main [miden-docs](https://github.com/0xMiden/miden-docs) repository for the main documentation website. Changes to the `next` branch trigger an automated deployment workflow. The docs folder requires npm packages to be installed before building. + ## License This project is [MIT licensed](./LICENSE) From f34f127580f2aafa71a4770d84743f02f6bab085 Mon Sep 17 00:00:00 2001 From: keinberger Date: Tue, 21 Oct 2025 15:08:52 +0300 Subject: [PATCH 099/133] chore(docs): convert .json docusaurus category files to yml format --- .github/workflows/build-docs.yml | 46 ++++++++++++++++++++++++++++++++ docs/src/_category_.json | 5 ---- docs/src/_category_.yml | 4 +++ docs/src/account/_category_.json | 5 ---- docs/src/account/_category_.yml | 4 +++ 5 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/build-docs.yml delete mode 100644 docs/src/_category_.json create mode 100644 docs/src/_category_.yml delete mode 100644 docs/src/account/_category_.json create mode 100644 docs/src/account/_category_.yml diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 0000000000..8400212bc1 --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,46 @@ +name: build-docs + +# Limits workflow concurrency to only the latest commit in the PR. +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + +on: + push: + branches: [main, next] + paths: + - "docs/**" + - ".github/workflows/build-docs.yml" + pull_request: + types: [opened, reopened, synchronize] + paths: + - "docs/**" + - ".github/workflows/build-docs.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-docs: + name: Build Documentation + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: docs/package-lock.json + + - name: Install dependencies + working-directory: ./docs + run: npm ci + + - name: Build documentation + working-directory: ./docs + run: npm run build:dev diff --git a/docs/src/_category_.json b/docs/src/_category_.json deleted file mode 100644 index 3212502e6c..0000000000 --- a/docs/src/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Protocol", - "position": 3, - "collapsed": true -} diff --git a/docs/src/_category_.yml b/docs/src/_category_.yml new file mode 100644 index 0000000000..6a45f5651f --- /dev/null +++ b/docs/src/_category_.yml @@ -0,0 +1,4 @@ +label: "Protocol" +# Determines where this documentation section appears relative to other sections on the main documentation page (which is the parent of this folder in the miden-docs repository) +position: 3 +collapsed: true diff --git a/docs/src/account/_category_.json b/docs/src/account/_category_.json deleted file mode 100644 index bc91c53b6d..0000000000 --- a/docs/src/account/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Accounts", - "position": 2, - "collapsed": true -} diff --git a/docs/src/account/_category_.yml b/docs/src/account/_category_.yml new file mode 100644 index 0000000000..8383b5d1df --- /dev/null +++ b/docs/src/account/_category_.yml @@ -0,0 +1,4 @@ +label: "Accounts" +# Determines where this documentation section appears relative to other sections in the parent folder +position: 2 +collapsed: true From 0d55cf558ecc74aaafe182ba11f170c19527c914 Mon Sep 17 00:00:00 2001 From: keinberger Date: Tue, 21 Oct 2025 17:32:07 +0300 Subject: [PATCH 100/133] fix(docs): fix broken links --- docs/src/account/code.md | 4 ++-- docs/src/account/id.md | 2 +- docs/src/account/index.md | 12 ++++++------ docs/src/account/storage.md | 4 ++-- docs/src/asset.md | 14 +++++++------- docs/src/fees.md | 1 - docs/src/index.md | 2 +- docs/src/note.md | 23 +++++++++++++---------- docs/src/transaction.md | 4 ++-- 9 files changed, 34 insertions(+), 32 deletions(-) diff --git a/docs/src/account/code.md b/docs/src/account/code.md index 21996b9b56..2f5eedf9e5 100644 --- a/docs/src/account/code.md +++ b/docs/src/account/code.md @@ -17,7 +17,7 @@ Every Miden `Account` is essentially a smart contract. The `Code` defines the ac ## Interface -An account's code is typically the result of merging multiple [account components](./component). This results in a set of procedures that make up the _interface_ of the account. As an example, a typical wallet uses the so-called _basic wallet_ interface, which is defined in `miden::contracts::wallets::basic`. It consists of the `receive_asset` and `move_asset_to_note` procedures. If an account has this interface, i.e. this set of procedures, it can consume standard [P2ID notes](../note#p2id-pay-to-id). If it doesn't, it can't consume this type of note. So, adhering to standard interfaces such as the basic wallet will generally make an account more interoperable. +An account's code is typically the result of merging multiple [account components](./components). This results in a set of procedures that make up the _interface_ of the account. As an example, a typical wallet uses the so-called _basic wallet_ interface, which is defined in `miden::contracts::wallets::basic`. It consists of the `receive_asset` and `move_asset_to_note` procedures. If an account has this interface, i.e. this set of procedures, it can consume standard [P2ID notes](../note#p2id-pay-to-id). If it doesn't, it can't consume this type of note. So, adhering to standard interfaces such as the basic wallet will generally make an account more interoperable. ## Authentication @@ -33,7 +33,7 @@ Such an authentication procedure typically inspects the transaction and then dec - checking whether notes have been consumed. - checking whether notes have been created. -Recall that an [account's nonce](overview#nonce) must be incremented whenever its state changes. Only authentication procedures are allowed to do so, to prevent accidental or unintended authorization of state changes. +Recall that an [account's nonce](index.md#nonce) must be incremented whenever its state changes. Only authentication procedures are allowed to do so, to prevent accidental or unintended authorization of state changes. ### Procedure tracking diff --git a/docs/src/account/id.md b/docs/src/account/id.md index 84a62aace0..5dcd13b0fb 100644 --- a/docs/src/account/id.md +++ b/docs/src/account/id.md @@ -32,7 +32,7 @@ There are two main categories of accounts in Miden: **basic accounts** and **fau ### Account storage mode -Users can choose whether their accounts are stored publicly or privately. The preference is encoded in the third and fourth most significant bits of the account's [ID](#id): +Users can choose whether their accounts are stored publicly or privately. The preference is encoded in the third and fourth most significant bits of the account's ID: - **Public Accounts:** The account's state is stored on-chain, similar to how accounts are stored in public blockchains like Ethereum. diff --git a/docs/src/account/index.md b/docs/src/account/index.md index 015b81a1c6..e43aaee065 100644 --- a/docs/src/account/index.md +++ b/docs/src/account/index.md @@ -20,16 +20,16 @@ An `Account` is composed of several core parts, illustrated below: These parts are: -1. [ID](id) -2. [Code](code) -3. [Storage](storage) +1. [ID](id.md) +2. [Code](code.md) +3. [Storage](storage.md) 4. [Vault](#vault) 5. [Nonce](#nonce) ### Vault :::note -A collection of [assets](../asset) stored by the `Account`. +A collection of [assets](../asset.md) stored by the `Account`. ::: Large amounts of fungible and non-fungible assets can be stored in the account's vault. @@ -55,5 +55,5 @@ However, a user can locally create a new `Account` ID before it's recognized net 3. Alice shares the new ID with Bob to receive an asset. 4. Bob executes a transaction against his account, creating a note with assets for Alice. 5. Alice consumes Bob's note in a transaction against her new account to claim the asset. This -transaction is the first transaction against Alice's account and so it will register the account -ID in the account database. + transaction is the first transaction against Alice's account and so it will register the account + ID in the account database. diff --git a/docs/src/account/storage.md b/docs/src/account/storage.md index 9a69437d45..5c8cad6872 100644 --- a/docs/src/account/storage.md +++ b/docs/src/account/storage.md @@ -12,9 +12,9 @@ A flexible, arbitrary data store within the `Account`. The [storage](https://docs.rs/miden-objects/latest/miden_objects/account/struct.AccountStorage.html) is divided into a maximum of 255 indexed [storage slots](https://docs.rs/miden-objects/latest/miden_objects/account/enum.StorageSlot.html). Each slot can either store a 32-byte value or serve as the cryptographic root to a key-value store with the capacity to store large amounts of data. - **Value slots:** Contains 32 bytes of arbitrary data. -- **Map slots:** Contains a [StorageMap](#storagemap), a key-value store where both keys and values are 32 bytes. The slot's value is a commitment to the entire map. +- **Map slots:** Contains a [StorageMap](#map-slots), a key-value store where both keys and values are 32 bytes. The slot's value is a commitment to the entire map. -An account's storage is typically the result of merging multiple [account components](./component). +An account's storage is typically the result of merging multiple [account components](./components). ## Value Slots diff --git a/docs/src/asset.md b/docs/src/asset.md index d9177eaf61..ce0e15ec85 100644 --- a/docs/src/asset.md +++ b/docs/src/asset.md @@ -4,14 +4,14 @@ sidebar_position: 4 # Assets -An `Asset` is a unit of value that can be transferred from one [account](account/overview) to another using [notes](note). +An `Asset` is a unit of value that can be transferred from one [account](./account) to another using [notes](note). ## What is the purpose of an asset? -In Miden, assets serve as the primary means of expressing and transferring value between [accounts](account/overview) through [notes](note). They are designed with four key principles in mind: +In Miden, assets serve as the primary means of expressing and transferring value between [accounts](./account) through [notes](note). They are designed with four key principles in mind: 1. **Parallelizable exchange:** - By managing ownership and transfers directly at the account level instead of relying on global structures like ERC20 contracts, accounts can exchange assets concurrently, boosting scalability and efficiency. + By managing ownership and transfers directly at the account level instead of relying on global structures like ERC20 contracts, accounts can exchange assets concurrently, boosting scalability and efficiency. 2. **Self-sovereign ownership:** Assets are stored in the accounts directly. This ensures that users retain complete control over their assets. @@ -20,7 +20,7 @@ In Miden, assets serve as the primary means of expressing and transferring value Users can transact freely and privately with no single contract or entity controlling `Asset` transfers. This reduces the risk of censored transactions, resulting in a more open and resilient system. 4. **Fee payment in native asset:** - Transaction fees are paid in the chain's native asset as defined by the current reference block's fee parameters. See [Fees](./fees). + Transaction fees are paid in the chain's native asset as defined by the current reference block's fee parameters. See [Fees](fees.md). ## Native asset @@ -28,12 +28,12 @@ In Miden, assets serve as the primary means of expressing and transferring value All data structures following the Miden asset model that can be exchanged. ::: -Native assets adhere to the Miden `Asset` model (encoding, issuance, storage). Every native `Asset` is encoded using 32 bytes, including both the [ID](account/id) of the issuing account and the `Asset` details. +Native assets adhere to the Miden `Asset` model (encoding, issuance, storage). Every native `Asset` is encoded using 32 bytes, including both the [ID](./account/id) of the issuing account and the `Asset` details. ### Issuance :::note -Only [faucet](account/id#account-type) accounts can issue assets. +Only [faucet](./account/id#account-type) accounts can issue assets. ::: Faucets can issue either fungible or non-fungible assets as defined at account creation. The faucet's code specifies the `Asset` minting conditions: i.e., how, when, and by whom these assets can be minted. Once minted, they can be transferred to other accounts using notes. @@ -54,7 +54,7 @@ Non-fungible assets are encoded by hashing the `Asset` data into 32 bytes and pl ### Storage -[Accounts](account/overview) and [notes](note) have vaults used to store assets. Accounts use a sparse Merkle tree as a vault while notes use a simple list. This enables an account to store a practically unlimited number of assets while a note can only store 255 assets. +[Accounts](./account) and [notes](note) have vaults used to store assets. Accounts use a sparse Merkle tree as a vault while notes use a simple list. This enables an account to store a practically unlimited number of assets while a note can only store 255 assets.

Asset storage diff --git a/docs/src/fees.md b/docs/src/fees.md index ba38a75553..f6400c3284 100644 --- a/docs/src/fees.md +++ b/docs/src/fees.md @@ -1,6 +1,5 @@ --- sidebar_position: 5.1 -draft: true --- # Fees diff --git a/docs/src/index.md b/docs/src/index.md index 8f399707c7..947d63c334 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -29,7 +29,7 @@ Miden uses _accounts_ and _notes_, both of which hold assets. Accounts consume a ### Accounts -An [Account](account/overview) can hold assets and define rules how assets can be transferred. Accounts can represent users or autonomous smart contracts. The [account chapter](account/overview) describes the design of an account, its storage types, and creating an account. +An [Account](account/index.md) can hold assets and define rules how assets can be transferred. Accounts can represent users or autonomous smart contracts. The [account chapter](account/index.md) describes the design of an account, its storage types, and creating an account. ### Notes diff --git a/docs/src/note.md b/docs/src/note.md index 9fbf5eb7af..785133e78e 100644 --- a/docs/src/note.md +++ b/docs/src/note.md @@ -4,11 +4,11 @@ sidebar_position: 3 # Notes -A `Note` is the medium through which [Accounts](account/overview) communicate. A `Note` holds assets and defines how they can be consumed. +A `Note` is the medium through which [Accounts](account/index.md) communicate. A `Note` holds assets and defines how they can be consumed. ## What is the purpose of a note? -In Miden's hybrid UTXO and account-based model notes represent UTXO's which enable parallel transaction execution and privacy through asynchronous local `Note` production and consumption. +In Miden's hybrid UTXO and account-based model notes represent UTXO's which enable parallel transaction execution and privacy through asynchronous local `Note` production and consumption. ## Note core components @@ -20,10 +20,10 @@ A `Note` is composed of several core components, illustrated below: These components are: -1. [Assets](#assets) -2. [Script](#script) -3. [Inputs](#inputs) -4. [Serial number](#serial-number) +1. [Assets](#assets) +2. [Script](#script) +3. [Inputs](#inputs) +4. [Serial number](#serial-number) 5. [Metadata](#metadata) ### Assets @@ -48,7 +48,7 @@ Each `Note` has a script that defines the conditions under which it can be consu Arguments passed to the `Note` script during execution. ::: -A `Note` can have up to 128 input values, which adds up to a maximum of 1 KB of data. The `Note` script can access these inputs. They can convey arbitrary parameters for `Note` consumption. +A `Note` can have up to 128 input values, which adds up to a maximum of 1 KB of data. The `Note` script can access these inputs. They can convey arbitrary parameters for `Note` consumption. ### Serial number @@ -83,7 +83,7 @@ Accounts can create notes in a transaction. The `Note` exists if it is included #### Note storage mode -As with [accounts](account/overview), notes can be stored either publicly or privately: +As with [accounts](account/index.md), notes can be stored either publicly or privately: - **Public mode:** The `Note` data is stored in the [note database](state#note-database), making it fully visible on-chain. - **Private mode:** Only the `Note`’s hash is stored publicly. The `Note`’s actual data remains off-chain, enhancing privacy. @@ -139,7 +139,6 @@ This achieves the following properties: That means if a `Note` is private and the operator stores only the note's hash, only those with the `Note` details know if this `Note` has been consumed already. Zcash first [introduced](https://zcash.github.io/orchard/design/nullifiers.html#nullifiers) this approach. -

Nullifier diagram

@@ -153,6 +152,7 @@ The miden-base repository provides several standard note scripts that implement The P2ID note script implements a simple pay-to-account-ID pattern. It adds all assets from the note to a specific target account. **Key characteristics:** + - **Purpose:** Direct asset transfer to a specific account ID - **Inputs:** Requires exactly 2 note inputs containing the target account ID - **Validation:** Ensures the consuming account's ID matches the target account ID specified in the note @@ -165,6 +165,7 @@ The P2ID note script implements a simple pay-to-account-ID pattern. It adds all The P2IDE note script extends P2ID with additional features including time-locking and reclaim functionality. **Key characteristics:** + - **Purpose:** Advanced asset transfer with time-lock and reclaim capabilities - **Inputs:** Requires exactly 4 note inputs: - Target account ID @@ -176,6 +177,7 @@ The P2IDE note script extends P2ID with additional features including time-locki - **Requirements:** Account must expose the `miden::contracts::wallets::basic::receive_asset` procedure **Use cases:** + - Escrow-like payments with time constraints - Conditional payments that can be reclaimed if not consumed - Time-delayed transfers @@ -185,6 +187,7 @@ The P2IDE note script extends P2ID with additional features including time-locki The SWAP note script implements atomic asset swapping functionality. **Key characteristics:** + - **Purpose:** Atomic asset exchange between two parties - **Inputs:** Requires exactly 12 note inputs specifying: - Requested asset details @@ -207,4 +210,4 @@ The SWAP note script implements atomic asset swapping functionality. - **Use SWAP** for atomic asset exchanges between parties - **Create custom scripts** for specialized use cases not covered by standard types -These standard note types provide a foundation for common operations while maintaining the flexibility to create custom note scripts for specialized requirements. \ No newline at end of file +These standard note types provide a foundation for common operations while maintaining the flexibility to create custom note scripts for specialized requirements. diff --git a/docs/src/transaction.md b/docs/src/transaction.md index d137fcb4dd..b92b92f230 100644 --- a/docs/src/transaction.md +++ b/docs/src/transaction.md @@ -4,7 +4,7 @@ sidebar_position: 5 # Transactions -A `Transaction` in Miden is the state transition of a single account. A `Transaction` takes as input a single [account](account/overview) and zero or more [notes](note), and outputs the same account with an updated state, together with zero or more notes. Transactions in Miden are Miden VM programs, their execution resulting in the generation of a zero-knowledge proof. +A `Transaction` in Miden is the state transition of a single account. A `Transaction` takes as input a single [account](./account) and zero or more [notes](note), and outputs the same account with an updated state, together with zero or more notes. Transactions in Miden are Miden VM programs, their execution resulting in the generation of a zero-knowledge proof. Miden's `Transaction` model aims for the following: @@ -51,7 +51,7 @@ A `Transaction` requires several inputs: 4. **Epilogue** Completes the execution, resulting in an updated account state and a generated zero-knowledge proof. The validity of the resulting transaction is ensured by a combination of user-defined and protocol-defined checks: - The account's [authentication procedure](account/code#authentication) is called to authorize the transaction. - - The transaction fee is computed and removed from the account's vault in the chain's native asset. See [Fees](./fees). + - The transaction fee is computed and removed from the account's vault in the chain's native asset. See [Fees](fees). - The account's state must have changed, or at least one input note must have been consumed to make the transaction non-empty. - If the account's state has changed, the `nonce` must have been incremented to prevent replay attacks. - Additionally, the sum of all input assets must be equal to the sum of all output assets (if the account is not a faucet). From 4435f8db5ca573b39f40cddaf444f4aed61d7aff Mon Sep 17 00:00:00 2001 From: keinberger Date: Wed, 22 Oct 2025 12:28:01 +0300 Subject: [PATCH 101/133] chore: use specific ref for trigger-deploy-docs.yml workflow --- .github/workflows/trigger-deploy-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trigger-deploy-docs.yml b/.github/workflows/trigger-deploy-docs.yml index d6b795817e..dd8349db2d 100644 --- a/.github/workflows/trigger-deploy-docs.yml +++ b/.github/workflows/trigger-deploy-docs.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Send repository_dispatch to aggregator - uses: peter-evans/repository-dispatch@v3 + uses: peter-evans/repository-dispatch@a628c95fd17070f003ea24579a56e6bc89b25766 with: # PAT that can access the central aggregator repository token: ${{ secrets.DOCS_REPO_TOKEN }} From 5b371d4ae8f6d169623840366b2e2d5a54d111ec Mon Sep 17 00:00:00 2001 From: Marti Date: Wed, 22 Oct 2025 14:30:39 +0200 Subject: [PATCH 102/133] chore: explicitly consume both authenticated and unauthenticated note in test (#2013) * chore: explicitly try consume both authenticated and unauthenticated note * chore: update test description --- crates/miden-testing/tests/auth/multisig.rs | 51 +++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index 371efb4088..b22c39bf8b 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -21,7 +21,7 @@ use miden_objects::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, }; -use miden_objects::transaction::OutputNote; +use miden_objects::transaction::{InputNote, OutputNote}; use miden_objects::vm::AdviceMap; use miden_objects::{Felt, Hasher, Word}; use miden_processor::AdviceInputs; @@ -916,6 +916,17 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu Ok(()) } +/// Checks note consumability for authenticated and unauthenticated notes, both +/// without and with multisig signatures. +/// +/// Cases covered: +/// - Without signatures: both the authenticated note and the unauthenticated note are reported as +/// `ConsumableWithAuthorization` — the notes are consumable in principle, but fail due to missing +/// multisig authorization. +/// - With valid multisig signatures on an authenticated transaction: the authenticated note becomes +/// `Consumable`, while the unauthenticated variant remains `ConsumableWithAuthorization` because +/// signatures are bound to a different `TransactionSummary` (the authenticated context) and do +/// not authorize the unauthenticated note. #[tokio::test] async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { // Setup keys and authenticators @@ -951,13 +962,28 @@ async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { let notes_checker = NoteConsumptionChecker::new(&tx_executor); + let note_authenticated = + tx_context_without_signatures.tx_inputs().input_notes().get_note(0).clone(); + assert_matches!(note_authenticated, InputNote::Authenticated { .. }); + // this check should return `ConsumableWithAuthorization` variant: the note is consumable, but // authentication is failing + let consumable_with_authorization = notes_checker + .can_consume(multisig_account.id(), block_ref, note_authenticated.clone(), tx_args.clone()) + .await?; + assert_matches!( + consumable_with_authorization, + NoteConsumptionStatus::ConsumableWithAuthorization + ); + // trying to consume the same note but as Unauthenticated should still return + // `ConsumableWithAuthorization` variant. let consumable_with_authorization = notes_checker .can_consume( multisig_account.id(), block_ref, - miden_objects::transaction::InputNote::Unauthenticated { note: p2id_note.clone() }, + miden_objects::transaction::InputNote::Unauthenticated { + note: note_authenticated.note().clone(), + }, tx_args.clone(), ) .await?; @@ -1005,11 +1031,30 @@ async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { // this check should return `Consumable` variant: we provided the signatures, so the transaction // should execute successfully. + let note_authenticated = notes.get_note(0).clone(); + assert_matches!(note_authenticated, InputNote::Authenticated { .. }); + let consumable_with_authorization = notes_checker - .can_consume(multisig_account.id(), block_num, notes.get_note(0).clone(), tx_args) + .can_consume(multisig_account.id(), block_num, note_authenticated.clone(), tx_args.clone()) .await?; assert_matches!(consumable_with_authorization, NoteConsumptionStatus::Consumable); + // trying to consume the same note but as Unauthenticated should return + // `ConsumableWithAuthorization` variant, since the signatures are provided on a different + // `TransactionSummary` (on a transaction with authenticated note) + let consumable_with_authorization = notes_checker + .can_consume( + multisig_account.id(), + block_num, + InputNote::Unauthenticated { note: note_authenticated.note().clone() }, + tx_args, + ) + .await?; + assert_matches!( + consumable_with_authorization, + NoteConsumptionStatus::ConsumableWithAuthorization + ); + Ok(()) } From e476e359bb4d1567dbda766d9920b11567432c68 Mon Sep 17 00:00:00 2001 From: keinberger Date: Wed, 22 Oct 2025 19:56:41 +0300 Subject: [PATCH 103/133] docs: implement custom styling --- docs/docusaurus.config.ts | 98 ++ docs/package-lock.json | 502 ++++++++ docs/package.json | 5 +- docs/src/theme/Admonition/Icon/Danger.tsx | 19 + docs/src/theme/Admonition/Icon/Info.tsx | 20 + docs/src/theme/Admonition/Icon/Note.tsx | 20 + docs/src/theme/Admonition/Icon/Tip.tsx | 20 + docs/src/theme/Admonition/Icon/Warning.tsx | 20 + docs/src/theme/Admonition/Layout/index.tsx | 51 + .../theme/Admonition/Layout/styles.module.css | 35 + docs/src/theme/Admonition/Type/Caution.tsx | 32 + docs/src/theme/Admonition/Type/Danger.tsx | 30 + docs/src/theme/Admonition/Type/Info.tsx | 30 + docs/src/theme/Admonition/Type/Note.tsx | 30 + docs/src/theme/Admonition/Type/Tip.tsx | 30 + docs/src/theme/Admonition/Type/Warning.tsx | 30 + docs/src/theme/Admonition/Types.tsx | 31 + docs/src/theme/Admonition/index.tsx | 21 + docs/static/img/custom_caret.svg | 3 + docs/static/img/favicon.ico | Bin 0 -> 15406 bytes docs/static/img/logo.png | Bin 0 -> 4335 bytes docs/styles.css | 1046 +++++++++++++++++ 22 files changed, 2071 insertions(+), 2 deletions(-) create mode 100644 docs/src/theme/Admonition/Icon/Danger.tsx create mode 100644 docs/src/theme/Admonition/Icon/Info.tsx create mode 100644 docs/src/theme/Admonition/Icon/Note.tsx create mode 100644 docs/src/theme/Admonition/Icon/Tip.tsx create mode 100644 docs/src/theme/Admonition/Icon/Warning.tsx create mode 100644 docs/src/theme/Admonition/Layout/index.tsx create mode 100644 docs/src/theme/Admonition/Layout/styles.module.css create mode 100644 docs/src/theme/Admonition/Type/Caution.tsx create mode 100644 docs/src/theme/Admonition/Type/Danger.tsx create mode 100644 docs/src/theme/Admonition/Type/Info.tsx create mode 100644 docs/src/theme/Admonition/Type/Note.tsx create mode 100644 docs/src/theme/Admonition/Type/Tip.tsx create mode 100644 docs/src/theme/Admonition/Type/Warning.tsx create mode 100644 docs/src/theme/Admonition/Types.tsx create mode 100644 docs/src/theme/Admonition/index.tsx create mode 100644 docs/static/img/custom_caret.svg create mode 100644 docs/static/img/favicon.ico create mode 100644 docs/static/img/logo.png create mode 100644 docs/styles.css diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index ba607d3613..ef8eee7646 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -1,4 +1,5 @@ import type { Config } from "@docusaurus/types"; +import { themes as prismThemes } from "prism-react-renderer"; // If your content lives in docs/src, set DOCS_PATH='src'; else '.' const DOCS_PATH = @@ -21,11 +22,108 @@ const config: Config = { sidebarPath: "./sidebars.ts", remarkPlugins: [require("remark-math")], rehypePlugins: [require("rehype-katex")], + versions: { + current: { + label: `unstable`, + }, + }, }, blog: false, pages: false, + theme: { + customCss: "./styles.css", + }, + }, + ], + ], + + plugins: [ + [ + "@cmfcmf/docusaurus-search-local", + { + // whether to index docs pages + indexDocs: true, + + // whether to index blog pages + indexBlog: false, + + // whether to index static pages + indexPages: false, + + // language of your documentation, see next section + language: "en", + + // setting this to "none" will prevent the default CSS to be included. The default CSS + // comes from autocomplete-theme-classic, which you can read more about here: + // https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-theme-classic/ + style: undefined, + + // lunr.js-specific settings + lunr: { + // When indexing your documents, their content is split into "tokens". + // Text entered into the search box is also tokenized. + // This setting configures the separator used to determine where to split the text into tokens. + // By default, it splits the text at whitespace and dashes. + // + // Note: Does not work for "ja" and "th" languages, since these use a different tokenizer. + tokenizerSeparator: /[\s\-]+/, + // https://lunrjs.com/guides/customising.html#similarity-tuning + // + // This parameter controls the importance given to the length of a document and its fields. This + // value must be between 0 and 1, and by default it has a value of 0.75. Reducing this value + // reduces the effect of different length documents on a term's importance to that document. + b: 0.75, + // This controls how quickly the boost given by a common word reaches saturation. Increasing it + // will slow down the rate of saturation and lower values result in quicker saturation. The + // default value is 1.2. If the collection of documents being indexed have high occurrences + // of words that are not covered by a stop word filter, these words can quickly dominate any + // similarity calculation. In these cases, this value can be reduced to get more balanced results. + k1: 1.2, + // By default, we rank pages where the search term appears in the title higher than pages where + // the search term appears in just the text. This is done by "boosting" title matches with a + // higher value than content matches. The concrete boosting behavior can be controlled by changing + // the following settings. + titleBoost: 5, + contentBoost: 1, + tagsBoost: 3, + parentCategoriesBoost: 2, // Only used when indexing is enabled for categories + }, }, ], ], + + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + { + colorMode: { + defaultMode: "light", + disableSwitch: true, + }, + prism: { + theme: prismThemes.oneLight, + darkTheme: prismThemes.oneDark, + additionalLanguages: ["rust", "solidity", "toml", "yaml"], + }, + navbar: { + logo: { + src: "img/logo.png", + alt: "Miden Logo", + height: 240, + }, + title: "MIDEN", + items: [ + { + type: "docsVersionDropdown", + position: "left", + dropdownActiveClassDisabled: true, + }, + { + href: "https://github.com/0xMiden/", + label: "GitHub", + position: "right", + }, + ], + }, + }, }; export default config; diff --git a/docs/package-lock.json b/docs/package-lock.json index 2bedc86c6b..cd68240f34 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "@miden/docs-dev", "devDependencies": { + "@cmfcmf/docusaurus-search-local": "^2.0.0", "@docusaurus/core": "^3", "@docusaurus/preset-classic": "^3", "rehype-katex": "^7", @@ -112,6 +113,59 @@ "@algolia/autocomplete-shared": "1.19.2" } }, + "node_modules/@algolia/autocomplete-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-js/-/autocomplete-js-1.19.4.tgz", + "integrity": "sha512-ZkwsuTTIEuw+hbsIooMrNLvTVulUSSKqJT3ZeYYd//kA5fHaFf2/T0BDmd9qSGxZRhT5WS8AJYjFARLmj5x08g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.19.4", + "@algolia/autocomplete-preset-algolia": "1.19.4", + "@algolia/autocomplete-shared": "1.19.4", + "htm": "^3.1.1", + "preact": "^10.13.2" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.5.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-js/node_modules/@algolia/autocomplete-core": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.4.tgz", + "integrity": "sha512-yVwXLrfwQ3dAndY12j1pfa0oyC5hTDv+/dgwvVHj57dY3zN6PbAmcHdV5DOOdGJrCMXff+fsPr8G2Ik8zWOPTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.4", + "@algolia/autocomplete-shared": "1.19.4" + } + }, + "node_modules/@algolia/autocomplete-js/node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.4.tgz", + "integrity": "sha512-K6TQhTKxx0Es1ZbjlAQjgm/QLDOtKvw23MX0xmpvO7AwkmlmaEXo2PwHdVSs3Bquv28CkO2BYKks7jVSIdcXUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.4" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-js/node_modules/@algolia/autocomplete-shared": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.4.tgz", + "integrity": "sha512-V7tYDgRXP0AqL4alwZBWNm1HPWjJvEU94Nr7Qa2cuPcIAbsTAj7M/F/+Pv/iwOWXl3N7tzVzNkOWm7sX6JT1SQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", @@ -125,6 +179,31 @@ "search-insights": ">= 1 < 3" } }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.19.4.tgz", + "integrity": "sha512-WhX4mYosy7yBDjkB6c/ag+WKICjvV2fqQv/+NWJlpvnk2JtMaZByi73F6svpQX945J+/PxpQe8YIRBZHuYsLAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.4" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia/node_modules/@algolia/autocomplete-shared": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.4.tgz", + "integrity": "sha512-V7tYDgRXP0AqL4alwZBWNm1HPWjJvEU94Nr7Qa2cuPcIAbsTAj7M/F/+Pv/iwOWXl3N7tzVzNkOWm7sX6JT1SQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, "node_modules/@algolia/autocomplete-shared": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", @@ -136,6 +215,40 @@ "algoliasearch": ">= 4.9.1 < 6" } }, + "node_modules/@algolia/autocomplete-theme-classic": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.19.4.tgz", + "integrity": "sha512-/qE8BETNFbul4WrrUyBYgaaKcgFPk0Px9FDKADnr3HlIkXquRpcFHTxXK16jdwXb33yrcXaAVSQZRfUUSSnxVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.25.2.tgz", + "integrity": "sha512-tA1rqAafI+gUdewjZwyTsZVxesl22MTgLWRKt1+TBiL26NiKx7SjRqTI3pzm8ngx1ftM5LSgXkVIgk2+SRgPTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.25.2" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.25.2.tgz", + "integrity": "sha512-E+aZwwwmhvZXsRA1+8DhH2JJIwugBzHivASTnoq7bmv0nmForLyH7rMG5cOTiDK36DDLnKq1rMGzxWZZ70KZag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.25.2.tgz", + "integrity": "sha512-KYcenhfPKgR+WJ6IEwKVEFMKKCWLZdnYuw08+3Pn1cxAXbJcTIKjoYgEXzEW6gJmDaau2l55qNrZo6MBbX7+sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.25.2" + } + }, "node_modules/@algolia/client-abtesting": { "version": "5.38.0", "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.38.0.tgz", @@ -152,6 +265,41 @@ "node": ">= 14.0.0" } }, + "node_modules/@algolia/client-account": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.25.2.tgz", + "integrity": "sha512-IfRGhBxvjli9mdexrCxX2N4XT9NBN3tvZK5zCaL8zkDcgsthiM9WPvGIZS/pl/FuXB7hA0lE5kqOzsQDP6OmGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.25.2", + "@algolia/client-search": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.25.2.tgz", + "integrity": "sha512-HXX8vbJPYW29P18GxciiwaDpQid6UhpPP9nW9WE181uGUgFhyP5zaEkYWf9oYBrjMubrGwXi5YEzJOz6Oa4faA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.25.2.tgz", + "integrity": "sha512-pO/LpVnQlbJpcHRk+AroWyyFnh01eOlO6/uLZRUmYvr/hpKZKxI6n7ufgTawbo0KrAu2CePfiOkStYOmDuRjzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, "node_modules/@algolia/client-analytics": { "version": "5.38.0", "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.38.0.tgz", @@ -265,6 +413,23 @@ "node": ">= 14.0.0" } }, + "node_modules/@algolia/logger-common": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.25.2.tgz", + "integrity": "sha512-aUXpcodoIpLPsnVc2OHgC9E156R7yXWLW2l+Zn24Cyepfq3IvmuVckBvJDpp7nPnXkEzeMuvnVxQfQsk+zP/BA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/logger-console": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.25.2.tgz", + "integrity": "sha512-H3Y+UB0Ty0htvMJ6zDSufhFTSDlg3Pyj3AXilfDdDRcvfhH4C/cJNVm+CTaGORxL5uKABGsBp+SZjsEMTyAunQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/logger-common": "4.25.2" + } + }, "node_modules/@algolia/monitoring": { "version": "1.38.0", "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.38.0.tgz", @@ -310,6 +475,13 @@ "node": ">= 14.0.0" } }, + "node_modules/@algolia/requester-common": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.25.2.tgz", + "integrity": "sha512-Q4wC3sgY0UFjV3Rb3icRLTpPB5/M44A8IxzJHM9PNeK1T3iX7X/fmz7ATUYQYZTpwHCYATlsQKWiTpql1hHjVg==", + "dev": true, + "license": "MIT" + }, "node_modules/@algolia/requester-fetch": { "version": "5.38.0", "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.38.0.tgz", @@ -336,6 +508,18 @@ "node": ">= 14.0.0" } }, + "node_modules/@algolia/transporter": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.25.2.tgz", + "integrity": "sha512-yw3RLHWc6V+pbdsFtq8b6T5bJqLDqnfKWS7nac1Vzcmgvs/V/Lfy7/6iOF9XRilu5aBDOBHoP1SOeIDghguzWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.25.2", + "@algolia/logger-common": "4.25.2", + "@algolia/requester-common": "4.25.2" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -2145,6 +2329,206 @@ "node": ">=6.9.0" } }, + "node_modules/@cmfcmf/docusaurus-search-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cmfcmf/docusaurus-search-local/-/docusaurus-search-local-2.0.0.tgz", + "integrity": "sha512-WfgqJN5VXeyhcAVTF4isXFNCvZXdOlQZIsXbgKPEmXcXykV+YfPX4UgKJ4J/MuKtaKQ8SGjfeXhg2clhdmwHVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-js": "^1.8.2", + "@algolia/autocomplete-theme-classic": "^1.8.2", + "@algolia/client-search": "^4.12.0", + "algoliasearch": "^4.12.0", + "cheerio": "^1.0.0", + "clsx": "^2.0.0", + "lunr-languages": "^1.4.0", + "mark.js": "^8.11.1", + "tslib": "^2.6.3" + }, + "peerDependencies": { + "@docusaurus/core": "^3.0.0", + "nodejieba": "^2.5.0 || ^3.0.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "nodejieba": { + "optional": true + } + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/client-analytics": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.25.2.tgz", + "integrity": "sha512-4Yxxhxh+XjXY8zPyo+h6tQuyoJWDBn8E3YLr8j+YAEy5p+r3/5Tp+ANvQ+hNaQXbwZpyf5d4ViYOBjJ8+bWNEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.25.2", + "@algolia/client-search": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/client-common": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.25.2.tgz", + "integrity": "sha512-HXX8vbJPYW29P18GxciiwaDpQid6UhpPP9nW9WE181uGUgFhyP5zaEkYWf9oYBrjMubrGwXi5YEzJOz6Oa4faA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/client-personalization": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.25.2.tgz", + "integrity": "sha512-K81PRaHF77mHv2u8foWTHnIf5c+QNf/SnKNM7rB8JPi7TMYi4E5o2mFbgdU1ovd8eg9YMOEAuLkl1Nz1vbM3zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/client-search": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.25.2.tgz", + "integrity": "sha512-pO/LpVnQlbJpcHRk+AroWyyFnh01eOlO6/uLZRUmYvr/hpKZKxI6n7ufgTawbo0KrAu2CePfiOkStYOmDuRjzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/recommend": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.25.2.tgz", + "integrity": "sha512-puRrGeXwAuVa4mLdvXvmxHRFz9MkcCOLPcjz7MjU4NihlpIa+lZYgikJ7z0SUAaYgd6l5Bh00hXiU/OlX5ffXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.25.2", + "@algolia/cache-common": "4.25.2", + "@algolia/cache-in-memory": "4.25.2", + "@algolia/client-common": "4.25.2", + "@algolia/client-search": "4.25.2", + "@algolia/logger-common": "4.25.2", + "@algolia/logger-console": "4.25.2", + "@algolia/requester-browser-xhr": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/requester-node-http": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/requester-browser-xhr": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.25.2.tgz", + "integrity": "sha512-aAjfsI0AjWgXLh/xr9eoR8/9HekBkIER3bxGoBf9d1XWMMoTo/q92Da2fewkxwLE6mla95QJ9suJGOtMOewXXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/requester-node-http": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.25.2.tgz", + "integrity": "sha512-Ja/FYB7W9ZM+m8UrMIlawNUAKpncvb9Mo+D8Jq5WepGTUyQ9CBYLsjwxv9O8wbj3TSWqTInf4uUBJ2FKR8G7xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/algoliasearch": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.25.2.tgz", + "integrity": "sha512-lYx98L6kb1VvXypbPI7Z54C4BJB2VT5QvOYthvPq6/POufZj+YdyeZSKjoLBKHJgGmYWQTHOKtcCTdKf98WOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.25.2", + "@algolia/cache-common": "4.25.2", + "@algolia/cache-in-memory": "4.25.2", + "@algolia/client-account": "4.25.2", + "@algolia/client-analytics": "4.25.2", + "@algolia/client-common": "4.25.2", + "@algolia/client-personalization": "4.25.2", + "@algolia/client-search": "4.25.2", + "@algolia/logger-common": "4.25.2", + "@algolia/logger-console": "4.25.2", + "@algolia/recommend": "4.25.2", + "@algolia/requester-browser-xhr": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/requester-node-http": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/cheerio": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.12.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -8068,6 +8452,33 @@ "node": ">= 0.8" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", @@ -9696,6 +10107,13 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/htm": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", + "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -10887,6 +11305,20 @@ "yallist": "^3.0.2" } }, + "node_modules/lunr-languages": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.14.0.tgz", + "integrity": "sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA==", + "dev": true, + "license": "MPL-1.1" + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", @@ -14151,6 +14583,19 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parse5/node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -15812,6 +16257,17 @@ "postcss": "^8.4.31" } }, + "node_modules/preact": { + "version": "10.27.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", + "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -18213,6 +18669,16 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/undici": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.12.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", @@ -19232,6 +19698,42 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/docs/package.json b/docs/package.json index 14b45b175d..a59fc51af1 100644 --- a/docs/package.json +++ b/docs/package.json @@ -7,9 +7,10 @@ "serve:dev": "docusaurus serve build" }, "devDependencies": { + "@cmfcmf/docusaurus-search-local": "^2.0.0", "@docusaurus/core": "^3", "@docusaurus/preset-classic": "^3", - "remark-math": "^6", - "rehype-katex": "^7" + "rehype-katex": "^7", + "remark-math": "^6" } } diff --git a/docs/src/theme/Admonition/Icon/Danger.tsx b/docs/src/theme/Admonition/Icon/Danger.tsx new file mode 100644 index 0000000000..ab14d7dec8 --- /dev/null +++ b/docs/src/theme/Admonition/Icon/Danger.tsx @@ -0,0 +1,19 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Danger"; + +export default function AdmonitionIconDanger(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/docs/src/theme/Admonition/Icon/Info.tsx b/docs/src/theme/Admonition/Icon/Info.tsx new file mode 100644 index 0000000000..59e48a5216 --- /dev/null +++ b/docs/src/theme/Admonition/Icon/Info.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Info"; + +export default function AdmonitionIconInfo(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/docs/src/theme/Admonition/Icon/Note.tsx b/docs/src/theme/Admonition/Icon/Note.tsx new file mode 100644 index 0000000000..d7c524b3a4 --- /dev/null +++ b/docs/src/theme/Admonition/Icon/Note.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Note"; + +export default function AdmonitionIconNote(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/docs/src/theme/Admonition/Icon/Tip.tsx b/docs/src/theme/Admonition/Icon/Tip.tsx new file mode 100644 index 0000000000..219bb8d0a6 --- /dev/null +++ b/docs/src/theme/Admonition/Icon/Tip.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Tip"; + +export default function AdmonitionIconTip(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/docs/src/theme/Admonition/Icon/Warning.tsx b/docs/src/theme/Admonition/Icon/Warning.tsx new file mode 100644 index 0000000000..f96398d118 --- /dev/null +++ b/docs/src/theme/Admonition/Icon/Warning.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Warning"; + +export default function AdmonitionIconCaution(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/docs/src/theme/Admonition/Layout/index.tsx b/docs/src/theme/Admonition/Layout/index.tsx new file mode 100644 index 0000000000..7b2c170d89 --- /dev/null +++ b/docs/src/theme/Admonition/Layout/index.tsx @@ -0,0 +1,51 @@ +import React, { type ReactNode } from "react"; +import clsx from "clsx"; +import { ThemeClassNames } from "@docusaurus/theme-common"; + +import type { Props } from "@theme/Admonition/Layout"; + +import styles from "./styles.module.css"; + +function AdmonitionContainer({ + type, + className, + children, +}: Pick & { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function AdmonitionHeading({ icon, title }: Pick) { + return ( +
+ {icon} + {/* {title} */} +
+ ); +} + +function AdmonitionContent({ children }: Pick) { + return children ? ( +
{children}
+ ) : null; +} + +export default function AdmonitionLayout(props: Props): ReactNode { + const { type, icon, title, children, className } = props; + return ( + + {title || icon ? : null} + {children} + + ); +} diff --git a/docs/src/theme/Admonition/Layout/styles.module.css b/docs/src/theme/Admonition/Layout/styles.module.css new file mode 100644 index 0000000000..88df7e639b --- /dev/null +++ b/docs/src/theme/Admonition/Layout/styles.module.css @@ -0,0 +1,35 @@ +.admonition { + margin-bottom: 1em; +} + +.admonitionHeading { + font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) / + var(--ifm-heading-line-height) var(--ifm-heading-font-family); + text-transform: uppercase; +} + +/* Heading alone without content (does not handle fragment content) */ +.admonitionHeading:not(:last-child) { + margin-bottom: 0.3rem; +} + +.admonitionHeading code { + text-transform: none; +} + +.admonitionIcon { + display: inline-block; + vertical-align: middle; + margin-right: 0.4em; +} + +.admonitionIcon svg { + display: inline-block; + height: 1.6em; + width: 1.6em; + fill: var(--ifm-alert-foreground-color); +} + +.admonitionContent > :last-child { + margin-bottom: 0; +} diff --git a/docs/src/theme/Admonition/Type/Caution.tsx b/docs/src/theme/Admonition/Type/Caution.tsx new file mode 100644 index 0000000000..b570a37a9d --- /dev/null +++ b/docs/src/theme/Admonition/Type/Caution.tsx @@ -0,0 +1,32 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Caution'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + caution + + ), +}; + +// TODO remove before v4: Caution replaced by Warning +// see https://github.com/facebook/docusaurus/issues/7558 +export default function AdmonitionTypeCaution(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/src/theme/Admonition/Type/Danger.tsx b/docs/src/theme/Admonition/Type/Danger.tsx new file mode 100644 index 0000000000..49901fa91c --- /dev/null +++ b/docs/src/theme/Admonition/Type/Danger.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Danger'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconDanger from '@theme/Admonition/Icon/Danger'; + +const infimaClassName = 'alert alert--danger'; + +const defaultProps = { + icon: , + title: ( + + danger + + ), +}; + +export default function AdmonitionTypeDanger(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/src/theme/Admonition/Type/Info.tsx b/docs/src/theme/Admonition/Type/Info.tsx new file mode 100644 index 0000000000..018e0a16d7 --- /dev/null +++ b/docs/src/theme/Admonition/Type/Info.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Info'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconInfo from '@theme/Admonition/Icon/Info'; + +const infimaClassName = 'alert alert--info'; + +const defaultProps = { + icon: , + title: ( + + info + + ), +}; + +export default function AdmonitionTypeInfo(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/src/theme/Admonition/Type/Note.tsx b/docs/src/theme/Admonition/Type/Note.tsx new file mode 100644 index 0000000000..c99e03857f --- /dev/null +++ b/docs/src/theme/Admonition/Type/Note.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Note'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconNote from '@theme/Admonition/Icon/Note'; + +const infimaClassName = 'alert alert--secondary'; + +const defaultProps = { + icon: , + title: ( + + note + + ), +}; + +export default function AdmonitionTypeNote(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/src/theme/Admonition/Type/Tip.tsx b/docs/src/theme/Admonition/Type/Tip.tsx new file mode 100644 index 0000000000..18604a5e9f --- /dev/null +++ b/docs/src/theme/Admonition/Type/Tip.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Tip'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconTip from '@theme/Admonition/Icon/Tip'; + +const infimaClassName = 'alert alert--success'; + +const defaultProps = { + icon: , + title: ( + + tip + + ), +}; + +export default function AdmonitionTypeTip(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/src/theme/Admonition/Type/Warning.tsx b/docs/src/theme/Admonition/Type/Warning.tsx new file mode 100644 index 0000000000..61d9597b61 --- /dev/null +++ b/docs/src/theme/Admonition/Type/Warning.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Warning'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + warning + + ), +}; + +export default function AdmonitionTypeWarning(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/src/theme/Admonition/Types.tsx b/docs/src/theme/Admonition/Types.tsx new file mode 100644 index 0000000000..2a1001900a --- /dev/null +++ b/docs/src/theme/Admonition/Types.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import AdmonitionTypeNote from '@theme/Admonition/Type/Note'; +import AdmonitionTypeTip from '@theme/Admonition/Type/Tip'; +import AdmonitionTypeInfo from '@theme/Admonition/Type/Info'; +import AdmonitionTypeWarning from '@theme/Admonition/Type/Warning'; +import AdmonitionTypeDanger from '@theme/Admonition/Type/Danger'; +import AdmonitionTypeCaution from '@theme/Admonition/Type/Caution'; +import type AdmonitionTypes from '@theme/Admonition/Types'; + +const admonitionTypes: typeof AdmonitionTypes = { + note: AdmonitionTypeNote, + tip: AdmonitionTypeTip, + info: AdmonitionTypeInfo, + warning: AdmonitionTypeWarning, + danger: AdmonitionTypeDanger, +}; + +// Undocumented legacy admonition type aliases +// Provide hardcoded/untranslated retrocompatible label +// See also https://github.com/facebook/docusaurus/issues/7767 +const admonitionAliases: typeof AdmonitionTypes = { + secondary: (props) => , + important: (props) => , + success: (props) => , + caution: AdmonitionTypeCaution, +}; + +export default { + ...admonitionTypes, + ...admonitionAliases, +}; diff --git a/docs/src/theme/Admonition/index.tsx b/docs/src/theme/Admonition/index.tsx new file mode 100644 index 0000000000..8f4225da2f --- /dev/null +++ b/docs/src/theme/Admonition/index.tsx @@ -0,0 +1,21 @@ +import React, {type ComponentType, type ReactNode} from 'react'; +import {processAdmonitionProps} from '@docusaurus/theme-common'; +import type {Props} from '@theme/Admonition'; +import AdmonitionTypes from '@theme/Admonition/Types'; + +function getAdmonitionTypeComponent(type: string): ComponentType { + const component = AdmonitionTypes[type]; + if (component) { + return component; + } + console.warn( + `No admonition component found for admonition type "${type}". Using Info as fallback.`, + ); + return AdmonitionTypes.info!; +} + +export default function Admonition(unprocessedProps: Props): ReactNode { + const props = processAdmonitionProps(unprocessedProps); + const AdmonitionTypeComponent = getAdmonitionTypeComponent(props.type); + return ; +} diff --git a/docs/static/img/custom_caret.svg b/docs/static/img/custom_caret.svg new file mode 100644 index 0000000000..0480cf1c4f --- /dev/null +++ b/docs/static/img/custom_caret.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..732d3522265c295e355937128b79431dbf96cedc GIT binary patch literal 15406 zcmeHNYe-Z<6du!pioza352^iA_@h7yq@+OTPtlKlgr#QEgG?<7j0&})h=L$23nD2K zk)j_#L|U1cy)>diP0BUsosv{O(tPXJIbFro_HlRjx_75$VA#F$JoDW%XU_f32oWFx zMR2g7UyDcz5+XtfVXVN-;-L}lUO1NHNTC=U@LjM`8a>YkKKfSI90p{N~+yk z;MFo<;~6b4KLjk^Kk$4O_}T7ythz>k``vg3WEBEwmx1a}Km=jXGnn9jPQ%D?lngTS;cz?~6w zd^UD;8zWakGa#bf&o4h1Ge3`#T?_=R2O`pd>raO=uRmn#&~x5>U}=UXtC91w4D7c| zhzC+G0)72lqWLZ(-+w3h7mM}*M$galvClG{-gi9tVLiRT?FwMtF0L7#-ZcPiox{tF zda`~YolgPQoX~XavDK)N^^a3ezGV1bV8s!jwL^bDBU`dB7j+m|k`6Sr89B1PvCN#` zO#Ew1)IT%#pI-aJ-zP@fA2aQ*+2@a|^Ox=MXr8~$tbs554`=h6?SDCwP1Pv`WI(_X zFYv#VGfzSkt=L3a@gy>6tddb7pb$_9C^!dlE>$ z0%X`^-n)|%wEES@hw3=KADsz=CINxO-SfJ9EkCb;If?|jqcAQOShz>KH_$)8-*Y|3 zkDTq_N{VBwJVqGM8qAQNW&F_!?9SEJz&d`D^k8rewJzl6c2C{_GZW2|pVz?gsMI{o zPKJ`b;TTn4AC-5}oeqklO(Gr2E#;Q>Q~m?s<2U4iEbfvLud&vL3PCmMq}nJ16aoqXg@8i9Qv~RGkV*;xj}iC{Uw-;d literal 0 HcmV?d00001 diff --git a/docs/static/img/logo.png b/docs/static/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..35e9497563533cdab4b533f7777d001430839297 GIT binary patch literal 4335 zcmeI0*H;rtw1+2=A|0joAWb-Q1*C`Giy(&*ARr(hp(xT7BB82uFaifbK>=x@NG}Np z2uP782}n_?0@8~VZ_ZiwkGK!_VP?O~>{+wdp8fmwBwCsqFwk<-0st6{40Wx@7XRC~*0|~S;&<2%5ysH2(mm2A6-3oQy$cq@yvgYsAeDm}tYZI&W;ENZ}OnW3N zB!5H-#N3T}F=Oqgb1nUTqAT;L*%i|g$NS<&ckKT*>%P_5SX7il!}fY0*XsJucaxDJBm;2qaoCv{8K!tejwZ8_(b7J#U(FOAlI zB3%a3IvpsvJr!p5L52p>A)i-kgUPVGj|pg`Zvz3P!IB6l?76A9$7LW+lkKbkg{eyw z`dkK|xs`_mfg`OnaD2x%@EjzFSQ}?kz>T%v+9m-*i-DCK3V5A$cK1k@X14@j1$X zM$wW@#gUz>USXznDIV#CY7ov48Tt;0)`qEhoY)i^inc>SlI@6;eGb2Osr z4r(?g#GQkrf|I}z)E_H9J=7c}991fxeiLMB73?#mMle&tH%VdER^SIeEhM!ics7$a znCscEUrDMWiwWRrX@JefdTVyTcUPCLSH^v;%%E>?NAZx_+GVS0Yn9gy*?o@i=aZ>% zgBE;*7!I@sg=6N^G`nWJw|&0)Rxz+xZa@9d@4b6(0E-tBz=zDmaa5n-lMWJWHkh)@ zBXf_Pk3D1|jln@RJ6TPXaKGj-`~EwRvC=UR&#soLp1{v{v|2Qv!P+_`@6Yi8wh=f5 zE!S1jHtr;{uY$*}e!KlP*Qb~CECR7iqGDwRKzOW7_3!>9CQo6bzBm=x&x^2)=Jdyhiz9#NCqFF`ramOkSV%MzpH+_ zw@g#Qwi=Y|4a{otw?k)nk!{6Ln8Xv2^EP#%6aio{$J-`J%psMtIjZn-Z>Ih6_U)#^ zlXj1zULkv3&;{>=fpIIQ_uibop=e4>+84c!RMJ4eF6LFBrWZ-xd}CsH8v=Iy`W`lx z@jblm7Ko&TN6JZOk1Cp~_{h_*(LWV3zgruq!lOvXACGDZsO;kapZ*Z77n197sJ@e)pM9)<5W}%a40YB9x`&vW0l6qq zwaE*!Iq-?%$@8Lavnaad83rjv3u#P}?!MpmV9x8GOfcv5EJ_FD;HUW5XX22axeAeI zt1GUf;S39%1YFd#yBTtuau3CzD9HZYkuTYYCANwlBe?u$h5Vw}$7=f9POpU)(3wW@ zPwJtHXYNGY@Q3k^FrsE*<~58V!>h9UG@3wG=2NkTL8odJg$D5}g?i88VI_+JoyAV( zax(7Whxcqn!!xupa~y3sb&)zZ-iz^0OSN}fcy>AQxeyQ&*n9g-maC7CULmIh)Bntl$~#{{%r{MA5^s?G+W+hKvY_v$nK;^C9apCuS)QXx zKlqdI9w`_zX+04h-t7w32}!{GF2%F0H)K4CMY zmzw2{YPXut7;H?0*rY2`EDwc=5mDHQ{(0ipV0+^7T6)mZqh#7(EK%i(NENxp9LIDy zI}~fsTo_KQ>{f}wy5dfL74AyJUn8p*x0OW%Rh%;vDaD91Be@H#{FXhHk7p4W-Leis zy+s4VLhC6b7Df3zuaG16;LxO`Lq`7ibtpX+i!Ay)Bwwxrir8*N$kjJRt6I=8cF>yC zS1+Av;pS+(?QPYSCCFD`tt8!^^H+*`bkb|B<)Wcb*GyXm37TV+F@09Dh&#ovIPU^6V#vAK|$`LklFi-Hw@6 zf?aT*7OnlnueQa)kx7PuK3}$0WW0X(Xe97kg|Jl7FjpA@u&T@>WtfuP9xgrKs#W%- zo)lM~kNL~>XL#&^@xH$$95k-b)^~*dt|f>N2Q;f@UvGR+spP5axU}ZC(OLUxni*6! zqkYlU*xRaMo#q84(!|k3>+sILuLMQnRkm9Yh^1wiQt1qPbK9A`+5`JNM}q6Jc0A83 z!mv~L3uOeffKxF4KyBvU{YDSfJkl)nd1!|7GdfNoh$Z}9Sw(n{-O))Rc@fF~wl!Hx zqt&5j%lWeFqbmlm>A#8RRO(Q+8ye68JoJy{*`W-fP^p#TkIbpa6yvmtkqkjvTL?Io zpt=g`V|TO|FwLo|i45d)tZ>}9dqt9C;-9QbAU0E_&nltzq|-G|;hXix&|5bfz}3w8 zdd{I_n}^j=jg+b$&0P!uw>V1Jb~9JCkN+WrQ^EzK{o*}|XKoIRP8Wl$E3840gy*8v zGdaZ%>$txjOvJsYTeNV<2ZrzLf{x#d$V-XjO$ve0)Rz0!t$ev&y<%~1dZY@*Hb|`b zahu}fg20OT65rGGxF>q0I^c&;3u8~efx1jTE!wdwb;aw0(5taL6>d4(3OYM;@Z(Bl z+tzG|ps6^1p|RoIj|RR6Mb;ko&=C|&ny*e98B`4=fD&V_ z-1!{Yi~9RiJb5%GK@E>HD&xk&yEPZXW_bcEde^XKjqM_%=SdRn(3YB@+~3)x3>Z+{ zm?87awT8}t!^e*pP4;=BH|Esq5vRy%i)FmihUchAaLM~K z5%01Uruo|~ggYdPF=r#c+*c$A0qo|N24i|7@CcSpnWoy-;ORm>kJ)cqKLnA#<@Wq8 zm}u-=K*#Vjm6PFr#|6zKI$7>S4H7z9JSaNw3F=YlAU#~ueV+!>9vx5xRZqeTN zbZEyyg!bZOWn7;S1__Ulq%l$O(cZ;ih+p4I?fMkS0Lg5QXS$EByX_uaM4#SXf8@>c zv%b(+e-6rq)Nh&oU!Z^-SfpC)KtN0P&dV{^gYkEWD_|!Px9nkSi;p45uBqTW=W*`6 z8A_TLE4dkh)10_Vr@++R$@ogN60*FVjKN8U5q9zFYf}_h2(!W`@9)1WrYV)0f8h|} zN#g7PS%u~r?t0NqNU$ZV$!hm>ZqB-%zZcep{7XFSJPXmAMv3=`J?APV!7d(k!CKF;%Ju1WzbZzA4cCZA<9BS>n*Rf_)rdbH=vm)=pliQI%W-I>k$56vZJ%d0|$IEb%APw)zE zgaZwCSQZ&rYz8pae5l33L8_DgKwp$fL+=eK2s604AKVv3c{j5nKN9KbGj-^NBW^t_ z+M&Qld-6p&eRyJl)v8x8B*Q4isSxWL(#n?He&fbV;bFF^#2mZ8r$;=eEH>-@OFNW! zgTW2v@>%Mx+ZG2DCnafa`4ngq0#+){nY#0Jg47G?6uo=ING4wzfYrJLq$iR+Ou&b9 z@i(SWn26w5nLa2g_IO|hg*gcID*wBa5^eI{jd&SUyBmvV07IjXd+#oRRc|h*43IGS z@g5^&-YS4RHtgyt;F8%^{q7OLztmY~^EuZSzFsGm`xmM&JV{&S?@doXS+q+60ellS zNJ4Z2hHgjkLV@V|3@i8bdo5HS-&64uSpYw!PGzGlS?Invva4IcK`1MI(>G@bWZpT@ ze$S9>$?)?ef|3FqG3fDGh-?iAsef&f8{SO?tX>MasQ$l$Bh66goP>^&&ja!g1~Aey K*R9lciT)qw4D3Gu literal 0 HcmV?d00001 diff --git a/docs/styles.css b/docs/styles.css new file mode 100644 index 0000000000..297366633a --- /dev/null +++ b/docs/styles.css @@ -0,0 +1,1046 @@ +/* Import Google Fonts */ +@import url("https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap"); + +/* ============================ + 0) SEMANTIC TOKENS (your brand) + ============================ */ + +:root { + /* ---- Brand palette (LIGHT) ---- */ + --color-brand: #ff5500; /* TODO: replace with your primary */ + --color-brand-600: #ff6a26; /* TODO */ + --color-brand-700: #e24c00; /* TODO */ + --color-brand-800: #b63c00; /* TODO */ + --color-secondary: #102445; + + --color-accent: #1764a8; /* TODO secondary/accent */ + --color-success: #00871d; + --color-warning: #ff5500; + --color-danger: #ff0000; + + /* ---- Neutral & Surfaces ---- */ + --color-bg: #ffffff; + --color-surface: #f9f9f9; + --color-elevated: color-mix(in oklab, #000 5%, transparent); + --color-border: color-mix(in oklab, #000 20%, transparent); + --color-text: #363636; + --color-muted: color-mix(in oklab, var(--color-text) 70%, transparent); + --color-link: var(--color-brand); + + /* ---- Typography ---- */ + --font-sans: "Geist", ui-sans-serif, system-ui, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, + "Liberation Mono", monospace; + --font-size-base: 14px; + --line-height-base: 150%; + --letter-spacing-base: 0; + + /* ---- Radii & Spacing ---- */ + --radius-xs: 5px; + --radius-md: 5px; + --radius-lg: 5px; + --radius-xl: 5px; + + /* Design system spacing - exact pixel values */ + --space-4px: 4px; /* Inline code padding */ + --space-16px: 16px; /* Standard paragraph, list, heading spacing */ + --space-24px: 24px; /* H1 to first content, H2 to large components, list indent */ + --space-32px: 32px; /* Section breaks, major structural spacing */ + --space-40px: 40px; /* Major structural reset (lower bound) */ + --space-48px: 48px; /* Major structural reset (upper bound) */ + + /* Legacy spacing variables for compatibility */ + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-8: 2rem; /* 32px */ + --space-10: 2.5rem; /* 40px */ + + /* ---- Shadows ---- */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06); + --shadow-md: 0 6px 16px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.12); + + /* ---- Container widths ---- */ + + /* Common layout sizes */ + --navbar-height: 64px; + + /* Docs sidebar width */ + --doc-sidebar-width: 250px !important; +} + +/* ========================================== + 1) MAP SEMANTIC → INFIMA (core variables) + ========================================== */ + +:root { + /* Brand */ + --ifm-color-primary: var(--color-brand); + --ifm-color-secondary: var(--color-secondary); + --ifm-color-primary-dark: var(--color-brand-700); + --ifm-color-primary-darker: var(--color-brand-800); + --ifm-color-primary-darkest: var(--color-brand-800); + --ifm-color-primary-light: var(--color-brand-600); + --ifm-color-primary-lighter: var(--color-brand-600); + --ifm-color-primary-lightest: var(--color-brand-600); + + /* Text & surfaces */ + --ifm-color-content: var(--color-text); + --ifm-color-content-secondary: var(--color-muted); + --ifm-link-color: var(--color-link); + --ifm-background-color: var(--color-bg); + --ifm-background-surface-color: var(--color-surface); + + /* Typography */ + --ifm-font-family-base: var(--font-sans); + --ifm-font-family-monospace: var(--font-mono); + --ifm-font-size-base: var(--font-size-base); + --ifm-line-height-base: var(--line-height-base); + --ifm-font-weight-base: 400; + --ifm-heading-font-weight: 700; + + /* Heading typography specifications */ + --ifm-h1-font-size: 28px; + --ifm-h1-line-height: 32px; + --ifm-h2-font-size: 24px; + --ifm-h2-line-height: 28px; + --ifm-h3-font-size: 20px; + --ifm-h3-line-height: 24px; + --ifm-h4-font-size: 16px; + --ifm-h4-line-height: 16px; + + /* Code typography specifications */ + --ifm-code-font-size: 14px; + --ifm-code-line-height: 150%; + --ifm-code-letter-spacing: -2%; + --ifm-code-padding-horizontal: 4px; + --ifm-code-padding-vertical: 4px; + + /* Layout & spacing */ + --ifm-spacing-horizontal: var(--space-4); + --ifm-global-radius: var(--radius-md); + + /* Component radii (Infima) */ + --ifm-breadcrumb-border-radius: var(--radius-md); + --ifm-button-border-radius: var(--radius-md); + --ifm-card-border-radius: var(--radius-md); + --ifm-alert-border-radius: var(--radius-md); + --ifm-badge-border-radius: var(--radius-md); + --ifm-tabs-border-radius: var(--radius-md); + --ifm-table-border-radius: var(--radius-md); + --ifm-code-border-radius: var(--radius-md); + + /* Sizes */ + --ifm-container-width: var(--container-width); + --ifm-navbar-height: var(--navbar-height); + --ifm-menu-link-padding-horizontal: 6px; + --ifm-menu-link-padding-vertical: 12px; + + /* Borders & shadows */ + --ifm-border-color: var(--color-border); + --ifm-card-box-shadow: var(--shadow-md); + --ifm-global-shadow-lw: var(--shadow-sm); + --ifm-global-shadow-md: var(--shadow-md); + --ifm-global-shadow-tl: var(--shadow-lg); + + /* Tables */ + --ifm-table-cell-padding: 0.75rem; + + /* Alerts */ + --ifm-alert-background-color: var(--color-elevated); + --ifm-alert-border-color: var(--color-border); + + /* Breadcrumbs */ + --ifm-breadcrumb-color-active: var(--color-text); + --ifm-breadcrumb-item-background-active: var(--color-elevated); +} + +/* ========================================== + 2) BASE ELEMENTS + ========================================== */ + +html, +body { + font-family: var(--ifm-font-family-base); + font-size: var(--ifm-font-size-base); + font-weight: var(--ifm-font-weight-base); + line-height: var(--ifm-line-height-base); + color: var(--ifm-color-content); + background: var(--ifm-background-color); + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; +} + +/* Remove underlines from all links by default */ +a { + color: var(--ifm-link-color); + text-decoration: none; +} + +/* Add underlines only to links within text content */ +p a, +li a, +blockquote a, +.theme-doc-markdown a, +.markdown a, +article a, +main a { + text-decoration: underline; + text-decoration-color: var(--ifm-link-color); + text-underline-offset: 0.2em; +} + +/* Hover effects for text links */ +p a:hover, +li a:hover, +blockquote a:hover, +.theme-doc-markdown a:hover, +.markdown a:hover, +article a:hover, +main a:hover { + text-decoration-color: currentColor; +} + +/* Ensure navigation links have no underlines */ +.navbar a, +.theme-doc-sidebar-container a, +.menu__link, +.breadcrumbs__link, +.pagination-nav__link, +.table-of-contents a { + text-decoration: none !important; +} + +/* Hover effects for navigation links */ +.navbar a:hover, +.theme-doc-sidebar-container a:hover, +.menu__link:hover, +.breadcrumbs__link:hover, +.pagination-nav__link:hover, +.table-of-contents a:hover { + text-decoration: none !important; +} + +/* Headings - exact design specifications */ +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-sans); + letter-spacing: 0em; + font-weight: bold; +} + +/* H1: 28px font, 32px line height */ +h1 { + font-size: var(--ifm-h1-font-size); + line-height: var(--ifm-h1-line-height); +} + +/* H2: 24px font, 28px line height */ +h2 { + font-size: var(--ifm-h2-font-size); + line-height: var(--ifm-h2-line-height); +} + +/* H3: 20px font, 24px line height */ +h3 { + font-size: var(--ifm-h3-font-size); + line-height: var(--ifm-h3-line-height); +} + +/* H4: 16px line height */ +h4 { + font-size: var(--ifm-h4-font-size); + line-height: var(--ifm-h4-line-height); +} + +h5 { + font-size: 1.125rem; + line-height: 20px; +} + +h6 { + font-size: 1rem; + text-transform: none; + line-height: 16px; +} + +/* Bold text styling */ +strong, +b { + font-weight: 600; + color: var(--ifm-color-content); +} + +/* Body/Paragraph: 14px, 130% line height, Regular */ +p { + font-size: var(--ifm-font-size-base); + line-height: var(--line-height-base); + font-weight: 400; + letter-spacing: 0em; +} + +/* Links (text): 14px, 130% line height, Semibold */ +a { + line-height: 130%; +} + +/* List/Bulletpoints: 14-16px, 150% line height, Regular */ +ul { + list-style-type: disc; + line-height: 150%; +} + +ul li::marker { + color: var(--color-border); +} + +/* Nested unordered lists */ +ul ul { + list-style-type: circle; +} + +ul ul ul { + list-style-type: square; +} + +/* Ordered lists: 14-16px, 150% line height, Regular */ +ol { + list-style-type: decimal; + line-height: 150%; + font-size: var(--font-size-base); + font-weight: 400; +} + +/* Nested ordered lists keep default styling */ +ol ol { + list-style-type: lower-alpha; +} + +ol ol ol { + list-style-type: lower-roman; +} + +/* Blockquote */ +blockquote { + border-left: 3px solid var(--ifm-color-primary); + background: var(--ifm-background-surface-color); + padding: var(--space-4); + border-radius: var(--radius-md); + color: var(--ifm-color-content); +} + +/* Images */ +img { + border-radius: var(--radius-md); +} + +/* ========================================== + 3) NAVBAR & FOOTER (scoped to stable classes) + ========================================== */ + +/* ThemeClassNames.layout.navbar.container */ +.theme-layout-navbar { + height: var(--ifm-navbar-height); + backdrop-filter: saturate(1.1) blur(8px); + border-bottom: 1px solid var(--ifm-border-color) !important; + background: color-mix(in oklab, var(--ifm-background-color) 75%, transparent); + backdrop-filter: saturate(1.1) blur(8px); + box-shadow: none; +} + +/* ThemeClassNames.layout.footer.container */ +.theme-layout-footer { + --ifm-footer-background-color: var(--color-elevated); + background: var(--ifm-footer-background-color); + color: var(--ifm-color-content); +} + +/* ========================================== + 4) SIDEBAR (Docs) + ========================================== */ + +/* ThemeClassNames.docs.docSidebarContainer */ +.theme-doc-sidebar-container { + border-right: 1px solid var(--ifm-border-color) !important; + padding: 5px; + font-size: 0.875rem; /* Sidebar-specific font size */ +} + +/* Main docs content area - wider to fill space */ +.theme-doc-markdown { + max-width: 100%; /* Use full available width */ +} + +/* ThemeClassNames.docs.docSidebarMenu */ +.theme-doc-sidebar-menu .menu__link { + font-family: var(--font-sans); + letter-spacing: -2%; + border-radius: var(--radius-md); + padding: var(--ifm-menu-link-padding-vertical) + var(--ifm-menu-link-padding-horizontal); +} + +.theme-doc-sidebar-menu .menu__link--active { + color: var(--ifm-color-primary); + background: color-mix( + in oklab, + var(--ifm-background-surface-color) 70%, + transparent + ); +} +.theme-doc-sidebar-menu .menu__link--active:hover { + background: var(--ifm-background-surface-color); +} + +/* Category collapsible */ +.menu__list-item-collapsible { + border-radius: var(--radius-md); +} + +/* ========================================== + 5) BREADCRUMBS + ========================================== */ + +/* ThemeClassNames.docs.docBreadcrumbs */ +.theme-doc-breadcrumbs .breadcrumbs__link { + line-height: 150%; + border-radius: var(--radius-md); + padding: var(--ifm-menu-link-padding-horizontal) + var(--ifm-menu-link-padding-vertical); +} + +.theme-doc-breadcrumbs .breadcrumbs__item--active .breadcrumbs__link { + color: var(--ifm-color-primary); +} + +/* ========================================== + 6) BUTTONS + ========================================== */ + +.button { + border-radius: var(--ifm-button-border-radius); + font-weight: 600; + letter-spacing: 0.01em; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); + line-height: 150%; +} +.button--outline:not(.button--link) { + border-color: color-mix(in oklab, var(--ifm-color-primary) 70%, transparent); +} +.button--link { + color: var(--ifm-link-color); +} + +/* ========================================== + 7) BADGES + ========================================== */ + +.badge { + border-radius: var(--ifm-badge-border-radius); + border: 1px solid var(--ifm-border-color); + background: var(--ifm-background-surface-color); +} + +/* ========================================== + 8) ALERTS (Admonitions) + ========================================== */ + +.theme-admonition { + display: flex; + align-items: flex-start; /* icon aligns with text top */ + gap: 0.75rem; /* spacing between icon and text */ + padding: 1rem 1.25rem; +} + +/* ThemeClassNames.common.admonition */ +.theme-admonition.alert { + --ifm-alert-background-color: var(--color-elevated); + --ifm-alert-border-color: var(--ifm-color-primary); + --ifm-alert-background-color-highlight: #fff; + + font-family: var(--font-mono); + border: 1px solid var(--color-elevated); + border-top: 5px solid var(--ifm-alert-border-color); /* top border accent */ + border-left: none; /* disable default */ + border-radius: var(--ifm-alert-border-radius); + box-shadow: none; + background: var(--ifm-alert-background-color); + color: var(--ifm-color-content); + /* padding: 1rem 1.25rem; */ +} + +/* NOTE */ +.theme-admonition.alert--secondary { + --ifm-alert-border-color: #df9f26; + border-top-color: #df9f26; +} +/* TIP */ +.theme-admonition.alert--success { + --ifm-alert-background-color: var(--color-elevated); + border-top-color: var(--ifm-color-success) !important; +} +/* INFO / IMPORTANT */ +.theme-admonition.alert--info { + --ifm-alert-border-color: #1764a8; + border-top-color: #1764a8; +} +/* WARNING */ +.theme-admonition.alert--warning { + --ifm-alert-border-color: var(--color-warning); + border-top-color: var(--color-warning); +} + +/* DANGER */ +.theme-admonition.alert--danger { + --ifm-alert-border-color: var(--color-danger); + border-top-color: var(--color-danger); +} + +.theme-admonition ul li::marker { + color: var(--ifm-color-primary); +} + +/* Exclude list margins from alert blocks */ +.theme-admonition ul, +.theme-admonition ol { + margin-top: 0 !important; /* Smaller margins for lists in alert blocks */ + margin-bottom: 0 !important; + padding-left: 1.5rem !important; /* Standard padding for lists in alerts */ +} + +/* Nested lists in alert blocks should have no extra margin */ +.theme-admonition ul ul, +.theme-admonition ol ol, +.theme-admonition ul ol, +.theme-admonition ol ul { + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +/* VERSION BANNER */ +.theme-doc-version-banner { + --ifm-alert-background-color: var(--color-elevated); + --ifm-alert-border-color: var(--color-warning); + + font-family: var(--font-mono); + border: 1px solid var(--color-elevated); + border-top: 6px solid var(--ifm-alert-border-color); + border-left: none; /* disable default */ + color: var(--ifm-color-content); +} + +/* ========================================== + 9) TABS + ========================================== */ + +/* ThemeClassNames.tabs.container */ +.theme-tabs-container .tabs { + --ifm-tabs-padding-vertical: 0.375rem; + --ifm-tabs-padding-horizontal: 0.75rem; +} +.theme-tabs-container .tabs__item { + border-radius: var(--ifm-tabs-border-radius); +} +.theme-tabs-container .tabs__item--active { + background: var(--ifm-background-surface-color); + box-shadow: inset 0 0 0 2px + color-mix(in oklab, var(--ifm-color-primary) 24%, transparent); +} + +/* ========================================== + 10) CARDS + ========================================== */ + +.card { + border-radius: var(--ifm-card-border-radius); + box-shadow: var(--ifm-card-box-shadow); + border: 1px solid var(--ifm-border-color); + background: var(--ifm-background-surface-color); +} + +/* ========================================== + 11) TABLES + ========================================== */ + +/* Apply border radius to the entire table */ +table { + border-radius: 5px; /* Adjust this value to change corner rounding */ +} + +tbody tr { + border-top: none; + background: var(--ifm-background-surface-color); +} + +/* ========================= + 12. Code block card + header + ========================= */ + +.theme-code-block { + border: 1px solid var(--ifm-border-color); + border-radius: 5px; + position: relative; +} + +/* Target CSS module class using attribute selector to handle hashed names */ +.theme-code-block [class*="codeBlockTitle"] { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + font-family: var(--font-sans) !important; + font-size: 0.875rem !important; + font-weight: 500 !important; + letter-spacing: 0.02rem !important; + color: var(--color-base) !important; + border-bottom: 1px solid var(--ifm-border-color) !important; +} + +/* Body */ +.theme-code-block pre { + margin: 0; + background: color-mix( + in oklab, + var(--color-surface) 96%, + var(--color-bg, #fff) + ); + padding: 16px 18px; +} + +/* Code text - design specifications */ +.theme-code-block code { + background: transparent; + border: 0; + border-radius: 0; + padding: 0; + font-family: var(--font-mono); + font-size: var(--ifm-code-font-size); /* 14px */ + line-height: var(--ifm-code-line-height); /* 24px */ + letter-spacing: var(--ifm-code-letter-spacing); /* -2% */ + color: var(--color-text); +} + +/* Inline code - design specifications */ +code { + font-family: var(--font-mono); + font-size: 12px; /* 14px */ + line-height: 24px; /* 24px */ + letter-spacing: var(--ifm-code-letter-spacing); /* -2% */ + font-weight: 400; +} + +/* Highlighted lines / magic comments */ +.token-line.highlighted, +.code-block-highlighted-line { + background: color-mix(in oklab, var(--ifm-color-primary) 12%, transparent); +} + +/* Optional: softer $ prompt tint in bash */ +.theme-code-block[data-language="bash"] pre code .token.operator { + opacity: 0.9; +} + +/* Dark tweaks */ +[data-theme="dark"] .theme-code-block .codeBlockTitle { + background: color-mix(in oklab, var(--color-surface) 85%, transparent); +} + +/* ========================================== + 12.1) DETAILS/SUMMARY (Expandable Sections) - styled like code blocks + ========================================== */ + +/* Scope to docs content only */ +.theme-doc-markdown details { + border: 1px solid var(--ifm-border-color); + border-radius: 5px; + background: var(--ifm-background-surface-color); + margin: 0.75rem 0 1rem; + overflow: hidden; /* crisp rounded corners */ + padding: 0; +} + +/* The clickable header row */ +.theme-doc-markdown details > summary { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + font-weight: 500; + text-transform: uppercase; + font-family: var(--font-sans); + line-height: 1.5; + cursor: pointer; + list-style: none; + user-select: none; + color: var(--color-text); + border-bottom: 1px solid transparent; +} + +/* Hide native disclosure marker */ +.theme-doc-markdown details > summary::marker, +.theme-doc-markdown details > summary::-webkit-details-marker { + display: none; + content: ""; +} + +/* Custom caret icon */ +.theme-doc-markdown details > summary::before { + content: ""; + width: 3px; + height: 3px; + flex-shrink: 0; + -webkit-mask: url("/img/custom_caret.svg") no-repeat center / contain; + mask: url("/img/custom_caret.svg") no-repeat center / contain; + background: currentColor; + transition: transform 0.15s ease; + transform: rotate(0deg); + transform-origin: center center; /* Rotate around its own center */ + opacity: 0.8; + position: relative; + top: 0; +} + +.theme-doc-markdown details[open] > summary { + border-bottom-color: var(--ifm-border-color); +} + +/* Rotate caret when details is open */ +.theme-doc-markdown details[open] > summary::before { + transform: rotate(90deg); + transform-origin: center center; /* Keep center rotation when open */ +} + +/* Body content area */ +.theme-doc-markdown details > :not(summary) { + padding: 1rem; +} + +/* Code block inside details (optional polish) */ +.theme-doc-markdown details pre { + margin: 0; + background: color-mix(in oklab, var(--color-surface) 96%, var(--color-bg)); + border-radius: 6px; +} + +.theme-doc-markdown details > summary { + border-top: none !important; + box-shadow: none !important; +} + +[class^="collapsibleContent_"], +[class*=" collapsibleContent_"] { + padding-top: 0 !important; + border-top: none !important; + box-shadow: none !important; +} + +[class^="collapsibleContent_"]::before, +[class*=" collapsibleContent_"]::before { + display: none !important; + content: none !important; +} + +/* ========================================== + 13) PAGINATION + ========================================== */ + +.pagination-nav__link { + border: 1px solid var(--ifm-border-color); + border-radius: var(--radius-md); + box-shadow: var(--ifm-global-shadow-lw); + background: var(--ifm-background-surface-color); + transition: all 0.2s ease; +} + +/* Hide "Previous" and "Next" labels */ +.pagination-nav__sublabel { + display: none; +} + +/* Main pagination link text - uppercase */ +.pagination-nav__label { + text-transform: uppercase; + font-weight: 500; + letter-spacing: 0.02em; + font-size: 1.2rem; + font-family: var(--font-mono); + color: color-mix(in oklab, #292929bf 75%, #000); +} + +/* Arrow text characters (>> and <<) */ +.pagination-nav__link--prev::before, +.pagination-nav__link--next::after { + color: var(--ifm-color-primary) !important; + font-weight: 700 !important; + font-size: 1.25rem !important; +} + +/* If arrows are in a separate element */ +.pagination-nav__link .pagination-nav__icon, +.pagination-nav__link--prev .pagination-nav__icon, +.pagination-nav__link--next .pagination-nav__icon { + color: var(--ifm-color-primary) !important; +} + +/* ========================================== + 14) TOC (right sidebar) + ========================================== */ + +/* TOC links styling */ +.table-of-contents { + font-family: var(--font-sans); + letter-spacing: 0.01rem; + border-left: 1px solid var(--ifm-border-color); + padding-left: var(--space-4); + font-size: 0.875rem; +} + +.table-of-contents__link { + color: var(--color-text); + display: block; + padding: 4px 0; +} + +.table-of-contents__link--active { + color: var(--ifm-color-primary); + font-weight: 500; +} + +/* ========================================== + 15) VERSION INDICATOR STYLING + ========================================== */ + +/* Style the version indicator that appears above page content */ +.theme-doc-version-badge { + font-size: 0.75rem !important; + font-weight: 600 !important; + color: var(--ifm-color-primary) !important; + background: color-mix( + in oklab, + var(--ifm-color-primary) 12%, + transparent + ) !important; + border: 1px solid + color-mix(in oklab, var(--ifm-color-primary) 30%, transparent) !important; + border-radius: var(--radius-md) !important; + /* line-height: 0 !important; */ + padding-top: 0.37rem !important; + display: inline-block !important; + text-transform: uppercase !important; + letter-spacing: 0.05em !important; + margin-top: 8px !important; + margin-bottom: 8px !important; +} + +/* ========================================== + 16) LOCAL SEARCH STYLING + ========================================== */ + +/* Local search modal styling - integrate with design system */ +.aa-Panel { + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + border: 1px solid var(--color-border); + background: var(--color-bg); +} + +.aa-Form { + border-radius: var(--radius-md); + background: var(--color-surface); + border: 1px solid var(--color-border); +} + +.aa-Input { + font-family: var(--font-sans); + font-size: var(--font-size-base); + color: var(--color-text); + background: transparent; +} + +.aa-Item { + border-radius: var(--radius-md); + color: var(--color-text); +} + +.aa-Item[aria-selected="true"] { + background: var(--color-surface); +} + +.aa-ItemContentTitle { + font-family: var(--font-sans); + font-weight: 600; + color: var(--color-text); +} + +.aa-ItemContentDescription { + font-family: var(--font-sans); + color: var(--color-muted); +} + +/* ========================================== + 17) NUMERED LISTS + ========================================== */ + +/* === DOCUSAURUS "VitePress-style" numbered steps === */ +.steps { + counter-reset: step-counter; + list-style: none; + padding-left: 1.5rem; + margin-left: 2.5rem; /* Space for number badges and line */ + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} +.steps > ol, +.steps > ul { + list-style: none; + margin-left: 0; + padding-left: 0; +} +.steps li { + counter-increment: step-counter; + position: relative; + margin-bottom: 2rem; + min-height: 2rem; +} + +/* Number badge - dynamically aligns with first element */ +.steps li::before { + content: counter(step-counter); + position: absolute; + left: -4rem; /* Align with .steps margin-left */ + top: 0.15rem; /* Slight offset for visual centering with text baseline */ + width: 2rem; + min-height: 2rem; + border-radius: 0px; + background: var(--ifm-background-surface-color); + color: var(--color-text); + font-size: 1rem; + font-family: var(--font-mono); + padding: 0.3rem 0.4rem; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--ifm-border-color); + z-index: 2; + flex-shrink: 0; + line-height: 1; +} + +/* All headings inside steps get proper spacing */ +.steps li > h1, +.steps li > h2, +.steps li > h3, +.steps li > h4, +.steps li > h5, +.steps li > h6 { + margin-top: 0rem !important; + margin-bottom: 1.5rem !important; + line-height: 1.3 !important; + padding-top: 0.7rem !important; +} /* Specific h5 styling (most common) */ +.steps li > h5 { + font-size: 1.15rem !important; + font-weight: 600 !important; +} /* Style for all content inside step items */ +.steps li > * { + margin-bottom: 1rem; +} +.steps li > *:last-child { + margin-bottom: 0; +} + +/* Paragraphs right after headings - reduce space */ +.steps li > h1 + p, +.steps li > h2 + p, +.steps li > h3 + p, +.steps li > h4 + p, +.steps li > h5 + p, +.steps li > h6 + p { + margin-top: -0.75rem; +} + +/* First paragraph without heading */ +.steps li > p:first-child { + margin-top: 0; +} /* Code blocks within steps */ +.steps li pre, +.steps li .theme-code-block { + margin-top: 0.75rem; + margin-bottom: 0.75rem; +} + +/* Lists within steps */ +.steps li ul, +.steps li ol { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + padding-left: 1.5rem; +} /* Admonitions within steps */ +.steps li .theme-admonition { + margin-top: 0.75rem; + margin-bottom: 0.75rem; +} + +/* ========================================== + 18) DESIGN SYSTEM SPACING SPECIFICATIONS + ========================================== */ + +/* Apply design system spacing to documentation content */ +.theme-doc-markdown h1 { + margin-bottom: var(--space-24px); /* H1 → First content block: 24px */ +} + +.theme-doc-markdown h2 { + margin-top: var( + --space-32px + ); /* H2 → previous section (major section break): 32px */ + margin-bottom: var(--space-16px); /* H2 → Paragraph below: 16px */ +} + +.theme-doc-markdown h3 { + margin-bottom: var(--space-16px); /* H3 → Paragraph below: 16px */ +} + +.theme-doc-markdown h4 { + margin-bottom: var(--space-16px); /* H4 → Paragraph below: 16px */ +} + +/* Paragraph spacing */ +.theme-doc-markdown p { + margin-bottom: var(--space-16px); /* Paragraph → Paragraph: 16px */ +} + +/* List spacing */ +.theme-doc-markdown ul, +.theme-doc-markdown ol { + margin-top: var(--space-16px); /* Paragraph → List: 16px */ + margin-bottom: var(--space-16px); /* List → Next Paragraph: 16px */ + padding-left: var(--space-24px); /* List indent: 24px */ +} + +/* H2 to large components (images, tables, diagrams) */ +.theme-doc-markdown h2 + img, +.theme-doc-markdown h2 + table, +.theme-doc-markdown h2 + .theme-admonition { + margin-top: var(--space-24px); /* H2 → large component: 24px */ +} + +/* Major structural reset for dramatic transitions */ +.theme-doc-markdown > *:first-child { + margin-top: 0; +} + +/* Section breaks - ensure consistent spacing between major sections */ +.theme-doc-markdown h1 + h2 { + margin-top: var(--space-32px); /* Major section break */ +} From c3cc60cd56ffe0dd72f99356c0cf89998ca507ad Mon Sep 17 00:00:00 2001 From: Tomas Fabrizio Orsi Date: Wed, 22 Oct 2025 19:45:00 -0300 Subject: [PATCH 104/133] feat: re-export Section and SectionId (#2015) --- CHANGELOG.md | 2 +- crates/miden-objects/src/lib.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d478810157..cb49a2286b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ - [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973](https://github.com/0xMiden/miden-base/pull/1973)). - Added `account::get_initial_balance` procedure to `miden` lib ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963)). -- Added `MastArtifact`, `PackageExport`, `PackageManifest`, `AttributeSet` and `QualifiedProcedureName` to re-export section ([#1984](https://github.com/0xMiden/miden-base/pull/1984)). +- Added `MastArtifact`, `PackageExport`, `PackageManifest`, `AttributeSet`, `QualifiedProcedureName`, `Section` and `SectionId` to re-export section ([#1984](https://github.com/0xMiden/miden-base/pull/1984) and [#2015](https://github.com/0xMiden/miden-base/pull/2015)). - [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). - Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). diff --git a/crates/miden-objects/src/lib.rs b/crates/miden-objects/src/lib.rs index c7c734bff5..7e35798d81 100644 --- a/crates/miden-objects/src/lib.rs +++ b/crates/miden-objects/src/lib.rs @@ -102,7 +102,14 @@ pub mod vm { pub use miden_assembly_syntax::ast::{AttributeSet, QualifiedProcedureName}; pub use miden_core::sys_events::SystemEvent; pub use miden_core::{AdviceMap, Program, ProgramInfo}; - pub use miden_mast_package::{MastArtifact, Package, PackageExport, PackageManifest}; + pub use miden_mast_package::{ + MastArtifact, + Package, + PackageExport, + PackageManifest, + Section, + SectionId, + }; pub use miden_processor::{AdviceInputs, FutureMaybeSend, RowIndex, StackInputs, StackOutputs}; pub use miden_verifier::ExecutionProof; } From f60406e92410fd993c4fda86342f5cd4dca994c9 Mon Sep 17 00:00:00 2001 From: juan518munoz <62400508+juan518munoz@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:23:51 -0300 Subject: [PATCH 105/133] feat: add `Display` for `AddressInterface` (#2016) --- CHANGELOG.md | 1 + crates/miden-objects/src/address/interface.rs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb49a2286b..3b0e47dd68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). - Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). +- Added `Display` trait for `AddressInterface` ([#2016](https://github.com/0xMiden/miden-base/pull/2016)). ### Changes diff --git a/crates/miden-objects/src/address/interface.rs b/crates/miden-objects/src/address/interface.rs index d298f39480..9116f5a34b 100644 --- a/crates/miden-objects/src/address/interface.rs +++ b/crates/miden-objects/src/address/interface.rs @@ -1,3 +1,5 @@ +use core::fmt::{self, Display, Formatter}; + use crate::AddressError; /// The account interface of an [`Address`](super::Address). @@ -41,3 +43,12 @@ impl TryFrom for AddressInterface { } } } + +impl Display for AddressInterface { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Unspecified => write!(f, "Unspecified"), + Self::BasicWallet => write!(f, "BasicWallet"), + } + } +} From 1161c377b4c15c73b6cce4279284fb6ba1b66407 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Fri, 24 Oct 2025 13:53:32 +0200 Subject: [PATCH 106/133] feat: makes `AccountTree` generic over a `B: AccountTreeBackend` (#2006) --- CHANGELOG.md | 2 + Cargo.lock | 29 +- crates/miden-objects/Cargo.toml | 1 + .../miden-objects/src/block/account_tree.rs | 561 +++++++++++++++--- .../src/block/account_witness.rs | 25 +- crates/miden-objects/src/block/mod.rs | 3 +- .../src/block/partial_account_tree.rs | 24 +- crates/miden-objects/src/testing/block.rs | 14 +- .../block/proven_block_success.rs | 11 +- crates/miden-testing/src/mock_chain/chain.rs | 2 +- .../src/mock_chain/chain_builder.rs | 2 +- 11 files changed, 545 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b0e47dd68..009c4d374e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). - Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). - Added `Display` trait for `AddressInterface` ([#2016](https://github.com/0xMiden/miden-base/pull/2016)). +- [BREAKING] Change `AccountTree` to be generic over `Smt` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). +- [BREAKING] Change `AccountTree` to be generic over `trait AccountTreeBackend` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). ### Changes diff --git a/Cargo.lock b/Cargo.lock index 6d82e86ae0..a7b66b72d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,12 @@ dependencies = [ "equator", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anes" version = "0.1.6" @@ -847,6 +853,12 @@ dependencies = [ "spin 0.9.8", ] +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "fs-err" version = "3.1.3" @@ -1022,6 +1034,19 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", + "rayon", + "serde", +] + [[package]] name = "hashbrown" version = "0.16.0" @@ -1065,7 +1090,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.0", ] [[package]] @@ -1427,6 +1452,7 @@ dependencies = [ "flume", "getrandom 0.2.16", "glob", + "hashbrown 0.15.5", "hkdf", "k256", "num", @@ -1435,6 +1461,7 @@ dependencies = [ "rand_chacha", "rand_core 0.9.3", "rand_hc", + "rayon", "sha3", "thiserror", "winter-crypto", diff --git a/crates/miden-objects/Cargo.toml b/crates/miden-objects/Cargo.toml index 58f1443e89..6bd643c885 100644 --- a/crates/miden-objects/Cargo.toml +++ b/crates/miden-objects/Cargo.toml @@ -26,6 +26,7 @@ std = [ "dep:toml", "miden-assembly/std", "miden-core/std", + "miden-crypto/concurrent", "miden-crypto/std", "miden-processor/std", "miden-verifier/std", diff --git a/crates/miden-objects/src/block/account_tree.rs b/crates/miden-objects/src/block/account_tree.rs index 666fd066c5..0aa6655f9c 100644 --- a/crates/miden-objects/src/block/account_tree.rs +++ b/crates/miden-objects/src/block/account_tree.rs @@ -1,17 +1,222 @@ +use alloc::boxed::Box; use alloc::string::ToString; use alloc::vec::Vec; use miden_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; -use miden_crypto::merkle::{MerkleError, MutationSet, Smt, SmtLeaf}; +use miden_crypto::merkle::{LeafIndex, MerkleError, MutationSet, Smt, SmtLeaf, SmtProof}; use miden_processor::{DeserializationError, SMT_DEPTH}; +use crate::Word; use crate::account::{AccountId, AccountIdPrefix}; use crate::block::AccountWitness; use crate::errors::AccountTreeError; -use crate::{Felt, Word}; -// ACCOUNT TREE +// FREE HELPER FUNCTIONS // ================================================================================================ +// These module-level functions provide conversions between AccountIds and SMT keys. +// They avoid the need for awkward syntax like account_id_to_smt_key(). + +const KEY_PREFIX_IDX: usize = 3; +const KEY_SUFFIX_IDX: usize = 2; + +/// Converts an [`AccountId`] to an SMT key for use in account trees. +/// +/// The key is constructed with the account ID suffix at index 2 and prefix at index 3. +pub fn account_id_to_smt_key(account_id: AccountId) -> Word { + let mut key = Word::empty(); + key[KEY_SUFFIX_IDX] = account_id.suffix(); + key[KEY_PREFIX_IDX] = account_id.prefix().as_felt(); + key +} + +/// Recovers an [`AccountId`] from an SMT key. +/// +/// # Panics +/// +/// Panics if the key does not represent a valid account ID. This should never happen +/// when used with keys from account trees, as the tree only stores valid IDs. +pub fn smt_key_to_account_id(key: Word) -> AccountId { + AccountId::try_from([key[KEY_PREFIX_IDX], key[KEY_SUFFIX_IDX]]) + .expect("account tree should only contain valid IDs") +} + +// ACCOUNT TREE BACKEND TRAIT +// ================================================================================================ + +/// This trait abstracts over different SMT backends (e.g., `Smt` and `LargeSmt`) to allow +/// the `AccountTree` to work with either implementation transparently. +/// +/// Implementors must provide `Default` for creating empty instances. Users should +/// instantiate the backend directly (potentially with entries) and then pass it to +/// [`AccountTree::new`]. +pub trait AccountTreeBackend: Sized { + type Error: core::error::Error + Send + 'static; + + /// Returns the number of leaves in the SMT. + fn num_leaves(&self) -> usize; + + /// Returns all leaves in the SMT as an iterator over leaf index and leaf pairs. + fn leaves<'a>(&'a self) -> Box, SmtLeaf)>>; + + /// Opens the leaf at the given key, returning a Merkle proof. + fn open(&self, key: &Word) -> SmtProof; + + /// Applies the given mutation set to the SMT. + fn apply_mutations( + &mut self, + set: MutationSet, + ) -> Result<(), Self::Error>; + + /// Applies the given mutation set to the SMT and returns the reverse mutation set. + /// + /// The reverse mutation set can be used to revert the changes made by this operation. + fn apply_mutations_with_reversion( + &mut self, + set: MutationSet, + ) -> Result, Self::Error>; + + /// Computes the mutation set required to apply the given updates to the SMT. + fn compute_mutations( + &self, + updates: Vec<(Word, Word)>, + ) -> Result, Self::Error>; + + /// Inserts a key-value pair into the SMT, returning the previous value at that key. + fn insert(&mut self, key: Word, value: Word) -> Result; + + /// Returns the value associated with the given key. + fn get_value(&self, key: &Word) -> Word; + + /// Returns the leaf at the given key. + fn get_leaf(&self, key: &Word) -> SmtLeaf; + + /// Returns the root of the SMT. + fn root(&self) -> Word; +} + +impl AccountTreeBackend for Smt { + type Error = MerkleError; + + fn num_leaves(&self) -> usize { + Smt::num_leaves(self) + } + + fn leaves<'a>(&'a self) -> Box, SmtLeaf)>> { + Box::new(Smt::leaves(self).map(|(idx, leaf)| (idx, leaf.clone()))) + } + + fn open(&self, key: &Word) -> SmtProof { + Smt::open(self, key) + } + + fn apply_mutations( + &mut self, + set: MutationSet, + ) -> Result<(), Self::Error> { + Smt::apply_mutations(self, set) + } + + fn apply_mutations_with_reversion( + &mut self, + set: MutationSet, + ) -> Result, Self::Error> { + Smt::apply_mutations_with_reversion(self, set) + } + + fn compute_mutations( + &self, + updates: Vec<(Word, Word)>, + ) -> Result, Self::Error> { + Smt::compute_mutations(self, updates) + } + + fn insert(&mut self, key: Word, value: Word) -> Result { + Smt::insert(self, key, value) + } + + fn get_value(&self, key: &Word) -> Word { + Smt::get_value(self, key) + } + + fn get_leaf(&self, key: &Word) -> SmtLeaf { + Smt::get_leaf(self, key) + } + + fn root(&self) -> Word { + Smt::root(self) + } +} + +#[cfg(feature = "std")] +use miden_crypto::merkle::{LargeSmt, LargeSmtError, SmtStorage}; +#[cfg(feature = "std")] +fn large_smt_error_to_merkle_error(err: LargeSmtError) -> MerkleError { + match err { + LargeSmtError::Storage(storage_err) => { + panic!("Storage error encountered: {:?}", storage_err) + }, + LargeSmtError::Merkle(merkle_err) => merkle_err, + } +} + +#[cfg(feature = "std")] +impl AccountTreeBackend for LargeSmt +where + Backend: SmtStorage, +{ + type Error = MerkleError; + + fn num_leaves(&self) -> usize { + // LargeSmt::num_leaves returns Result + // We'll unwrap or return 0 on error + LargeSmt::num_leaves(self).map_err(large_smt_error_to_merkle_error).unwrap_or(0) + } + + fn leaves<'a>(&'a self) -> Box, SmtLeaf)>> { + Box::new(LargeSmt::leaves(self).expect("Only IO can error out here")) + } + + fn open(&self, key: &Word) -> SmtProof { + LargeSmt::open(self, key) + } + + fn apply_mutations( + &mut self, + set: MutationSet, + ) -> Result<(), Self::Error> { + LargeSmt::apply_mutations(self, set).map_err(large_smt_error_to_merkle_error) + } + + fn apply_mutations_with_reversion( + &mut self, + set: MutationSet, + ) -> Result, Self::Error> { + LargeSmt::apply_mutations_with_reversion(self, set).map_err(large_smt_error_to_merkle_error) + } + + fn compute_mutations( + &self, + updates: Vec<(Word, Word)>, + ) -> Result, Self::Error> { + LargeSmt::compute_mutations(self, updates).map_err(large_smt_error_to_merkle_error) + } + + fn insert(&mut self, key: Word, value: Word) -> Result { + LargeSmt::insert(self, key, value) + } + + fn get_value(&self, key: &Word) -> Word { + LargeSmt::get_value(self, key) + } + + fn get_leaf(&self, key: &Word) -> SmtLeaf { + LargeSmt::get_leaf(self, key) + } + + fn root(&self) -> Word { + LargeSmt::root(self).map_err(large_smt_error_to_merkle_error).unwrap() + } +} /// The sparse merkle tree of all accounts in the blockchain. /// @@ -22,11 +227,23 @@ use crate::{Felt, Word}; /// Each account ID occupies exactly one leaf in the tree, which is identified by its /// [`AccountId::prefix`]. In other words, account ID prefixes are unique in the blockchain. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct AccountTree { - smt: Smt, +pub struct AccountTree { + smt: S, +} + +impl Default for AccountTree +where + S: Default, +{ + fn default() -> Self { + Self { smt: Default::default() } + } } -impl AccountTree { +impl AccountTree +where + S: AccountTreeBackend, +{ // CONSTANTS // -------------------------------------------------------------------------------------------- @@ -41,68 +258,54 @@ impl AccountTree { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Creates a new, empty account tree. - pub fn new() -> Self { - AccountTree { smt: Smt::new() } - } - - /// Returns a new [`Smt`] instantiated with the provided entries. + /// Creates a new `AccountTree` from its inner representation with validation. /// - /// If the `concurrent` feature of `miden-crypto` is enabled, this function uses a parallel - /// implementation to process the entries efficiently, otherwise it defaults to the - /// sequential implementation. + /// This constructor validates that the provided SMT upholds the guarantees of the + /// [`AccountTree`]. The constructor ensures only the uniqueness of the account ID prefix. /// /// # Errors /// /// Returns an error if: - /// - the provided entries contain multiple commitments for the same account ID. - /// - multiple account IDs share the same prefix. - pub fn with_entries( - entries: impl IntoIterator, - ) -> Result - where - I: ExactSizeIterator, - { - let entries = entries.into_iter(); - let num_accounts = entries.len(); - - let smt = Smt::with_entries( - entries.map(|(id, commitment)| (Self::id_to_smt_key(id), commitment)), - ) - .map_err(|err| { - let MerkleError::DuplicateValuesForIndex(leaf_idx) = err else { - unreachable!("the only error returned by Smt::with_entries is of this type"); - }; - - // SAFETY: Since we only inserted account IDs into the SMT, it is guaranteed that - // the leaf_idx is a valid Felt as well as a valid account ID prefix. - AccountTreeError::DuplicateStateCommitments { - prefix: AccountIdPrefix::new_unchecked( - Felt::try_from(leaf_idx).expect("leaf index should be a valid felt"), - ), - } - })?; - - // If the number of leaves in the SMT is smaller than the number of accounts that were - // passed in, it means that at least one account ID pair ended up in the same leaf. If this - // is the case, we iterate the SMT entries to find the duplicated account ID prefix. - if smt.num_leaves() < num_accounts { - for (leaf_idx, leaf) in smt.leaves() { - if leaf.num_entries() >= 2 { - // SAFETY: Since we only inserted account IDs into the SMT, it is guaranteed - // that the leaf_idx is a valid Felt as well as a valid - // account ID prefix. - return Err(AccountTreeError::DuplicateIdPrefix { - duplicate_prefix: AccountIdPrefix::new_unchecked( - Felt::try_from(leaf_idx.value()) - .expect("leaf index should be a valid felt"), - ), - }); - } + /// - The SMT contains duplicate account ID prefixes + pub fn new(smt: S) -> Result { + for (_leaf_idx, leaf) in smt.leaves() { + match leaf { + SmtLeaf::Empty(_) => { + // Empty leaves are fine (shouldn't be returned by leaves() but handle anyway) + continue; + }, + SmtLeaf::Single((key, _)) => { + // Single entry is good - verify it's a valid account ID + Self::smt_key_to_id(key); + }, + SmtLeaf::Multiple(entries) => { + // Multiple entries means duplicate prefixes + // Extract one of the keys to identify the duplicate prefix + if let Some((key, _)) = entries.first() { + let account_id = Self::smt_key_to_id(*key); + return Err(AccountTreeError::DuplicateIdPrefix { + duplicate_prefix: account_id.prefix(), + }); + } + }, } } - Ok(AccountTree { smt }) + Ok(Self::new_unchecked(smt)) + } + + /// Creates a new `AccountTree` from its inner representation without validation. + /// + /// # Warning + /// + /// Assumes the provided SMT upholds the guarantees of the [`AccountTree`]. Specifically: + /// - Each account ID prefix must be unique (no duplicate prefixes allowed) + /// - The SMT should only contain valid account IDs and their state commitments + /// + /// See type-level documentation for more details on these invariants. Using this constructor + /// with an SMT that violates these guarantees may lead to undefined behavior. + pub fn new_unchecked(smt: S) -> Self { + AccountTree { smt } } // PUBLIC ACCESSORS @@ -112,6 +315,10 @@ impl AccountTree { /// current state commitment of the given account ID. /// /// Conceptually, an opening is a Merkle path to the leaf, as well as the leaf itself. + /// + /// # Panics + /// + /// Panics if the SMT backend fails to open the leaf (only possible with [`LargeSmt`] backend). pub fn open(&self, account_id: AccountId) -> AccountWitness { let key = Self::id_to_smt_key(account_id); let proof = self.smt.open(&key); @@ -158,7 +365,7 @@ impl AccountTree { // SAFETY: By construction, the tree only contains valid IDs. AccountId::try_from([key[Self::KEY_PREFIX_IDX], key[Self::KEY_SUFFIX_IDX]]) .expect("account tree should only contain valid IDs"), - *commitment, + commitment, ) }) } @@ -186,11 +393,11 @@ impl AccountTree { ) -> Result { let mutation_set = self .smt - .compute_mutations( + .compute_mutations(Vec::from_iter( account_commitments .into_iter() .map(|(id, commitment)| (Self::id_to_smt_key(id), commitment)), - ) + )) .map_err(AccountTreeError::ComputeMutations)?; for id_key in mutation_set.new_pairs().keys() { @@ -325,9 +532,48 @@ impl AccountTree { } } -impl Default for AccountTree { - fn default() -> Self { - Self::new() +// CONVENIENCE METHODS +// ================================================================================================ + +impl AccountTree { + /// Creates a new [`AccountTree`] with the provided entries. + /// + /// This is a convenience method for testing that creates an SMT backend with the provided + /// entries and wraps it in an AccountTree. It validates that the entries don't contain + /// duplicate prefixes. + /// + /// # Errors + /// + /// Returns an error if: + /// - The provided entries contain duplicate account ID prefixes + /// - The backend fails to create the SMT with the entries + pub fn with_entries( + entries: impl IntoIterator, + ) -> Result + where + I: ExactSizeIterator, + { + // Create the SMT with the entries + let smt = Smt::with_entries( + entries + .into_iter() + .map(|(id, commitment)| (account_id_to_smt_key(id), commitment)), + ) + .map_err(|err| { + let MerkleError::DuplicateValuesForIndex(leaf_idx) = err else { + unreachable!("the only error returned by Smt::with_entries is of this type"); + }; + + // SAFETY: Since we only inserted account IDs into the SMT, it is guaranteed that + // the leaf_idx is a valid Felt as well as a valid account ID prefix. + AccountTreeError::DuplicateStateCommitments { + prefix: AccountIdPrefix::new_unchecked( + crate::Felt::try_from(leaf_idx).expect("leaf index should be a valid felt"), + ), + } + })?; + + AccountTree::new(smt) } } @@ -343,8 +589,23 @@ impl Serializable for AccountTree { impl Deserializable for AccountTree { fn read_from(source: &mut R) -> Result { let entries = Vec::<(AccountId, Word)>::read_from(source)?; - Self::with_entries(entries) - .map_err(|err| DeserializationError::InvalidValue(err.to_string())) + + // Validate uniqueness of account ID prefixes before creating the tree + let mut seen_prefixes = alloc::collections::BTreeSet::new(); + for (id, _) in &entries { + if !seen_prefixes.insert(id.prefix()) { + return Err(DeserializationError::InvalidValue(format!( + "Duplicate account ID prefix: {}", + id.prefix() + ))); + } + } + + // Create the SMT with validated entries + let smt = + Smt::with_entries(entries.into_iter().map(|(k, v)| (account_id_to_smt_key(k), v))) + .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; + Ok(Self::new_unchecked(smt)) } } @@ -359,7 +620,7 @@ impl Deserializable for AccountTree { /// It is returned by and used in methods on the [`AccountTree`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct AccountMutationSet { - mutation_set: MutationSet<{ AccountTree::DEPTH }, Word, Word>, + mutation_set: MutationSet, } impl AccountMutationSet { @@ -367,7 +628,7 @@ impl AccountMutationSet { // -------------------------------------------------------------------------------------------- /// Creates a new [`AccountMutationSet`] from the provided raw mutation set. - fn new(mutation_set: MutationSet<{ AccountTree::DEPTH }, Word, Word>) -> Self { + fn new(mutation_set: MutationSet) -> Self { Self { mutation_set } } @@ -375,7 +636,7 @@ impl AccountMutationSet { // -------------------------------------------------------------------------------------------- /// Returns a reference to the underlying [`MutationSet`]. - pub fn as_mutation_set(&self) -> &MutationSet<{ AccountTree::DEPTH }, Word, Word> { + pub fn as_mutation_set(&self) -> &MutationSet { &self.mutation_set } @@ -383,7 +644,7 @@ impl AccountMutationSet { // -------------------------------------------------------------------------------------------- /// Consumes self and returns the underlying [`MutationSet`]. - pub fn into_mutation_set(self) -> MutationSet<{ AccountTree::DEPTH }, Word, Word> { + pub fn into_mutation_set(self) -> MutationSet { self.mutation_set } } @@ -425,7 +686,7 @@ pub(super) mod tests { #[test] fn insert_fails_on_duplicate_prefix() { - let mut tree = AccountTree::new(); + let mut tree = AccountTree::::default(); let [(id0, commitment0), (id1, commitment1)] = setup_duplicate_prefix_ids(); tree.insert(id0, commitment0).unwrap(); @@ -438,20 +699,9 @@ pub(super) mod tests { } if duplicate_prefix == id0.prefix()); } - #[test] - fn with_entries_fails_on_duplicate_prefix() { - let entries = setup_duplicate_prefix_ids(); - - let err = AccountTree::with_entries(entries.iter().copied()).unwrap_err(); - - assert_matches!(err, AccountTreeError::DuplicateIdPrefix { - duplicate_prefix - } if duplicate_prefix == entries[0].0.prefix()); - } - #[test] fn insert_succeeds_on_multiple_updates() { - let mut tree = AccountTree::new(); + let mut tree = AccountTree::::default(); let [(id0, commitment0), (_, commitment1)] = setup_duplicate_prefix_ids(); tree.insert(id0, commitment0).unwrap(); @@ -491,7 +741,6 @@ pub(super) mod tests { let commitment2 = Word::from([0, 0, 0, 99u32]); let tree = AccountTree::with_entries([pair0, (id2, commitment2)]).unwrap(); - let err = tree.compute_mutations([pair1]).unwrap_err(); assert_matches!(err, AccountTreeError::DuplicateIdPrefix { @@ -537,8 +786,8 @@ pub(super) mod tests { assert_eq!(tree.num_accounts(), 2); for id in [id0, id1] { - let (control_path, control_leaf) = - tree.smt.open(&AccountTree::id_to_smt_key(id)).into_parts(); + let proof = tree.smt.open(&account_id_to_smt_key(id)); + let (control_path, control_leaf) = proof.into_parts(); let witness = tree.open(id); assert_eq!(witness.leaf(), control_leaf); @@ -550,7 +799,7 @@ pub(super) mod tests { fn contains_account_prefix() { // Create a tree with a single account. let [pair0, pair1] = setup_duplicate_prefix_ids(); - let tree = AccountTree::with_entries([(pair0.0, pair0.1)]).unwrap(); + let tree = AccountTree::with_entries([pair0]).unwrap(); assert_eq!(tree.num_accounts(), 1); // Validate the leaf for the inserted account exists. @@ -563,4 +812,138 @@ pub(super) mod tests { let id1 = AccountIdBuilder::new().build_with_seed([7; 32]); assert!(!tree.contains_account_id_prefix(id1.prefix())); } + + #[cfg(feature = "std")] + #[test] + fn large_smt_backend_basic_operations() { + use miden_crypto::merkle::{LargeSmt, MemoryStorage}; + + // Create test data + let id0 = AccountIdBuilder::new().build_with_seed([5; 32]); + let id1 = AccountIdBuilder::new().build_with_seed([6; 32]); + let id2 = AccountIdBuilder::new().build_with_seed([7; 32]); + + let digest0 = Word::from([0, 0, 0, 1u32]); + let digest1 = Word::from([0, 0, 0, 2u32]); + let digest2 = Word::from([0, 0, 0, 3u32]); + + // Create AccountTree with LargeSmt backend + let tree = LargeSmt::::with_entries( + MemoryStorage::default(), + [(account_id_to_smt_key(id0), digest0), (account_id_to_smt_key(id1), digest1)], + ) + .map(AccountTree::new_unchecked) + .unwrap(); + + // Test basic operations + assert_eq!(tree.num_accounts(), 2); + assert_eq!(tree.get(id0), digest0); + assert_eq!(tree.get(id1), digest1); + + // Test opening + let witness0 = tree.open(id0); + assert_eq!(witness0.id(), id0); + + // Test mutations + let mut tree_mut = LargeSmt::::with_entries( + MemoryStorage::default(), + [(account_id_to_smt_key(id0), digest0), (account_id_to_smt_key(id1), digest1)], + ) + .map(AccountTree::new_unchecked) + .unwrap(); + tree_mut.insert(id2, digest2).unwrap(); + assert_eq!(tree_mut.num_accounts(), 3); + assert_eq!(tree_mut.get(id2), digest2); + + // Verify original tree unchanged + assert_eq!(tree.num_accounts(), 2); + } + + #[cfg(feature = "std")] + #[test] + fn large_smt_backend_duplicate_prefix_check() { + use miden_crypto::merkle::{LargeSmt, MemoryStorage}; + + let [(id0, commitment0), (id1, commitment1)] = setup_duplicate_prefix_ids(); + + let mut tree = AccountTree::new_unchecked(LargeSmt::new(MemoryStorage::default()).unwrap()); + + tree.insert(id0, commitment0).unwrap(); + assert_eq!(tree.get(id0), commitment0); + + let err = tree.insert(id1, commitment1).unwrap_err(); + + assert_matches!( + err, + AccountTreeError::DuplicateIdPrefix { duplicate_prefix } + if duplicate_prefix == id0.prefix() + ); + } + + #[cfg(feature = "std")] + #[test] + fn large_smt_backend_apply_mutations() { + use miden_crypto::merkle::{LargeSmt, MemoryStorage}; + + let id0 = AccountIdBuilder::new().build_with_seed([5; 32]); + let id1 = AccountIdBuilder::new().build_with_seed([6; 32]); + let id2 = AccountIdBuilder::new().build_with_seed([7; 32]); + + let digest0 = Word::from([0, 0, 0, 1u32]); + let digest1 = Word::from([0, 0, 0, 2u32]); + let digest2 = Word::from([0, 0, 0, 3u32]); + let digest3 = Word::from([0, 0, 0, 4u32]); + + let mut tree = LargeSmt::with_entries( + MemoryStorage::default(), + [(account_id_to_smt_key(id0), digest0), (account_id_to_smt_key(id1), digest1)], + ) + .map(AccountTree::new_unchecked) + .unwrap(); + + let mutations = tree + .compute_mutations([(id0, digest1), (id1, digest2), (id2, digest3)]) + .unwrap(); + + tree.apply_mutations(mutations).unwrap(); + + assert_eq!(tree.num_accounts(), 3); + assert_eq!(tree.get(id0), digest1); + assert_eq!(tree.get(id1), digest2); + assert_eq!(tree.get(id2), digest3); + } + + #[cfg(feature = "std")] + #[test] + fn large_smt_backend_same_root_as_regular_smt() { + use miden_crypto::merkle::{LargeSmt, MemoryStorage}; + + let id0 = AccountIdBuilder::new().build_with_seed([5; 32]); + let id1 = AccountIdBuilder::new().build_with_seed([6; 32]); + + let digest0 = Word::from([0, 0, 0, 1u32]); + let digest1 = Word::from([0, 0, 0, 2u32]); + + // Create tree with LargeSmt backend + let large_tree = LargeSmt::with_entries( + MemoryStorage::default(), + [(account_id_to_smt_key(id0), digest0), (account_id_to_smt_key(id1), digest1)], + ) + .map(AccountTree::new_unchecked) + .unwrap(); + + // Create tree with regular Smt backend + let regular_tree = AccountTree::with_entries([(id0, digest0), (id1, digest1)]).unwrap(); + + // Both should have the same root + assert_eq!(large_tree.root(), regular_tree.root()); + + // Both should have the same account commitments + let large_commitments: std::collections::BTreeMap<_, _> = + large_tree.account_commitments().collect(); + let regular_commitments: std::collections::BTreeMap<_, _> = + regular_tree.account_commitments().collect(); + + assert_eq!(large_commitments, regular_commitments); + } } diff --git a/crates/miden-objects/src/block/account_witness.rs b/crates/miden-objects/src/block/account_witness.rs index 6b7b0c003d..ca654f90cb 100644 --- a/crates/miden-objects/src/block/account_witness.rs +++ b/crates/miden-objects/src/block/account_witness.rs @@ -11,17 +11,18 @@ use miden_crypto::merkle::{ }; use crate::account::AccountId; -use crate::block::AccountTree; +use crate::block::account_tree::{account_id_to_smt_key, smt_key_to_account_id}; use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use crate::{AccountTreeError, Word}; // ACCOUNT WITNESS // ================================================================================================ -/// A specialized version of an [`SmtProof`] for use in [`AccountTree`] and +/// A specialized version of an [`SmtProof`] for use in +/// [`AccountTree`](super::account_tree::AccountTree) and /// [`PartialAccountTree`](crate::block::PartialAccountTree). It proves the inclusion of an account /// ID at a certain state (i.e. [`Account::commitment`](crate::account::Account::commitment)) in the -/// [`AccountTree`]. +/// [`AccountTree`](super::account_tree::AccountTree). /// /// By construction the witness can only represent the equivalent of an [`SmtLeaf`] with zero or one /// entries, which guarantees that the account ID prefix it represents is unique in the tree. @@ -48,7 +49,7 @@ impl AccountWitness { /// # Errors /// /// Returns an error if: - /// - the merkle path's depth is not [`AccountTree::DEPTH`]. + /// - the merkle path's depth is not [`SMT_DEPTH`]. pub fn new( account_id: AccountId, commitment: Word, @@ -74,7 +75,7 @@ impl AccountWitness { /// # Panics /// /// Panics if: - /// - the merkle path in the proof does not have depth equal to [`AccountTree::DEPTH`]. + /// - the merkle path in the proof does not have depth equal to [`SMT_DEPTH`]. /// - the proof contains an SmtLeaf::Multiple. pub(super) fn from_smt_proof(requested_account_id: AccountId, proof: SmtProof) -> Self { // Check which account ID this proof actually contains. We rely on the fact that the @@ -89,7 +90,7 @@ impl AccountWitness { SmtLeaf::Empty(_) => requested_account_id, SmtLeaf::Single((key_in_leaf, _)) => { // SAFETY: By construction, the tree only contains valid IDs. - AccountTree::smt_key_to_id(*key_in_leaf) + smt_key_to_account_id(*key_in_leaf) }, SmtLeaf::Multiple(_) => { unreachable!("account tree should only contain zero or one entry per ID prefix") @@ -97,12 +98,12 @@ impl AccountWitness { }; let commitment = proof - .get(&AccountTree::id_to_smt_key(witness_id)) + .get(&account_id_to_smt_key(witness_id)) .expect("we should have received a proof for the witness key"); - // SAFETY: The proof is guaranteed to have depth AccountTree::DEPTH if it comes from one of + // SAFETY: The proof is guaranteed to have depth SMT_DEPTH if it comes from one of // the account trees. - debug_assert_eq!(proof.path().depth(), AccountTree::DEPTH); + debug_assert_eq!(proof.path().depth(), SMT_DEPTH); AccountWitness::new_unchecked(witness_id, commitment, proof.into_parts().0) } @@ -138,10 +139,10 @@ impl AccountWitness { /// Returns the [`SmtLeaf`] of the account witness. pub fn leaf(&self) -> SmtLeaf { if self.commitment == Word::empty() { - let leaf_idx = LeafIndex::from(AccountTree::id_to_smt_key(self.id)); + let leaf_idx = LeafIndex::from(account_id_to_smt_key(self.id)); SmtLeaf::new_empty(leaf_idx) } else { - let key = AccountTree::id_to_smt_key(self.id); + let key = account_id_to_smt_key(self.id); SmtLeaf::new_single(key, self.commitment) } } @@ -149,7 +150,7 @@ impl AccountWitness { /// Consumes self and returns the inner proof. pub fn into_proof(self) -> SmtProof { let leaf = self.leaf(); - debug_assert_eq!(self.path.depth(), AccountTree::DEPTH); + debug_assert_eq!(self.path.depth(), SMT_DEPTH); SmtProof::new(self.path, leaf) .expect("merkle path depth should be the SMT depth by construction") } diff --git a/crates/miden-objects/src/block/mod.rs b/crates/miden-objects/src/block/mod.rs index 7c3afa7c30..083551363f 100644 --- a/crates/miden-objects/src/block/mod.rs +++ b/crates/miden-objects/src/block/mod.rs @@ -16,8 +16,7 @@ pub use nullifier_witness::NullifierWitness; mod partial_account_tree; pub use partial_account_tree::PartialAccountTree; -pub(super) mod account_tree; -pub use account_tree::{AccountMutationSet, AccountTree}; +pub mod account_tree; mod nullifier_tree; pub use nullifier_tree::NullifierTree; diff --git a/crates/miden-objects/src/block/partial_account_tree.rs b/crates/miden-objects/src/block/partial_account_tree.rs index 44e09c43b5..ae3e761f89 100644 --- a/crates/miden-objects/src/block/partial_account_tree.rs +++ b/crates/miden-objects/src/block/partial_account_tree.rs @@ -2,13 +2,14 @@ use miden_crypto::merkle::SmtLeaf; use crate::Word; use crate::account::AccountId; -use crate::block::{AccountTree, AccountWitness}; +use crate::block::AccountWitness; +use crate::block::account_tree::account_id_to_smt_key; use crate::crypto::merkle::PartialSmt; use crate::errors::AccountTreeError; /// The partial sparse merkle tree containing the state commitments of accounts in the chain. /// -/// This is the partial version of [`AccountTree`]. +/// This is the partial version of [`AccountTree`](crate::block::account_tree::AccountTree). #[derive(Debug, Clone, PartialEq, Eq)] pub struct PartialAccountTree { smt: PartialSmt, @@ -55,7 +56,7 @@ impl PartialAccountTree { /// Returns an error if: /// - the account ID is not tracked by this account tree. pub fn open(&self, account_id: AccountId) -> Result { - let key = AccountTree::id_to_smt_key(account_id); + let key = account_id_to_smt_key(account_id); self.smt .open(&key) @@ -70,7 +71,7 @@ impl PartialAccountTree { /// Returns an error if: /// - the account ID is not tracked by this account tree. pub fn get(&self, account_id: AccountId) -> Result { - let key = AccountTree::id_to_smt_key(account_id); + let key = account_id_to_smt_key(account_id); self.smt .get_value(&key) .map_err(|source| AccountTreeError::UntrackedAccountId { id: account_id, source }) @@ -96,7 +97,7 @@ impl PartialAccountTree { /// witness. pub fn track_account(&mut self, witness: AccountWitness) -> Result<(), AccountTreeError> { let id_prefix = witness.id().prefix(); - let id_key = AccountTree::id_to_smt_key(witness.id()); + let id_key = account_id_to_smt_key(witness.id()); let (path, leaf) = witness.into_proof().into_parts(); // If a leaf with the same prefix is already tracked by this partial tree, consider it an @@ -151,7 +152,7 @@ impl PartialAccountTree { account_id: AccountId, state_commitment: Word, ) -> Result { - let key = AccountTree::id_to_smt_key(account_id); + let key = account_id_to_smt_key(account_id); // If there exists a tracked leaf whose key is _not_ the one we're about to overwrite, then // we would insert the new commitment next to an existing account ID with the same prefix, @@ -185,11 +186,12 @@ mod tests { use miden_crypto::merkle::Smt; use super::*; + use crate::block::account_tree::AccountTree; use crate::block::account_tree::tests::setup_duplicate_prefix_ids; #[test] fn insert_fails_on_duplicate_prefix() { - let mut full_tree = AccountTree::new(); + let mut full_tree = AccountTree::::default(); let mut partial_tree = PartialAccountTree::new(); let [(id0, commitment0), (id1, commitment1)] = setup_duplicate_prefix_ids(); @@ -217,7 +219,7 @@ mod tests { #[test] fn insert_succeeds_on_multiple_updates() { - let mut full_tree = AccountTree::new(); + let mut full_tree = AccountTree::::default(); let mut partial_tree = PartialAccountTree::new(); let [(id0, commitment0), (_, commitment1)] = setup_duplicate_prefix_ids(); @@ -258,14 +260,14 @@ mod tests { // account IDs with the same prefix. let full_tree = Smt::with_entries( setup_duplicate_prefix_ids() - .map(|(id, commitment)| (AccountTree::id_to_smt_key(id), commitment)), + .map(|(id, commitment)| (account_id_to_smt_key(id), commitment)), ) .unwrap(); let [(id0, _), (id1, _)] = setup_duplicate_prefix_ids(); - let key0 = AccountTree::id_to_smt_key(id0); - let key1 = AccountTree::id_to_smt_key(id1); + let key0 = account_id_to_smt_key(id0); + let key1 = account_id_to_smt_key(id1); let proof0 = full_tree.open(&key0); let proof1 = full_tree.open(&key1); assert_eq!(proof0.leaf(), proof1.leaf()); diff --git a/crates/miden-objects/src/testing/block.rs b/crates/miden-objects/src/testing/block.rs index 92e1fd78b1..52948bece2 100644 --- a/crates/miden-objects/src/testing/block.rs +++ b/crates/miden-objects/src/testing/block.rs @@ -1,9 +1,11 @@ +use miden_crypto::merkle::Smt; #[cfg(not(target_family = "wasm"))] use winter_rand_utils::rand_value; use crate::Word; use crate::account::Account; -use crate::block::{AccountTree, BlockHeader, BlockNumber, FeeParameters}; +use crate::block::account_tree::{AccountTree, account_id_to_smt_key}; +use crate::block::{BlockHeader, BlockNumber, FeeParameters}; use crate::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET; impl BlockHeader { @@ -20,9 +22,13 @@ impl BlockHeader { accounts: &[Account], tx_kernel_commitment: Word, ) -> Self { - let acct_db = - AccountTree::with_entries(accounts.iter().map(|acct| (acct.id(), acct.commitment()))) - .expect("failed to create account db"); + let smt = Smt::with_entries( + accounts + .iter() + .map(|acct| (account_id_to_smt_key(acct.id()), acct.commitment())), + ) + .expect("failed to create account db"); + let acct_db = AccountTree::new(smt).expect("failed to create account tree"); let account_root = acct_db.root(); let fee_parameters = FeeParameters::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(), 500) diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs index 80804a9ece..ab324b942c 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs @@ -7,13 +7,8 @@ use miden_block_prover::LocalBlockProver; use miden_lib::note::create_p2id_note; use miden_objects::asset::FungibleAsset; use miden_objects::batch::BatchNoteTree; -use miden_objects::block::{ - AccountTree, - BlockInputs, - BlockNoteIndex, - BlockNoteTree, - ProposedBlock, -}; +use miden_objects::block::account_tree::AccountTree; +use miden_objects::block::{BlockInputs, BlockNoteIndex, BlockNoteTree, ProposedBlock}; use miden_objects::crypto::merkle::Smt; use miden_objects::note::NoteType; use miden_objects::transaction::InputNoteCommitment; @@ -398,7 +393,7 @@ async fn proven_block_succeeds_with_empty_batches() -> anyhow::Result<()> { assert_eq!(latest_block_header.commitment(), blockx.commitment()); // Sanity check: The account and nullifier tree roots should not be the empty tree roots. - assert_ne!(latest_block_header.account_root(), AccountTree::new().root()); + assert_ne!(latest_block_header.account_root(), AccountTree::::default().root()); assert_ne!(latest_block_header.nullifier_root(), Smt::new().root()); let (_, empty_partial_blockchain) = chain.latest_selective_partial_blockchain([])?; diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 626d7aa695..5de13af506 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -6,8 +6,8 @@ use miden_block_prover::{LocalBlockProver, ProvenBlockError}; use miden_objects::account::delta::AccountUpdateDetails; use miden_objects::account::{Account, AccountId, AuthSecretKey, PartialAccount}; use miden_objects::batch::{ProposedBatch, ProvenBatch}; +use miden_objects::block::account_tree::AccountTree; use miden_objects::block::{ - AccountTree, AccountWitness, BlockHeader, BlockInputs, diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 802dccd233..77e1ae9332 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -18,8 +18,8 @@ use miden_objects::account::{ StorageSlot, }; use miden_objects::asset::{Asset, FungibleAsset, TokenSymbol}; +use miden_objects::block::account_tree::AccountTree; use miden_objects::block::{ - AccountTree, BlockAccountUpdate, BlockHeader, BlockNoteTree, From a2c57796f1a16b7a5c40c8c7d3ba2e7e536384e9 Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Sat, 25 Oct 2025 01:11:31 +0530 Subject: [PATCH 107/133] feat: Introduce Word wrapper for asset vault keys (#1978) * feat: Introduce Word wrapper for asset vault keys * fix: add changelog * fix: apply suggestions * fix: apply suggestions + tests * fix: nits * fix: rename AssetKey to VaultKey * fix: update tests * fix: update todo * fix: remove as_word function --- CHANGELOG.md | 1 + crates/miden-lib/src/testing/mock_account.rs | 2 +- crates/miden-objects/src/asset/fungible.rs | 13 +- crates/miden-objects/src/asset/mod.rs | 4 +- crates/miden-objects/src/asset/nonfungible.rs | 5 +- .../src/asset/vault/asset_witness.rs | 18 +- crates/miden-objects/src/asset/vault/mod.rs | 38 ++-- .../miden-objects/src/asset/vault/partial.rs | 14 +- .../src/asset/vault/vault_key.rs | 175 ++++++++++++++++++ crates/miden-objects/src/errors.rs | 3 +- .../src/kernel_tests/tx/test_faucet.rs | 2 +- .../src/kernel_tests/tx/test_prologue.rs | 2 +- .../miden-testing/src/tx_context/context.rs | 8 +- crates/miden-tx/src/errors/mod.rs | 5 +- crates/miden-tx/src/executor/data_store.rs | 4 +- crates/miden-tx/src/executor/exec_host.rs | 19 +- crates/miden-tx/src/host/mod.rs | 29 ++- 17 files changed, 257 insertions(+), 85 deletions(-) create mode 100644 crates/miden-objects/src/asset/vault/vault_key.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 009c4d374e..bdb1a67c30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963)). - Added `MastArtifact`, `PackageExport`, `PackageManifest`, `AttributeSet`, `QualifiedProcedureName`, `Section` and `SectionId` to re-export section ([#1984](https://github.com/0xMiden/miden-base/pull/1984) and [#2015](https://github.com/0xMiden/miden-base/pull/2015)). - [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). +- [BREAKING] Introduce `VaultKey` newtype wrapper for asset vault keys ([#1978]https://github.com/0xMiden/miden-base/pull/1978). - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). - Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). - Added `Display` trait for `AddressInterface` ([#2016](https://github.com/0xMiden/miden-base/pull/2016)). diff --git a/crates/miden-lib/src/testing/mock_account.rs b/crates/miden-lib/src/testing/mock_account.rs index 54564bc29e..35fd96e137 100644 --- a/crates/miden-lib/src/testing/mock_account.rs +++ b/crates/miden-lib/src/testing/mock_account.rs @@ -70,7 +70,7 @@ pub trait MockAccountExt { let asset = NonFungibleAsset::mock(&constants::NON_FUNGIBLE_ASSET_DATA_2); let non_fungible_storage_map = - StorageMap::with_entries([(asset.vault_key(), asset.into())]).unwrap(); + StorageMap::with_entries([(asset.vault_key().into(), asset.into())]).unwrap(); let storage = AccountStorage::new(vec![StorageSlot::Map(non_fungible_storage_map)]).unwrap(); diff --git a/crates/miden-objects/src/asset/fungible.rs b/crates/miden-objects/src/asset/fungible.rs index 17f1ae112c..94c90af4c2 100644 --- a/crates/miden-objects/src/asset/fungible.rs +++ b/crates/miden-objects/src/asset/fungible.rs @@ -2,6 +2,7 @@ use alloc::boxed::Box; use alloc::string::ToString; use core::fmt; +use super::vault::VaultKey; use super::{AccountType, Asset, AssetError, Felt, Word, ZERO, is_not_a_non_fungible_asset}; use crate::account::{AccountId, AccountIdPrefix}; use crate::utils::serde::{ @@ -83,8 +84,8 @@ impl FungibleAsset { } /// Returns the key which is used to store this asset in the account vault. - pub fn vault_key(&self) -> Word { - Self::vault_key_from_faucet(self.faucet_id) + pub fn vault_key(&self) -> VaultKey { + VaultKey::from_account_id(self.faucet_id).expect("faucet ID should be of type fungible") } // OPERATIONS @@ -161,14 +162,6 @@ impl FungibleAsset { Ok(self) } - - /// Returns the key which is used to store this asset in the account vault. - pub(super) fn vault_key_from_faucet(faucet_id: AccountId) -> Word { - let mut key = Word::empty(); - key[2] = faucet_id.suffix(); - key[3] = faucet_id.prefix().as_felt(); - key - } } impl From for Word { diff --git a/crates/miden-objects/src/asset/mod.rs b/crates/miden-objects/src/asset/mod.rs index 9fdd803870..4113e5e070 100644 --- a/crates/miden-objects/src/asset/mod.rs +++ b/crates/miden-objects/src/asset/mod.rs @@ -22,7 +22,7 @@ mod token_symbol; pub use token_symbol::TokenSymbol; mod vault; -pub use vault::{AssetVault, AssetWitness, PartialVault}; +pub use vault::{AssetVault, AssetWitness, PartialVault, VaultKey}; // ASSET // ================================================================================================ @@ -137,7 +137,7 @@ impl Asset { } /// Returns the key which is used to store this asset in the account vault. - pub fn vault_key(&self) -> Word { + pub fn vault_key(&self) -> VaultKey { match self { Self::Fungible(asset) => asset.vault_key(), Self::NonFungible(asset) => asset.vault_key(), diff --git a/crates/miden-objects/src/asset/nonfungible.rs b/crates/miden-objects/src/asset/nonfungible.rs index 65d3b6f7e4..a30384921c 100644 --- a/crates/miden-objects/src/asset/nonfungible.rs +++ b/crates/miden-objects/src/asset/nonfungible.rs @@ -3,6 +3,7 @@ use alloc::string::ToString; use alloc::vec::Vec; use core::fmt; +use super::vault::VaultKey; use super::{AccountIdPrefix, AccountType, Asset, AssetError, Felt, Hasher, Word}; use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use crate::{FieldElement, WORD_SIZE}; @@ -106,7 +107,7 @@ impl NonFungibleAsset { /// It also ensures that there is never any collision in the leaf index between a non-fungible /// asset and a fungible asset, as the former's vault key always has the fungible bit set to `0` /// and the latter's vault key always has the bit set to `1`. - pub fn vault_key(&self) -> Word { + pub fn vault_key(&self) -> VaultKey { let mut vault_key = self.0; // Swap prefix of faucet ID with hash0. @@ -116,7 +117,7 @@ impl NonFungibleAsset { vault_key[3] = AccountIdPrefix::clear_fungible_bit(self.faucet_id_prefix().version(), vault_key[3]); - vault_key + VaultKey::new_unchecked(vault_key) } /// Return ID prefix of the faucet which issued this asset. diff --git a/crates/miden-objects/src/asset/vault/asset_witness.rs b/crates/miden-objects/src/asset/vault/asset_witness.rs index 7193d8bb4f..0e56ddf20b 100644 --- a/crates/miden-objects/src/asset/vault/asset_witness.rs +++ b/crates/miden-objects/src/asset/vault/asset_witness.rs @@ -1,7 +1,8 @@ use miden_crypto::merkle::{InnerNodeInfo, SmtLeaf, SmtProof}; +use super::vault_key::VaultKey; +use crate::AssetError; use crate::asset::Asset; -use crate::{AssetError, Word}; /// A witness of an asset in an [`AssetVault`](super::AssetVault). /// @@ -23,10 +24,10 @@ impl AssetWitness { pub fn new(smt_proof: SmtProof) -> Result { for (vault_key, asset) in smt_proof.leaf().entries() { let asset = Asset::try_from(asset)?; - if asset.vault_key() != *vault_key { + if *vault_key != asset.vault_key().into() { return Err(AssetError::VaultKeyMismatch { actual: *vault_key, - expected: asset.vault_key(), + expected: asset.vault_key().into(), }); } } @@ -46,7 +47,7 @@ impl AssetWitness { // -------------------------------------------------------------------------------------------- /// Searches for an [`Asset`] in the witness with the given `vault_key`. - pub fn find(&self, vault_key: Word) -> Option { + pub fn find(&self, vault_key: VaultKey) -> Option { self.assets().find(|asset| asset.vault_key() == vault_key) } @@ -114,14 +115,15 @@ mod tests { let fungible_asset = FungibleAsset::mock(500); let non_fungible_asset = NonFungibleAsset::mock(&[1]); - let smt = Smt::with_entries([(fungible_asset.vault_key(), non_fungible_asset.into())])?; - let proof = smt.open(&fungible_asset.vault_key()); + let smt = + Smt::with_entries([(fungible_asset.vault_key().into(), non_fungible_asset.into())])?; + let proof = smt.open(&fungible_asset.vault_key().into()); let err = AssetWitness::new(proof).unwrap_err(); assert_matches!(err, AssetError::VaultKeyMismatch { actual, expected } => { - assert_eq!(actual, fungible_asset.vault_key()); - assert_eq!(expected, non_fungible_asset.vault_key()); + assert_eq!(actual, fungible_asset.vault_key().into()); + assert_eq!(expected, non_fungible_asset.vault_key().into()); }); Ok(()) diff --git a/crates/miden-objects/src/asset/vault/mod.rs b/crates/miden-objects/src/asset/vault/mod.rs index 5f421704e2..553fddf8bc 100644 --- a/crates/miden-objects/src/asset/vault/mod.rs +++ b/crates/miden-objects/src/asset/vault/mod.rs @@ -16,7 +16,7 @@ use super::{ }; use crate::account::{AccountId, AccountVaultDelta, NonFungibleDeltaAction}; use crate::crypto::merkle::Smt; -use crate::{AssetVaultError, Felt, Word}; +use crate::{AssetVaultError, Word}; mod partial; pub use partial::PartialVault; @@ -24,6 +24,9 @@ pub use partial::PartialVault; mod asset_witness; pub use asset_witness::AssetWitness; +mod vault_key; +pub use vault_key::VaultKey; + // ASSET VAULT // ================================================================================================ @@ -57,7 +60,7 @@ impl AssetVault { pub fn new(assets: &[Asset]) -> Result { Ok(Self { asset_tree: Smt::with_entries( - assets.iter().map(|asset| (asset.vault_key(), (*asset).into())), + assets.iter().map(|asset| (asset.vault_key().into(), (*asset).into())), ) .map_err(AssetVaultError::DuplicateAsset)?, }) @@ -74,7 +77,7 @@ impl AssetVault { /// Returns true if the specified non-fungible asset is stored in this vault. pub fn has_non_fungible_asset(&self, asset: NonFungibleAsset) -> Result { // check if the asset is stored in the vault - match self.asset_tree.get_value(&asset.vault_key()) { + match self.asset_tree.get_value(&asset.vault_key().into()) { asset if asset == Smt::EMPTY_VALUE => Ok(false), _ => Ok(true), } @@ -91,7 +94,11 @@ impl AssetVault { } // if the tree value is [0, 0, 0, 0], the asset is not stored in the vault - match self.asset_tree.get_value(&FungibleAsset::vault_key_from_faucet(faucet_id)) { + match self.asset_tree.get_value( + &VaultKey::from_account_id(faucet_id) + .expect("faucet ID should be of type fungible") + .into(), + ) { asset if asset == Smt::EMPTY_VALUE => Ok(0), asset => Ok(FungibleAsset::new_unchecked(asset).amount()), } @@ -111,8 +118,8 @@ impl AssetVault { /// Returns an opening of the leaf associated with `vault_key`. /// /// The `vault_key` can be obtained with [`Asset::vault_key`]. - pub fn open(&self, vault_key: Word) -> AssetWitness { - let smt_proof = self.asset_tree.open(&vault_key); + pub fn open(&self, vault_key: VaultKey) -> AssetWitness { + let smt_proof = self.asset_tree.open(&vault_key.into()); // SAFETY: The asset vault should only contain valid assets. AssetWitness::new_unchecked(smt_proof) } @@ -138,13 +145,6 @@ impl AssetVault { self.asset_tree.num_entries() } - // TODO: Replace with https://github.com/0xMiden/crypto/issues/515 once implemented. - /// Returns the leaf index of a vault key. - pub fn vault_key_to_leaf_index(vault_key: Word) -> Felt { - // The third element in an SMT key is the index. - vault_key[3] - } - // PUBLIC MODIFIERS // -------------------------------------------------------------------------------------------- @@ -204,7 +204,7 @@ impl AssetVault { asset: FungibleAsset, ) -> Result { // fetch current asset value from the tree and add the new asset to it. - let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key()) { + let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key().into()) { current if current == Smt::EMPTY_VALUE => asset, current => { let current = FungibleAsset::new_unchecked(current); @@ -212,7 +212,7 @@ impl AssetVault { }, }; self.asset_tree - .insert(new.vault_key(), new.into()) + .insert(new.vault_key().into(), new.into()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return the new asset @@ -231,7 +231,7 @@ impl AssetVault { // add non-fungible asset to the vault let old = self .asset_tree - .insert(asset.vault_key(), asset.into()) + .insert(asset.vault_key().into(), asset.into()) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // if the asset already exists, return an error @@ -275,7 +275,7 @@ impl AssetVault { asset: FungibleAsset, ) -> Result { // fetch the asset from the vault. - let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key()) { + let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key().into()) { current if current == Smt::EMPTY_VALUE => { return Err(AssetVaultError::FungibleAssetNotFound(asset)); }, @@ -291,7 +291,7 @@ impl AssetVault { _ => new.into(), }; self.asset_tree - .insert(new.vault_key(), value) + .insert(new.vault_key().into(), value) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return the asset that was removed. @@ -311,7 +311,7 @@ impl AssetVault { // remove the asset from the vault. let old = self .asset_tree - .insert(asset.vault_key(), Smt::EMPTY_VALUE) + .insert(asset.vault_key().into(), Smt::EMPTY_VALUE) .map_err(AssetVaultError::MaxLeafEntriesExceeded)?; // return an error if the asset did not exist in the vault. diff --git a/crates/miden-objects/src/asset/vault/partial.rs b/crates/miden-objects/src/asset/vault/partial.rs index 3925dfbc38..b62ad53a97 100644 --- a/crates/miden-objects/src/asset/vault/partial.rs +++ b/crates/miden-objects/src/asset/vault/partial.rs @@ -2,7 +2,7 @@ use alloc::string::ToString; use miden_crypto::merkle::{InnerNodeInfo, MerkleError, PartialSmt, SmtLeaf, SmtProof}; -use super::AssetVault; +use super::{AssetVault, VaultKey}; use crate::Word; use crate::asset::{Asset, AssetWitness}; use crate::errors::PartialAssetVaultError; @@ -60,7 +60,7 @@ impl PartialVault { // vault. This is the most minimal and correct partial vault we can build. // TODO: Workaround for https://github.com/0xMiden/miden-base/issues/1966. Fix when implemented. partial_vault - .add(vault.open(Word::empty())) + .add(vault.open(VaultKey::new_unchecked(Word::empty()))) .expect("adding the first proof should never fail"); partial_vault @@ -97,10 +97,10 @@ impl PartialVault { /// /// Returns an error if: /// - the key is not tracked by this partial vault. - pub fn open(&self, vault_key: Word) -> Result { + pub fn open(&self, vault_key: VaultKey) -> Result { let smt_proof = self .partial_smt - .open(&vault_key) + .open(&vault_key.into()) .map_err(PartialAssetVaultError::UntrackedAsset)?; // SAFETY: The partial vault should only contain valid assets. Ok(AssetWitness::new_unchecked(smt_proof)) @@ -114,8 +114,8 @@ impl PartialVault { /// /// Returns an error if: /// - the key is not tracked by this partial SMT. - pub fn get(&self, vault_key: Word) -> Result, MerkleError> { - self.partial_smt.get_value(&vault_key).map(|word| { + pub fn get(&self, vault_key: VaultKey) -> Result, MerkleError> { + self.partial_smt.get_value(&vault_key.into()).map(|word| { if word.is_empty() { None } else { @@ -158,7 +158,7 @@ impl PartialVault { PartialAssetVaultError::InvalidAssetInSmt { entry: *asset, source } })?; - if asset.vault_key() != *vault_key { + if *vault_key != asset.vault_key().into() { return Err(PartialAssetVaultError::VaultKeyMismatch { expected: asset.vault_key(), actual: *vault_key, diff --git a/crates/miden-objects/src/asset/vault/vault_key.rs b/crates/miden-objects/src/asset/vault/vault_key.rs new file mode 100644 index 0000000000..5f26f59df6 --- /dev/null +++ b/crates/miden-objects/src/asset/vault/vault_key.rs @@ -0,0 +1,175 @@ +use core::fmt; + +use miden_crypto::merkle::LeafIndex; +use miden_processor::SMT_DEPTH; + +use crate::Word; +use crate::account::AccountType::FungibleFaucet; +use crate::account::{AccountId, AccountIdPrefix}; +use crate::asset::{Asset, FungibleAsset, NonFungibleAsset}; + +/// The key of an [`Asset`] in the asset vault. +/// +/// The layout of an asset key is: +/// - Fungible asset key: `[0, 0, faucet_id_suffix, faucet_id_prefix]`. +/// - Non-fungible asset key: `[faucet_id_prefix, hash1, hash2, hash0']`, where `hash0'` is +/// equivalent to `hash0` with the fungible bit set to `0`. See [`NonFungibleAsset::vault_key`] +/// for more details. +/// +/// For details on the layout of an asset, see the documentation of [`Asset`]. +/// +/// ## Guarantees +/// +/// This type guarantees that it contains a valid fungible or non-fungible asset key: +/// - For fungible assets +/// - The felt at index 3 has the fungible bit set to 1 and it is a valid account ID prefix. +/// - The felt at index 2 is a valid account ID suffix. +/// - For non-fungible assets +/// - The felt at index 3 has the fungible bit set to 0. +/// - The felt at index 0 is a valid account ID prefix. +/// +/// The fungible bit is the bit in the [`AccountId`] that encodes whether the ID is a faucet. +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub struct VaultKey(Word); + +impl VaultKey { + /// Creates a new [`VaultKey`] from the given [`Word`] **without performing validation**. + /// + /// ## Warning + /// + /// This function **does not check** whether the provided `Word` represents a valid + /// fungible or non-fungible asset key. + pub fn new_unchecked(value: Word) -> Self { + Self(value) + } + + /// Returns an [`AccountIdPrefix`] from the asset key. + pub fn faucet_id_prefix(&self) -> AccountIdPrefix { + if self.is_fungible() { + AccountIdPrefix::new_unchecked(self.0[3]) + } else { + AccountIdPrefix::new_unchecked(self.0[0]) + } + } + + /// Returns the [`AccountId`] from the asset key if it is a fungible asset, `None` otherwise. + pub fn faucet_id(&self) -> Option { + if self.is_fungible() { + Some(AccountId::new_unchecked([self.0[3], self.0[2]])) + } else { + None + } + } + + /// Returns the leaf index of a vault key. + pub fn to_leaf_index(&self) -> LeafIndex { + LeafIndex::::from(self.0) + } + + /// Constructs a fungible asset's key from a faucet ID. + /// + /// Returns `None` if the provided ID is not of type + /// [`AccountType::FungibleFaucet`](crate::account::AccountType::FungibleFaucet) + pub fn from_account_id(faucet_id: AccountId) -> Option { + match faucet_id.account_type() { + FungibleFaucet => { + let mut key = Word::empty(); + key[2] = faucet_id.suffix(); + key[3] = faucet_id.prefix().as_felt(); + Some(VaultKey::new_unchecked(key)) + }, + _ => None, + } + } + + /// Returns `true` if the asset key is for a fungible asset, `false` otherwise. + fn is_fungible(&self) -> bool { + self.0[0].as_int() == 0 && self.0[1].as_int() == 0 + } +} + +impl fmt::Display for VaultKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +// CONVERSIONS +// ================================================================================================ + +impl From for Word { + fn from(vault_key: VaultKey) -> Self { + vault_key.0 + } +} + +impl From for VaultKey { + fn from(asset: Asset) -> Self { + asset.vault_key() + } +} + +impl From for VaultKey { + fn from(fungible_asset: FungibleAsset) -> Self { + fungible_asset.vault_key() + } +} + +impl From for VaultKey { + fn from(non_fungible_asset: NonFungibleAsset) -> Self { + non_fungible_asset.vault_key() + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_core::Felt; + + use super::*; + use crate::account::{AccountIdVersion, AccountStorageMode, AccountType}; + + fn make_non_fungible_key(prefix: u64) -> VaultKey { + let word = [Felt::new(prefix), Felt::new(11), Felt::new(22), Felt::new(33)].into(); + VaultKey::new_unchecked(word) + } + + #[test] + fn test_faucet_id_for_fungible_asset() { + let id = AccountId::dummy( + [0xff; 15], + AccountIdVersion::Version0, + AccountType::FungibleFaucet, + AccountStorageMode::Public, + ); + + let key = VaultKey::from_account_id(id).expect("Expected VaultKey for FungibleFaucet"); + + // faucet_id_prefix() should match AccountId prefix + assert_eq!(key.faucet_id_prefix(), id.prefix()); + + // faucet_id() should return the same account id + assert_eq!(key.faucet_id().unwrap(), id); + } + + #[test] + fn test_faucet_id_for_non_fungible_asset() { + let id = AccountId::dummy( + [0xff; 15], + AccountIdVersion::Version0, + AccountType::NonFungibleFaucet, + AccountStorageMode::Public, + ); + + let prefix_value = id.prefix().as_u64(); + let key = make_non_fungible_key(prefix_value); + + // faucet_id_prefix() should match AccountId prefix + assert_eq!(key.faucet_id_prefix(), id.prefix()); + + // faucet_id() should return the None + assert_eq!(key.faucet_id(), None); + } +} diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index bfb5cbc5f2..27f8f10e87 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -28,6 +28,7 @@ use crate::account::{ TemplateTypeError, }; use crate::address::AddressType; +use crate::asset::VaultKey; use crate::batch::BatchId; use crate::block::BlockNumber; use crate::note::{NoteAssets, NoteExecutionHint, NoteTag, NoteType, Nullifier}; @@ -470,7 +471,7 @@ pub enum PartialAssetVaultError { #[error("provided SMT entry {entry} is not a valid asset")] InvalidAssetInSmt { entry: Word, source: AssetError }, #[error("expected asset vault key to be {expected} but it was {actual}")] - VaultKeyMismatch { expected: Word, actual: Word }, + VaultKeyMismatch { expected: VaultKey, actual: Word }, #[error("failed to add asset proof")] FailedToAddProof(#[source] MerkleError), #[error("asset is not tracked in the partial vault")] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs index 04b46881ec..7eab71ff10 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_faucet.rs @@ -233,7 +233,7 @@ async fn test_mint_non_fungible_asset_succeeds() -> anyhow::Result<()> { end "#, non_fungible_asset = Word::from(non_fungible_asset), - asset_vault_key = StorageMap::hash_key(asset_vault_key), + asset_vault_key = StorageMap::hash_key(asset_vault_key.into()), ); tx_context.execute_code(&code).await?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index 949aca5055..b3d65aa2f3 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -685,7 +685,7 @@ pub async fn create_account_non_fungible_faucet_invalid_initial_reserved_slot() // Create a storage map with a mock asset to make it non-empty. let asset = NonFungibleAsset::mock(&[1, 2, 3, 4]); let non_fungible_storage_map = - StorageMap::with_entries([(asset.vault_key(), asset.into())]).unwrap(); + StorageMap::with_entries([(asset.vault_key().into(), asset.into())]).unwrap(); let storage = AccountStorage::new(vec![StorageSlot::Map(non_fungible_storage_map)]).unwrap(); let account = AccountBuilder::new([1; 32]) diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index 288ab33266..9fc1d0448f 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -7,7 +7,7 @@ use miden_lib::transaction::TransactionKernel; use miden_objects::account::{Account, AccountId, PartialAccount, StorageMapWitness, StorageSlot}; use miden_objects::assembly::debuginfo::{SourceLanguage, Uri}; use miden_objects::assembly::{SourceManager, SourceManagerSync}; -use miden_objects::asset::AssetWitness; +use miden_objects::asset::{AssetWitness, VaultKey}; use miden_objects::block::{AccountWitness, BlockHeader, BlockNumber}; use miden_objects::note::{Note, NoteScript}; use miden_objects::transaction::{ @@ -230,7 +230,7 @@ impl DataStore for TransactionContext { &self, account_id: AccountId, vault_root: Word, - vault_key: Word, + asset_key: VaultKey, ) -> impl FutureMaybeSend> { async move { if account_id == self.account().id() { @@ -241,7 +241,7 @@ impl DataStore for TransactionContext { ))); } - Ok(self.account().vault().open(vault_key)) + Ok(self.account().vault().open(asset_key)) } else { let (foreign_account, _witness) = self .foreign_account_inputs @@ -264,7 +264,7 @@ impl DataStore for TransactionContext { ))); } - Ok(foreign_account.vault().open(vault_key)) + Ok(foreign_account.vault().open(asset_key)) } } } diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index fc7fafbc50..e959ec09ba 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -6,6 +6,7 @@ use core::error::Error; use miden_lib::transaction::TransactionAdviceMapMismatch; use miden_objects::account::AccountId; use miden_objects::assembly::diagnostics::reporting::PrintDiagnostic; +use miden_objects::asset::VaultKey; use miden_objects::block::BlockNumber; use miden_objects::crypto::merkle::SmtProofError; use miden_objects::note::{NoteId, NoteMetadata}; @@ -287,11 +288,11 @@ pub enum TransactionKernelError { source: DataStoreError, }, #[error( - "failed to get vault asset witness from data store for vault root {vault_root} and vault_key {vault_key}" + "failed to get vault asset witness from data store for vault root {vault_root} and vault_key {asset_key}" )] GetVaultAssetWitness { vault_root: Word, - vault_key: Word, + asset_key: VaultKey, // thiserror will return this when calling Error::source on TransactionKernelError. source: DataStoreError, }, diff --git a/crates/miden-tx/src/executor/data_store.rs b/crates/miden-tx/src/executor/data_store.rs index 5591abc71c..cee0887bad 100644 --- a/crates/miden-tx/src/executor/data_store.rs +++ b/crates/miden-tx/src/executor/data_store.rs @@ -1,7 +1,7 @@ use alloc::collections::BTreeSet; use miden_objects::account::{AccountId, PartialAccount, StorageMapWitness}; -use miden_objects::asset::AssetWitness; +use miden_objects::asset::{AssetWitness, VaultKey}; use miden_objects::block::{BlockHeader, BlockNumber}; use miden_objects::note::NoteScript; use miden_objects::transaction::{AccountInputs, PartialBlockchain}; @@ -51,7 +51,7 @@ pub trait DataStore: MastForestStore { &self, account_id: AccountId, vault_root: Word, - vault_key: Word, + vault_key: VaultKey, ) -> impl FutureMaybeSend>; /// Returns a witness for a storage map item identified by `map_key` in the requested account's diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index c1bf3273a0..71245f6cf0 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -12,7 +12,7 @@ use miden_objects::account::{ }; use miden_objects::assembly::debuginfo::Location; use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; -use miden_objects::asset::{Asset, AssetWitness, FungibleAsset}; +use miden_objects::asset::{Asset, AssetWitness, FungibleAsset, VaultKey}; use miden_objects::block::BlockNumber; use miden_objects::crypto::merkle::SmtProof; use miden_objects::transaction::{InputNote, InputNotes, OutputNote}; @@ -217,7 +217,7 @@ where .await .map_err(|err| TransactionKernelError::GetVaultAssetWitness { vault_root: self.base_host.initial_account_header().vault_root(), - vault_key: fee_asset.vault_key(), + asset_key: fee_asset.vault_key(), source: err, })?; @@ -346,17 +346,16 @@ where &self, current_account_id: AccountId, vault_root: Word, - asset: Asset, + asset_key: VaultKey, ) -> Result, TransactionKernelError> { - let vault_key = asset.vault_key(); let asset_witness = self .base_host .store() - .get_vault_asset_witness(current_account_id, vault_root, vault_key) + .get_vault_asset_witness(current_account_id, vault_root, asset_key) .await .map_err(|err| TransactionKernelError::GetVaultAssetWitness { vault_root, - vault_key, + asset_key, source: err, })?; @@ -452,9 +451,13 @@ where TransactionEventData::AccountVaultAssetWitness { current_account_id, vault_root, - asset, + asset_key, } => self - .on_account_vault_asset_witness_requested(current_account_id, vault_root, asset) + .on_account_vault_asset_witness_requested( + current_account_id, + vault_root, + asset_key, + ) .await .map_err(EventError::from), TransactionEventData::AccountStorageMapWitness { diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 7993dd9b09..4961e78cdb 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -39,7 +39,7 @@ use miden_objects::account::{ StorageMap, StorageSlotType, }; -use miden_objects::asset::{Asset, AssetVault, FungibleAsset}; +use miden_objects::asset::{Asset, AssetVault, FungibleAsset, VaultKey}; use miden_objects::note::NoteId; use miden_objects::transaction::{ InputNote, @@ -818,7 +818,7 @@ where )) })?; - self.on_account_vault_asset_accessed(process, asset, current_vault_root) + self.on_account_vault_asset_accessed(process, asset.vault_key(), current_vault_root) } /// Extracts the asset that is being removed from the account's vault from the process state @@ -861,17 +861,12 @@ where let vault_root_ptr = stack_top[1]; let vault_root = Self::get_vault_root(process, vault_root_ptr)?; - // Construct the fungible asset so we can easily fetch the vault key. - // TODO: Replace this once we have a AssetKey type that can be constructed from a faucet ID - // directly: https://github.com/0xMiden/miden-base/issues/1890. - let asset = FungibleAsset::new(faucet_id, 0).map_err(|err| { - TransactionKernelError::other_with_source( - "provided faucet ID is not valid for fungible assets", - err, - ) + let vault_key = VaultKey::from_account_id(faucet_id).ok_or_else(|| { + TransactionKernelError::other(format!( + "provided faucet ID {faucet_id} is not valid for fungible assets" + )) })?; - - self.on_account_vault_asset_accessed(process, asset.into(), vault_root) + self.on_account_vault_asset_accessed(process, vault_key, vault_root) } /// Checks if the necessary witness for accessing the asset is already in the merkle store, @@ -890,7 +885,7 @@ where let vault_root_ptr = process.get_stack_item(5); let vault_root = Self::get_vault_root(process, vault_root_ptr)?; - self.on_account_vault_asset_accessed(process, asset, vault_root) + self.on_account_vault_asset_accessed(process, asset.vault_key(), vault_root) } /// Checks if the necessary witness for accessing the provided asset is already in the merkle @@ -898,10 +893,10 @@ where fn on_account_vault_asset_accessed( &self, process: &ProcessState, - asset: Asset, + vault_key: VaultKey, current_vault_root: Word, ) -> Result { - let leaf_index = AssetVault::vault_key_to_leaf_index(asset.vault_key()); + let leaf_index = Felt::new(vault_key.to_leaf_index().value()); let current_account_id = Self::get_current_account_id(process)?; // Note that we check whether a merkle path for the current vault root is present, not @@ -929,7 +924,7 @@ where TransactionEventData::AccountVaultAssetWitness { current_account_id, vault_root, - asset, + asset_key: vault_key, }, )) } @@ -1184,7 +1179,7 @@ pub(super) enum TransactionEventData { /// The vault root identifying the asset vault from which a witness is requested. vault_root: Word, /// The asset for which a witness is requested. - asset: Asset, + asset_key: VaultKey, }, /// The data necessary to request a storage map witness from the data store. AccountStorageMapWitness { From efdf2565db20b355692656d16d8c3aa133dee502 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Sat, 25 Oct 2025 08:25:25 +0700 Subject: [PATCH 108/133] chore: Re-add bech32 encoding for `AccountId` (#2018) * chore: Revert PR 1762 * chore: adjust to compile * chore: add changelog * chore: Address review comments --- CHANGELOG.md | 1 + .../src/account/account_id/mod.rs | 177 ++++++++++++++++++ .../src/account/account_id/v0/mod.rs | 69 ++++++- crates/miden-objects/src/errors.rs | 2 + docs/src/account/id.md | 24 ++- 5 files changed, 265 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdb1a67c30..2a0069cc85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). - Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). - Added `Display` trait for `AddressInterface` ([#2016](https://github.com/0xMiden/miden-base/pull/2016)). +- Re-add bech32 encoding for `AccountId` ([#2018](https://github.com/0xMiden/miden-base/pull/2018)). - [BREAKING] Change `AccountTree` to be generic over `Smt` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). - [BREAKING] Change `AccountTree` to be generic over `trait AccountTreeBackend` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). diff --git a/crates/miden-objects/src/account/account_id/mod.rs b/crates/miden-objects/src/account/account_id/mod.rs index 2ca927f7ea..51aa5bd849 100644 --- a/crates/miden-objects/src/account/account_id/mod.rs +++ b/crates/miden-objects/src/account/account_id/mod.rs @@ -22,6 +22,7 @@ use miden_core::utils::{ByteReader, Deserializable, Serializable}; use miden_crypto::utils::hex_to_bytes; use miden_processor::DeserializationError; +use crate::address::NetworkId; use crate::errors::AccountIdError; use crate::{AccountError, Word}; @@ -272,6 +273,49 @@ impl AccountId { } } + /// Encodes the [`AccountId`] into a [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) + /// string. + /// + /// # Encoding + /// + /// The encoding of an account ID into bech32 is done as follows: + /// - Convert the account ID into its `[u8; 15]` data format. + /// - Insert the address type `AddressType::AccountId` byte at index 0, shifting all other + /// elements to the right. + /// - Choose an HRP, defined as a [`NetworkId`], for example [`NetworkId::Mainnet`] whose string + /// representation is `mm`. + /// - Encode the resulting HRP together with the data into a bech32 string using the + /// [`bech32::Bech32m`] checksum algorithm. + /// + /// This is an example of an account ID in hex and bech32 representations: + /// + /// ```text + /// hex: 0xd7585ada5ab5d2b01c77fad88c0ae4 + /// bech32: mm1qrt4skk6t26a9vquwlad3rq2usul8fy2 + /// ``` + /// + /// ## Rationale + /// + /// Having the address type at the very beginning is so that it can be decoded to detect the + /// type of the address without having to decode the entire data. Moreover, choosing the + /// address type as a multiple of 8 means the first character of the bech32 string after the + /// `1` separator will be different for every address type. This makes the type of the address + /// conveniently human-readable. + pub fn to_bech32(&self, network_id: NetworkId) -> String { + match self { + AccountId::V0(account_id_v0) => account_id_v0.to_bech32(network_id), + } + } + + /// Decodes a [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) string into an [`AccountId`]. + /// + /// See [`AccountId::to_bech32`] for details on the format. The procedure for decoding the + /// bech32 data into the ID consists of the inverse operations of encoding. + pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AccountIdError> { + AccountIdV0::from_bech32(bech32_string) + .map(|(network_id, account_id)| (network_id, AccountId::V0(account_id))) + } + /// Returns the [`AccountIdPrefix`] of this ID. /// /// The prefix of an account ID is guaranteed to be unique. @@ -436,8 +480,15 @@ impl Deserializable for AccountId { #[cfg(test)] mod tests { + use alloc::boxed::Box; + + use assert_matches::assert_matches; + use bech32::{Bech32, Bech32m, NoChecksum}; use super::*; + use crate::account::account_id::v0::{extract_storage_mode, extract_type, extract_version}; + use crate::address::{AddressType, CustomNetworkId}; + use crate::errors::Bech32Error; use crate::testing::account_id::{ ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, @@ -445,6 +496,7 @@ mod tests { ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, + AccountIdBuilder, }; #[test] @@ -468,4 +520,129 @@ mod tests { ); } } + + #[test] + fn bech32_encode_decode_roundtrip() -> anyhow::Result<()> { + // We use this to check that encoding does not panic even when using the longest possible + // HRP. + let longest_possible_hrp = + "01234567890123456789012345678901234567890123456789012345678901234567890123456789012"; + assert_eq!(longest_possible_hrp.len(), 83); + + let random_id = AccountIdBuilder::new().build_with_rng(&mut rand::rng()); + + for network_id in [ + NetworkId::Mainnet, + NetworkId::Custom(Box::new("custom".parse::()?)), + NetworkId::Custom(Box::new(longest_possible_hrp.parse::()?)), + ] { + for account_id in [ + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, + ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, + ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, + ACCOUNT_ID_PRIVATE_SENDER, + random_id.into(), + ] + .into_iter() + { + let account_id = AccountId::try_from(account_id).unwrap(); + + let bech32_string = account_id.to_bech32(network_id.clone()); + let (decoded_network_id, decoded_account_id) = + AccountId::from_bech32(&bech32_string).unwrap(); + + assert_eq!(network_id, decoded_network_id, "network id failed for {account_id}",); + assert_eq!(account_id, decoded_account_id, "account id failed for {account_id}"); + + let (_, data) = bech32::decode(&bech32_string).unwrap(); + + // Raw bech32 data should contain the address type as the first byte. + assert_eq!(data[0], AddressType::AccountId as u8); + + // Raw bech32 data should contain the metadata byte at index 8. + assert_eq!(extract_version(data[8] as u64).unwrap(), account_id.version()); + assert_eq!(extract_type(data[8] as u64), account_id.account_type()); + assert_eq!( + extract_storage_mode(data[8] as u64).unwrap(), + account_id.storage_mode() + ); + } + } + + Ok(()) + } + + #[test] + fn bech32_invalid_checksum() { + let network_id = NetworkId::Mainnet; + let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); + + let bech32_string = account_id.to_bech32(network_id); + let mut invalid_bech32_1 = bech32_string.clone(); + invalid_bech32_1.remove(0); + let mut invalid_bech32_2 = bech32_string.clone(); + invalid_bech32_2.remove(7); + + let error = AccountId::from_bech32(&invalid_bech32_1).unwrap_err(); + assert_matches!(error, AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(_))); + + let error = AccountId::from_bech32(&invalid_bech32_2).unwrap_err(); + assert_matches!(error, AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(_))); + } + + #[test] + fn bech32_invalid_address_type() { + let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); + let mut id_bytes = account_id.to_bytes(); + + // Set invalid address type. + id_bytes.insert(0, 16); + + let invalid_bech32 = + bech32::encode::(NetworkId::Mainnet.into_hrp(), &id_bytes).unwrap(); + + let error = AccountId::from_bech32(&invalid_bech32).unwrap_err(); + assert_matches!( + error, + AccountIdError::Bech32DecodeError(Bech32Error::UnknownAddressType(16)) + ); + } + + #[test] + fn bech32_invalid_other_checksum() { + let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); + let mut id_bytes = account_id.to_bytes(); + id_bytes.insert(0, AddressType::AccountId as u8); + + // Use Bech32 instead of Bech32m which is disallowed. + let invalid_bech32_regular = + bech32::encode::(NetworkId::Mainnet.into_hrp(), &id_bytes).unwrap(); + let error = AccountId::from_bech32(&invalid_bech32_regular).unwrap_err(); + assert_matches!(error, AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(_))); + + // Use no checksum instead of Bech32m which is disallowed. + let invalid_bech32_no_checksum = + bech32::encode::(NetworkId::Mainnet.into_hrp(), &id_bytes).unwrap(); + let error = AccountId::from_bech32(&invalid_bech32_no_checksum).unwrap_err(); + assert_matches!(error, AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(_))); + } + + #[test] + fn bech32_invalid_length() { + let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); + let mut id_bytes = account_id.to_bytes(); + id_bytes.insert(0, AddressType::AccountId as u8); + // Add one byte to make the length invalid. + id_bytes.push(5); + + let invalid_bech32 = + bech32::encode::(NetworkId::Mainnet.into_hrp(), &id_bytes).unwrap(); + + let error = AccountId::from_bech32(&invalid_bech32).unwrap_err(); + assert_matches!( + error, + AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength { .. }) + ); + } } diff --git a/crates/miden-objects/src/account/account_id/v0/mod.rs b/crates/miden-objects/src/account/account_id/v0/mod.rs index f08efff057..c19ac7c6c5 100644 --- a/crates/miden-objects/src/account/account_id/v0/mod.rs +++ b/crates/miden-objects/src/account/account_id/v0/mod.rs @@ -4,9 +4,12 @@ use alloc::vec::Vec; use core::fmt; use core::hash::Hash; +use bech32::Bech32m; +use bech32::primitives::decode::CheckedHrpstring; use miden_crypto::utils::hex_to_bytes; pub use prefix::AccountIdPrefixV0; +use crate::account::account_id::NetworkId; use crate::account::account_id::account_type::{ FUNGIBLE_FAUCET, NON_FUNGIBLE_FAUCET, @@ -15,7 +18,8 @@ use crate::account::account_id::account_type::{ }; use crate::account::account_id::storage_mode::{NETWORK, PRIVATE, PUBLIC}; use crate::account::{AccountIdVersion, AccountStorageMode, AccountType}; -use crate::errors::AccountIdError; +use crate::address::AddressType; +use crate::errors::{AccountIdError, Bech32Error}; use crate::utils::{ByteReader, Deserializable, DeserializationError, Serializable}; use crate::{AccountError, EMPTY_WORD, Felt, Hasher, Word}; @@ -212,6 +216,69 @@ impl AccountIdV0 { hex_string } + /// See [`AccountId::to_bech32`](super::AccountId::to_bech32) for details. + pub fn to_bech32(&self, network_id: NetworkId) -> String { + let id_bytes: [u8; Self::SERIALIZED_SIZE] = (*self).into(); + + let mut data = [0; Self::SERIALIZED_SIZE + 1]; + data[0] = AddressType::AccountId as u8; + data[1..16].copy_from_slice(&id_bytes); + + // SAFETY: Encoding only panics if the total length of the hrp, data (in GF(32)), separator + // and checksum exceeds Bech32m::CODE_LENGTH, which is 1023. Since the data is 26 bytes in + // that field and the hrp is at most 83 in size we are way below the limit. + // + // The only allowed checksum algorithm is [`Bech32m`](bech32::Bech32m) due to being the + // best available checksum algorithm with no known weaknesses (unlike + // [`Bech32`](bech32::Bech32)). No checksum is also not allowed since the intended + // use of bech32 is to have error detection capabilities. + bech32::encode::(network_id.into_hrp(), &data) + .expect("code length of bech32 should not be exceeded") + } + + /// See [`AccountId::from_bech32`](super::AccountId::from_bech32) for details. + pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AccountIdError> { + // We use CheckedHrpString instead of bech32::decode with an explicit checksum algorithm so + // we don't allow the `Bech32` or `NoChecksum` algorithms. + let checked_string = CheckedHrpstring::new::(bech32_string).map_err(|source| { + // The CheckedHrpStringError does not implement core::error::Error, only + // std::error::Error, so for now we convert it to a String. Even if it will + // implement the trait in the future, we should include it as an opaque + // error since the crate does not have a stable release yet. + AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into())) + })?; + + let hrp = checked_string.hrp(); + let network_id = NetworkId::from_hrp(hrp); + + let mut byte_iter = checked_string.byte_iter(); + // The length must be the serialized size of the account ID plus the address byte. + if byte_iter.len() != Self::SERIALIZED_SIZE + 1 { + return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength { + expected: Self::SERIALIZED_SIZE + 1, + actual: byte_iter.len(), + })); + } + + let address_byte = byte_iter.next().expect("there should be at least one byte"); + if address_byte != AddressType::AccountId as u8 { + return Err(AccountIdError::Bech32DecodeError(Bech32Error::UnknownAddressType( + address_byte, + ))); + } + + // Every byte is guaranteed to be overwritten since we've checked the length of the + // iterator. + let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE]; + for (i, byte) in byte_iter.enumerate() { + id_bytes[i] = byte; + } + + let account_id = Self::try_from(id_bytes)?; + + Ok((network_id, account_id)) + } + /// Returns the [`AccountIdPrefixV0`] of this account ID. /// /// See also [`AccountId::prefix`](super::AccountId::prefix) for details. diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index 27f8f10e87..7bb5909813 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -217,6 +217,8 @@ pub enum AccountIdError { AccountIdSuffixMostSignificantBitMustBeZero, #[error("least significant byte of account ID suffix must be zero")] AccountIdSuffixLeastSignificantByteMustBeZero, + #[error("failed to decode bech32 string into account ID")] + Bech32DecodeError(#[source] Bech32Error), } // SLOT NAME ERROR diff --git a/docs/src/account/id.md b/docs/src/account/id.md index 5dcd13b0fb..bc620c1c6b 100644 --- a/docs/src/account/id.md +++ b/docs/src/account/id.md @@ -47,12 +47,22 @@ Users can choose whether their accounts are stored publicly or privately. The pr An `Account` ID can be encoded in different formats: -1. [**Address**](./address#types--interfaces): - - - Used when sending or receiving notes or assets. - - Used to communicate the [account interface](./code#interface) between sender and receiver. +1. [**Bech32**](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) (user-facing): + - Example: `mm1qrt4skk6t26a9vquwlad3rq2usul8fy2` + - **Benefits**: + - Built-in error detection via checksum algorithm + - Human-readable prefix indicates network type + - Less prone to transcription errors + - **Structure**: + - [Human-readable prefix](https://github.com/satoshilabs/slips/blob/master/slip-0173.md) that + determines the network: + - `mm` (indicates **M**iden **M**ainnet) + - `mtst` (indicates Miden Testnet) + - `mdev` (indicates Miden Devnet) + - Separator: `1` + - Data part with integrated checksum 2. **Hexadecimal**: - - Example: `0xd345c9766a2d5e606477a5676b049a` - - Frequently used encoding for blockchain addresses - - Used to identify accounts in command-line interfaces or explorers. + - Example: `0xd7585ada5ab5d2b01c77fad88c0ae4` + - Frequently used encoding for blockchain addresses + - Used to identify accounts in command-line interfaces or explorers. From f14d0eb719c6e124e4101eb8a75c279edc00c29c Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Mon, 27 Oct 2025 19:12:41 +0300 Subject: [PATCH 109/133] Update type signatures in `account_components` (#1971) * refactor: update type signatures in account_components * chore: update changelog * refactor: use lower-case parameter names, update event regex * refactor: use export instead of pub proc * chore: remove duplications from changelog * refactor: use BeWord instead of Word * refactor: replace export with pub proc * chore: remove changelog duplications after merge --- CHANGELOG.md | 2 +- .../basic_fungible_faucet.masm | 4 +-- .../asm/account_components/basic_wallet.masm | 4 +-- .../multisig_rpo_falcon_512.masm | 34 +++++++++++-------- .../asm/account_components/no_auth.masm | 6 ++-- .../account_components/rpo_falcon_512.masm | 10 +++--- .../rpo_falcon_512_acl.masm | 16 +++++---- .../asm/kernels/transaction/lib/epilogue.masm | 2 +- crates/miden-lib/build.rs | 12 ++++--- 9 files changed, 50 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a0069cc85..6d19eb326c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,7 @@ - [BREAKING] Remove account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). - Simplify `MockChain` internals and rework its documentation ([#1942]https://github.com/0xMiden/miden-base/pull/1942). - [BREAKING] Change the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). -- [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). +- Update the type signature syntax in the `account_components` module ([#1971](https://github.com/0xMiden/miden-base/pull/1971)). - [BREAKING] Return `ExecutionOutput` from `TransactionContext::execute_code` ([#1955](https://github.com/0xMiden/miden-base/pull/1955)). - Dynamically lookup all masm `EventId`s from source ([#1954](https://github.com/0xMiden/miden-base/pull/1954)). - [BREAKING] Rename `get_item_init` and `get_map_item_init` to `get_initial_item` and `get_initial_map_item` respectively ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). diff --git a/crates/miden-lib/asm/account_components/basic_fungible_faucet.masm b/crates/miden-lib/asm/account_components/basic_fungible_faucet.masm index 238c6ea2ab..f436d1b541 100644 --- a/crates/miden-lib/asm/account_components/basic_fungible_faucet.masm +++ b/crates/miden-lib/asm/account_components/basic_fungible_faucet.masm @@ -2,5 +2,5 @@ # # See the `BasicFungibleFaucet` Rust type's documentation for more details. -export.::miden::contracts::faucets::basic_fungible::distribute -export.::miden::contracts::faucets::basic_fungible::burn +pub proc ::miden::contracts::faucets::basic_fungible::distribute +pub proc ::miden::contracts::faucets::basic_fungible::burn diff --git a/crates/miden-lib/asm/account_components/basic_wallet.masm b/crates/miden-lib/asm/account_components/basic_wallet.masm index 091a03c062..36fbc7d7b9 100644 --- a/crates/miden-lib/asm/account_components/basic_wallet.masm +++ b/crates/miden-lib/asm/account_components/basic_wallet.masm @@ -2,5 +2,5 @@ # # See the `BasicWallet` Rust type's documentation for more details. -export.::miden::contracts::wallets::basic::receive_asset -export.::miden::contracts::wallets::basic::move_asset_to_note +pub proc ::miden::contracts::wallets::basic::receive_asset +pub proc ::miden::contracts::wallets::basic::move_asset_to_note diff --git a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm index 6808a8e145..0311bd473e 100644 --- a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm @@ -2,8 +2,10 @@ # # See the `AuthRpoFalcon512Multisig` Rust type's documentation for more details. -use.miden::account -use.miden::auth +use miden::account +use miden::auth + +type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt } # CONSTANTS # ================================================================================================= @@ -11,7 +13,7 @@ use.miden::auth # Auth Request Constants # The event emitted when a signature is not found for a required signer. -const.AUTH_UNAUTHORIZED_EVENT=event("miden::auth::unauthorized") +const AUTH_UNAUTHORIZED_EVENT = event("miden::auth::unauthorized") # Storage Layout Constants # @@ -26,29 +28,31 @@ const.AUTH_UNAUTHORIZED_EVENT=event("miden::auth::unauthorized") # number of approvers are stored as: # [default_threshold, num_approvers, 0, 0]. # The threshold is guaranteed to be less than or equal to num_approvers. -const.THRESHOLD_CONFIG_SLOT=0 +const THRESHOLD_CONFIG_SLOT = 0 # The slot in this component's storage layout where the public keys map is stored. # Map entries: [key_index, 0, 0, 0] => APPROVER_PUBLIC_KEY -const.PUBLIC_KEYS_MAP_SLOT=1 +const PUBLIC_KEYS_MAP_SLOT = 1 # The slot in this component's storage layout where executed transactions are stored. # Map entries: transaction_message => [is_executed, 0, 0, 0] -const.EXECUTED_TXS_SLOT=2 +const EXECUTED_TXS_SLOT = 2 # The slot in this component's storage layout where procedure thresholds are stored. # Map entries: PROC_ROOT => [proc_threshold, 0, 0, 0] const.PROC_THRESHOLD_ROOTS_SLOT=3 # Executed Transaction Flag Constant -const.IS_EXECUTED_FLAG=[1, 0, 0, 0] +const IS_EXECUTED_FLAG = [1, 0, 0, 0] # ERRORS -const.ERR_TX_ALREADY_EXECUTED="failed to approve multisig transaction as it was already executed" +# ================================================================================================= + +const ERR_TX_ALREADY_EXECUTED = "failed to approve multisig transaction as it was already executed" -const.ERR_MALFORMED_MULTISIG_CONFIG="number of approvers must be equal to or greater than threshold" +const ERR_MALFORMED_MULTISIG_CONFIG = "number of approvers must be equal to or greater than threshold" -const.ERR_ZERO_IN_MULTISIG_CONFIG="number of approvers or threshold must not be zero" +const ERR_ZERO_IN_MULTISIG_CONFIG = "number of approvers or threshold must not be zero" #! Check if transaction has already been executed and add it to executed transactions for replay protection. #! @@ -57,7 +61,7 @@ const.ERR_ZERO_IN_MULTISIG_CONFIG="number of approvers or threshold must not be #! #! Panics if: #! - the same transaction has already been executed -proc.assert_new_tx +proc assert_new_tx(msg: BeWord) push.IS_EXECUTED_FLAG # => [[0, 0, 0, is_executed], MSG] @@ -90,7 +94,7 @@ end #! Where: #! - init_num_of_approvers is the original number of approvers before the update #! - new_num_of_approvers is the new number of approvers after the update -proc.cleanup_pubkey_mapping +proc cleanup_pubkey_mapping(init_num_of_approvers: u32, new_num_of_approvers: u32) dup.1 dup.1 u32assert2 u32lt # => [should_loop, i = init_num_of_approvers, new_num_of_approvers] @@ -146,7 +150,7 @@ end #! Locals: #! 0: new_num_of_approvers #! 1: init_num_of_approvers -export.update_signers_and_threshold.2 +pub proc update_signers_and_threshold.2(multisig_config_hash: BeWord) adv.push_mapval # => [MULTISIG_CONFIG_HASH, pad(12)] @@ -233,7 +237,7 @@ end # #! Inputs: [default_threshold] #! Outputs: [transaction_threshold] -proc.compute_transaction_threshold.1 +proc compute_transaction_threshold.1(default_threshold: u32) -> u32 # 1. initialize transaction_threshold = 0 # 2. iterate through all account procedures # a. check if the procedure was called during the transaction @@ -348,7 +352,7 @@ end #! - the same transaction has already been executed (replay protection). #! #! Invocation: call -export.auth_tx_rpo_falcon512_multisig.1 +pub proc auth_tx_rpo_falcon512_multisig.1(salt: BeWord) exec.account::incr_nonce drop # => [SALT] diff --git a/crates/miden-lib/asm/account_components/no_auth.masm b/crates/miden-lib/asm/account_components/no_auth.masm index b97b662eba..1e9deba999 100644 --- a/crates/miden-lib/asm/account_components/no_auth.masm +++ b/crates/miden-lib/asm/account_components/no_auth.masm @@ -1,5 +1,5 @@ -use.miden::account -use.std::word +use miden::account +use std::word #! Increment the nonce only if the account commitment has changed #! @@ -11,7 +11,7 @@ use.std::word #! #! Inputs: [pad(16)] #! Outputs: [pad(16)] -export.auth_no_auth +pub proc auth_no_auth # check if the account state has changed by comparing initial and final commitments exec.account::get_initial_commitment diff --git a/crates/miden-lib/asm/account_components/rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/rpo_falcon_512.masm index efa5e0df44..58fcc84f89 100644 --- a/crates/miden-lib/asm/account_components/rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/rpo_falcon_512.masm @@ -2,14 +2,16 @@ # # See the `AuthRpoFalcon512` Rust type's documentation for more details. -use.miden::auth::rpo_falcon512 -use.miden::account +use miden::auth::rpo_falcon512 +use miden::account + +type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt } # CONSTANTS # ================================================================================================= # The slot in this component's storage layout where the public key is stored. -const.PUBLIC_KEY_SLOT=0 +const PUBLIC_KEY_SLOT = 0 #! Authenticate a transaction using the Falcon signature scheme. #! @@ -26,7 +28,7 @@ const.PUBLIC_KEY_SLOT=0 #! Outputs: [pad(16)] #! #! Invocation: call -export.auth_tx_rpo_falcon512 +pub proc auth_tx_rpo_falcon512(auth_args: BeWord) dropw # => [pad(16)] diff --git a/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm b/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm index c2c3b8d320..c0b17b49b6 100644 --- a/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm +++ b/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm @@ -2,21 +2,23 @@ # # See the `AuthRpoFalcon512Acl` Rust type's documentation for more details. -use.miden::account -use.miden::tx -use.std::word +use miden::account +use miden::tx +use std::word + +type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt } # CONSTANTS # ================================================================================================ # The slot in this component's storage layout where the public key is stored. -const.PUBLIC_KEY_SLOT=0 +const PUBLIC_KEY_SLOT = 0 # The slot where the authentication configuration is stored. -const.AUTH_CONFIG_SLOT=1 +const AUTH_CONFIG_SLOT = 1 # The slot where the map of auth trigger procedure roots is stored. -const.AUTH_TRIGGER_PROCS_MAP_SLOT=2 +const AUTH_TRIGGER_PROCS_MAP_SLOT = 2 #! Authenticate a transaction using the Falcon signature scheme based on procedure calls and note usage. #! @@ -32,7 +34,7 @@ const.AUTH_TRIGGER_PROCS_MAP_SLOT=2 #! Outputs: [pad(16)] #! #! Invocation: call -export.auth_tx_rpo_falcon512_acl.2 +pub proc auth_tx_rpo_falcon512_acl.2(auth_args: BeWord) dropw # => [pad(16)] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm index fa57015450..ab23d3b9cf 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -220,8 +220,8 @@ proc.execute_auth_procedure # if auth procedure was called already, it must have been called by a user, which is disallowed exec.account::was_procedure_called - # => [was_auth_called, auth_procedure_ptr, AUTH_ARGS, pad(12)] assertz.err=ERR_AUTH_PROCEDURE_CALLED_FROM_WRONG_CONTEXT + # => [auth_procedure_ptr, AUTH_ARGS, pad(12)] # execute the auth procedure dyncall diff --git a/crates/miden-lib/build.rs b/crates/miden-lib/build.rs index eb78ea4e4b..b87b2f5b9b 100644 --- a/crates/miden-lib/build.rs +++ b/crates/miden-lib/build.rs @@ -589,7 +589,7 @@ fn extract_masm_errors( errors: &mut BTreeMap, file_contents: &str, ) -> Result<()> { - let regex = Regex::new(r#"const\.ERR_(?.*)="(?.*)""#).unwrap(); + let regex = Regex::new(r#"const(\.|\ )ERR_(?.*)\ ?=\ ?"(?.*)""#).unwrap(); for capture in regex.captures_iter(file_contents) { let error_name = capture @@ -812,17 +812,19 @@ fn extract_all_event_definitions(asm_source_dir: &Path) -> Result, file_contents: &str, file_path: &Path, ) -> Result<()> { - let regex = Regex::new(r#"const\.(\w+)=event\("([^"]+)"\)"#).unwrap(); + let regex = Regex::new(r#"const(\.|\ )(\w+)\ ?=\ ?event\("([^"]+)"\)"#).unwrap(); for capture in regex.captures_iter(file_contents) { - let const_name = capture.get(1).expect("const name should be captured"); - let event_path = capture.get(2).expect("event path should be captured"); + let const_name = capture.get(2).expect("const name should be captured"); + let event_path = capture.get(3).expect("event path should be captured"); let event_path = event_path.as_str(); let const_name = const_name.as_str(); From aada660ee0ac959a5a37b4295e6538d350faa9e8 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Tue, 28 Oct 2025 22:59:05 +0300 Subject: [PATCH 110/133] Implement `has_procedure` for `miden::account` (#2017) * refactor: preparations before implementation * feat: impl has_procedure, add test, update changelog * chore: update changelog * docs: update protocol library docs, update doc comments * refactor: update inline comment --- CHANGELOG.md | 1 + .../asm/kernels/transaction/api.masm | 146 ++++++++++-------- .../asm/kernels/transaction/lib/account.masm | 68 ++++++++ crates/miden-lib/asm/miden/account.masm | 30 ++++ .../asm/miden/kernel_proc_offsets.masm | 65 ++++---- .../src/transaction/kernel_procedures.rs | 4 +- .../src/kernel_tests/tx/test_account.rs | 54 +++++++ docs/src/protocol_library.md | 1 + 8 files changed, 279 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d19eb326c..fcbce0cdf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). - Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). - Added `Display` trait for `AddressInterface` ([#2016](https://github.com/0xMiden/miden-base/pull/2016)). +- Added `has_procedure` procedure to the `miden::account` module ([#2017](https://github.com/0xMiden/miden-base/pull/2017)). - Re-add bech32 encoding for `AccountId` ([#2018](https://github.com/0xMiden/miden-base/pull/2018)). - [BREAKING] Change `AccountTree` to be generic over `Smt` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). - [BREAKING] Change `AccountTree` to be generic over `trait AccountTreeBackend` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 8295917fa3..4960df09bc 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -682,6 +682,89 @@ export.account_has_non_fungible_asset # => [has_asset, pad(15)] end +#! Returns 1 if a procedure was called during transaction execution, and 0 otherwise. +#! +#! Inputs: [PROC_ROOT, pad(12)] +#! Outputs: [was_called, pad(15)] +#! +#! Where: +#! - PROC_ROOT is the hash of the procedure to check. +#! - was_called is 1 if the procedure was called at least once during tx execution, 0 otherwise. +#! +#! Panics if: +#! - the procedure root is not part of the account code. +#! +#! Invocation: dynexec +export.account_was_procedure_called + # check if the procedure was called + exec.account::was_procedure_called + # => [was_called, pad(15)] +end + +#! Returns the number of procedures in the current account. +#! +#! Inputs: [pad(16)] +#! Outputs: [num_procedures, pad(15)] +#! +#! Where: +#! - num_procedures is the number of procedures in the current account. +#! +#! Invocation: dynexec +export.account_get_num_procedures + # get the number of procedures + exec.memory::get_num_account_procedures + # => [num_procedures, pad(16)] + + # truncate the stack + swap drop + # => [num_procedures, pad(15)] +end + +#! Returns the procedure root for the procedure at the specified index. +#! +#! Inputs: [index, pad(15)] +#! Outputs: [PROC_ROOT, pad(12)] +#! +#! Where: +#! - index is the index of the procedure. +#! - PROC_ROOT is the hash of the procedure. +#! +#! Panics if: +#! - the procedure index is out of bounds. +#! +#! Invocation: dynexec +export.account_get_procedure_root + # get the procedure information + exec.account::get_procedure_info + # => [PROC_ROOT, storage_offset, storage_size, pad(15)] + + swapw dropw + # => [PROC_ROOT, pad(13)] + + movup.4 drop + # => [PROC_ROOT, pad(12)] +end + +#! Returns the binary flag indicating whether the procedure with the provided root is available on +#! the active account. +#! +#! Returns 1 if the procedure is available on the active account and 0 otherwise. +#! +#! Inputs: [PROC_ROOT, pad(12)] +#! Outputs: [is_procedure_available, pad(15)] +#! +#! Where: +#! - PROC_ROOT is the hash of the procedure of interest. +#! - is_procedure_available is the binary flag indicating whether the procedure with PROC_ROOT is +#! available on the active account. +#! +#! Invocation: dynexec +export.account_has_procedure + # check if the procedure was called + exec.account::has_procedure + # => [is_procedure_available, pad(15)] +end + ### FAUCET ###################################### #! Mint an asset from the faucet the transaction is being executed against. @@ -1444,69 +1527,6 @@ export.tx_get_expiration_delta # => [block_height_delta, pad(15)] end -#! Returns 1 if a procedure was called during transaction execution, and 0 otherwise. -#! -#! Inputs: [PROC_ROOT, pad(12)] -#! Outputs: [was_called, pad(15)] -#! -#! Where: -#! - PROC_ROOT is the hash of the procedure to check. -#! - was_called is 1 if the procedure was called at least once during tx execution, 0 otherwise. -#! -#! Panics if: -#! - the procedure root is not part of the account code. -#! -#! Invocation: dynexec -export.account_was_procedure_called - # check if the procedure was called - exec.account::was_procedure_called - # => [was_called, pad(15)] -end - -#! Returns the number of procedures in the current account. -#! -#! Inputs: [pad(16)] -#! Outputs: [num_procedures, pad(15)] -#! -#! Where: -#! - num_procedures is the number of procedures in the current account. -#! -#! Invocation: dynexec -export.account_get_num_procedures - # get the number of procedures - exec.memory::get_num_account_procedures - # => [num_procedures, pad(16)] - - # truncate the stack - swap drop - # => [num_procedures, pad(15)] -end - -#! Returns the procedure root for the procedure at the specified index. -#! -#! Inputs: [index, pad(15)] -#! Outputs: [PROC_ROOT, pad(12)] -#! -#! Where: -#! - index is the index of the procedure. -#! - PROC_ROOT is the hash of the procedure. -#! -#! Panics if: -#! - the procedure index is out of bounds. -#! -#! Invocation: dynexec -export.account_get_procedure_root - # get the procedure information - exec.account::get_procedure_info - # => [PROC_ROOT, storage_offset, storage_size, pad(15)] - - swapw dropw - # => [PROC_ROOT, pad(13)] - - movup.4 drop - # => [PROC_ROOT, pad(12)] -end - #! Executes a kernel procedure specified by its offset. #! #! Inputs: [procedure_offset, , ] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 91b4f34d81..8e10d51967 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -7,6 +7,7 @@ use.$kernel::memory use.std::collections::smt use.std::crypto::hashes::rpo use.std::mem +use.std::word # ERRORS # ================================================================================================= @@ -1630,3 +1631,70 @@ export.set_was_procedure_called # save 1 to the was_called address add push.1 swap mem_store end + +#! Returns the binary flag indicating whether the procedure with the provided root is available on +#! the active account. +#! +#! Returns 1 if the procedure is available on the active account and 0 otherwise. +#! +#! Inputs: [PROC_ROOT] +#! Outputs: [is_procedure_available] +#! +#! Where: +#! - PROC_ROOT is the hash of the procedure of interest. +#! - is_procedure_available is the binary flag indicating whether the procedure with PROC_ROOT is +#! available on the active account. +export.has_procedure + # get the end pointer of the procedure section (where we should stop iterating) + exec.memory::get_num_account_procedures + exec.memory::get_account_procedure_ptr + # => [end_ptr, PROC_ROOT] + + # get the start pointer of the procedure section (where we will start iterating) + push.0 exec.memory::get_account_procedure_ptr + # => [start_ptr, end_ptr, PROC_ROOT] + + # prepare the stack for the loop + movdn.5 movdn.5 push.0 movdn.6 + # => [PROC_ROOT, start_ptr, end_ptr, is_procedure_available] + + # push the flag to enter the loop: an account should have at least 2 procedures + push.1 + # => [should_loop, PROC_ROOT, start_ptr, end_ptr, is_procedure_available] + + while.true + # => [PROC_ROOT, curr_proc_ptr, end_ptr, is_procedure_available] + + # load the root of the current procedure + padw dup.8 mem_loadw_be + # => [CURR_PROC_ROOT, PROC_ROOT, curr_proc_ptr, end_ptr, is_procedure_available] + + # check whether the current root is equal to the provided root + dupw.1 exec.word::eq + # => [is_equal, PROC_ROOT, curr_proc_ptr, end_ptr, is_procedure_available] + + # update the is_procedure_available flag + movup.7 or movdn.6 + # => [PROC_ROOT, curr_proc_ptr, end_ptr, is_procedure_available'] + + # move the current procedure pointer + movup.4 add.8 + # => [curr_proc_ptr + 8, PROC_ROOT, end_ptr, is_procedure_available'] + + # compute should_loop flag: we should continue iterating if + # !(is_procedure_available' || curr_proc_ptr + 8 == end_ptr), i.e. we didn't find the + # procedure and we didn't reach the end of the procedures memory block + dup dup.6 eq dup.7 or eq.0 + # => [should_loop, curr_proc_ptr + 8, PROC_ROOT, end_ptr, is_procedure_available'] + + # rearrange the stack + swap movdn.5 + # => [should_loop, PROC_ROOT, curr_proc_ptr + 8, end_ptr, is_procedure_available'] + end + + # => [PROC_ROOT, curr_proc_ptr', end_ptr, is_procedure_available'] + + # clean the stack + dropw drop drop + # => [is_procedure_available'] +end diff --git a/crates/miden-lib/asm/miden/account.masm b/crates/miden-lib/asm/miden/account.masm index 39ffe3dc7a..6a573cbaf0 100644 --- a/crates/miden-lib/asm/miden/account.masm +++ b/crates/miden-lib/asm/miden/account.masm @@ -797,3 +797,33 @@ export.get_procedure_root swapdw dropw dropw swapw dropw # => [PROC_ROOT] end + +#! Returns the binary flag indicating whether the procedure with the provided root is available on +#! the active account. +#! +#! Returns 1 if the procedure is available on the active account and 0 otherwise. +#! +#! Inputs: [PROC_ROOT] +#! Outputs: [is_procedure_available] +#! +#! Where: +#! - PROC_ROOT is the hash of the procedure of interest. +#! - is_procedure_available is the binary flag indicating whether the procedure with PROC_ROOT is +#! available on the active account. +#! +#! Invocation: exec +export.has_procedure + exec.kernel_proc_offsets::account_has_procedure_offset + # => [offset, PROC_ROOT] + + # pad the stack + push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw + # => [offset, PROC_ROOT, pad(11)] + + syscall.exec_kernel_proc + # => [is_procedure_available, pad(15)] + + # clean the stack + swapdw dropw dropw swapw dropw movdn.3 drop drop drop + # => [is_procedure_available] +end diff --git a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm index 023224220c..c791963f31 100644 --- a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm +++ b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm @@ -45,52 +45,53 @@ const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=23 const.ACCOUNT_GET_NUM_PROCEDURES_OFFSET=24 const.ACCOUNT_GET_PROCEDURE_ROOT_OFFSET=25 const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=26 +const.ACCOUNT_HAS_PROCEDURE_OFFSET=27 ### Faucet ###################################### -const.FAUCET_MINT_ASSET_OFFSET=27 -const.FAUCET_BURN_ASSET_OFFSET=28 -const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=29 -const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=30 +const.FAUCET_MINT_ASSET_OFFSET=28 +const.FAUCET_BURN_ASSET_OFFSET=29 +const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=30 +const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=31 ### Note ######################################## # input notes -const.INPUT_NOTE_GET_METADATA_OFFSET=31 -const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=32 -const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=33 -const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=34 -const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=35 -const.INPUT_NOTE_GET_RECIPIENT_OFFSET=36 +const.INPUT_NOTE_GET_METADATA_OFFSET=32 +const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=33 +const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=34 +const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=35 +const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=36 +const.INPUT_NOTE_GET_RECIPIENT_OFFSET=37 # output notes -const.OUTPUT_NOTE_CREATE_OFFSET=37 -const.OUTPUT_NOTE_GET_METADATA_OFFSET=38 -const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=39 -const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=40 -const.OUTPUT_NOTE_ADD_ASSET_OFFSET=41 +const.OUTPUT_NOTE_CREATE_OFFSET=38 +const.OUTPUT_NOTE_GET_METADATA_OFFSET=39 +const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=40 +const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=41 +const.OUTPUT_NOTE_ADD_ASSET_OFFSET=42 ### Tx ########################################## # input notes -const.TX_GET_NUM_INPUT_NOTES_OFFSET=42 -const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=43 +const.TX_GET_NUM_INPUT_NOTES_OFFSET=43 +const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=44 # output notes -const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=44 -const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=45 +const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=45 +const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=46 # block info -const.TX_GET_BLOCK_COMMITMENT_OFFSET=46 -const.TX_GET_BLOCK_NUMBER_OFFSET=47 -const.TX_GET_BLOCK_TIMESTAMP_OFFSET=48 +const.TX_GET_BLOCK_COMMITMENT_OFFSET=47 +const.TX_GET_BLOCK_NUMBER_OFFSET=48 +const.TX_GET_BLOCK_TIMESTAMP_OFFSET=49 # foreign context -const.TX_START_FOREIGN_CONTEXT_OFFSET=49 -const.TX_END_FOREIGN_CONTEXT_OFFSET=50 +const.TX_START_FOREIGN_CONTEXT_OFFSET=50 +const.TX_END_FOREIGN_CONTEXT_OFFSET=51 # expiration data -const.TX_GET_EXPIRATION_DELTA_OFFSET=51 # accessor -const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=52 # mutator +const.TX_GET_EXPIRATION_DELTA_OFFSET=52 # accessor +const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=53 # mutator # ACCESSORS # ------------------------------------------------------------------------------------------------- @@ -397,6 +398,18 @@ export.account_was_procedure_called_offset push.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET end +#! Returns the offset of the `account_has_procedure` kernel procedure. +#! +#! Inputs: [] +#! Outputs: [proc_offset] +#! +#! Where: +#! - proc_offset is the offset of the `account_has_procedure` kernel procedure required to get the +#! address where this procedure is stored. +export.account_has_procedure_offset + push.ACCOUNT_HAS_PROCEDURE_OFFSET +end + #! Returns the offset of the `account_get_num_procedures` kernel procedure. #! #! Inputs: [] diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index 95b09b15f2..a840ba6e7c 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -6,7 +6,7 @@ use miden_objects::{Word, word}; // ================================================================================================ /// Hashes of all dynamically executed kernel procedures. -pub const KERNEL_PROCEDURES: [Word; 53] = [ +pub const KERNEL_PROCEDURES: [Word; 54] = [ // account_get_initial_commitment word!("0x920898348bacd6d98a399301eb308478fd32b32eab019a5a6ef7a6b44abb61f6"), // account_compute_current_commitment @@ -61,6 +61,8 @@ pub const KERNEL_PROCEDURES: [Word; 53] = [ word!("0x4d7b2e6083820088cd1139ed658b631cf391989b16de8af6741d7e17de9245cd"), // account_was_procedure_called word!("0x34f27a609f2f2b4fec454b17182552b0acc52524e507e134257a1f1ed30a57cd"), + // account_has_procedure + word!("0x667d5ce1b7a54c3b8965666ce90e59085c97775b82eba25dddfe218db5fe137d"), // faucet_mint_asset word!("0x83cca30914ec2265bb79bf0eda0c4fc1dfa2a300b47511528a1c85f49967faa9"), // faucet_burn_asset diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index a79f333af2..31a3a7e71f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -1621,6 +1621,60 @@ async fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { Ok(()) } +#[tokio::test] +async fn test_has_procedure() -> miette::Result<()> { + // Create a standard account using the mock component + let mock_component = MockAccountComponent::with_slots(AccountStorage::mock_storage_slots()); + let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(mock_component) + .build_existing() + .unwrap(); + + let tx_script_code = r#" + use.mock::account->mock_account + use.miden::account + + begin + # check that get_item procedure is available on the mock account + procref.mock_account::get_item + # => [GET_ITEM_ROOT] + + exec.account::has_procedure + # => [is_procedure_available] + + # assert that the get_item is exposed + assert.err="get_item procedure should be exposed by the mock account" + + # get some random word and assert that it is not exposed + push.5.3.15.686 + + exec.account::has_procedure + # => [is_procedure_available] + + # assert that the procedure with some random root is not exposed + assertz.err="procedure with some random root should not be exposed by the mock account" + end + "#; + + // Compile the transaction script using the testing assembler with mock account + let tx_script = ScriptBuilder::with_mock_libraries() + .into_diagnostic()? + .compile_tx_script(tx_script_code) + .into_diagnostic()?; + + // Create transaction context and execute + let tx_context = TransactionContextBuilder::new(account).tx_script(tx_script).build().unwrap(); + + tx_context + .execute() + .await + .into_diagnostic() + .wrap_err("Failed to execute transaction")?; + + Ok(()) +} + // ACCOUNT INITIAL STORAGE TESTS // ================================================================================================ diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index eae86cf401..467b66cbea 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -58,6 +58,7 @@ Account procedures can be used to read and write to account storage, add or remo | `was_procedure_called` | Returns 1 if a procedure was called during transaction execution, and 0 otherwise.

**Inputs:** `[PROC_ROOT]`
**Outputs:** `[was_called]` | Any | | `get_num_procedures` | Returns the number of procedures in the current account.

**Inputs:** `[]`
**Outputs:** `[num_procedures]` | Any | | `get_procedure_root` | Returns the procedure root for the procedure at the specified index.

**Inputs:** `[index]`
**Outputs:** `[PROC_ROOT]` | Any | +| `has_procedure` | Returns the binary flag indicating whether the procedure with the provided root is available on the active account.

**Inputs:** `[PROC_ROOT]`
**Outputs:** `[is_procedure_available]` | Any | ## Active Note Procedures (`miden::active_note`) From 231b2a550fa370cdb0e593173ae90b7b0efb2962 Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:52:20 +0300 Subject: [PATCH 111/133] feat: Add network faucet & `MINT` & `BURN` notes. (#1925) * feat: init ntx faucet * chore: update changelog * refactor: address nits & comments * refactor: add comment in burn procedure * feat: BURN & MINT notes * refactor: revert changes and address nits * refactor: cleanup * fix: new mock_chain method fix * refactor: minor comment updates * refactor: use account_id::is_equal * refactor: split faucet/mod.rs into two * fix: fix merge issues & nits * refactor: address nits * refactor: address nits * refactor: use AccountId in ntx faucet contructor * feat: add doc comments & update well_known_note * refactor: address nits * refactor: address pad nits * refactor: address nits * feat: add distribute padding procedure * fix: rustfmt * fix: make tests async * refactor: address nit * fix: fix stack comment * refactor: add padding in distribute * Update crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm Co-authored-by: Marti * refactor: move burn proc to contracts/faucets/mod.masm * refactor: address MINT note nit * refactor: address basic fungible faucet comment * refactor: set default faucet decimals * Update crates/miden-testing/tests/scripts/faucet.rs Co-authored-by: Marti * refactor: address nits * refactor: address padding stack comment nits * refactor: clarify comment * refactor: address comment nits * feat: consume minted output note from ntx faucet * feat: add test & fix lint warnings * refactor: address stack comments * fix: changelog * fix: minor updates * chore: clean up stack comments in MINT note script * chore: address PR comment regarding test code comment being incorrect * chore: remove debug_assert_eq statement from well_known_note logic * chore: rustfmt * Update crates/miden-testing/tests/scripts/faucet.rs Co-authored-by: Marti * Update crates/miden-testing/tests/scripts/faucet.rs Co-authored-by: Marti * Update crates/miden-testing/tests/scripts/faucet.rs Co-authored-by: Marti * feat: improve network faucet mint / burn tests to show asset balance increase / decrease * Update crates/miden-lib/asm/note_scripts/MINT.masm Co-authored-by: Philipp Gackstatter * Update crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm Co-authored-by: Philipp Gackstatter * Update crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm Co-authored-by: Philipp Gackstatter * Update crates/miden-lib/asm/note_scripts/MINT.masm Co-authored-by: Philipp Gackstatter * refactor: improve faucet documentation and make BURN note generic * refactor: remove usage of local memory in faucets::distribute * refactor: fix stack comment in MINT note * refactor: fix padding comments in MINT note * Update crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm Co-authored-by: Philipp Gackstatter * Update crates/miden-testing/tests/scripts/faucet.rs Co-authored-by: Philipp Gackstatter * chore: explicitly consume both authenticated and unauthenticated note in test (#2013) * chore: explicitly try consume both authenticated and unauthenticated note * chore: update test description * feat: re-export Section and SectionId (#2015) * chore: migrate docs from mdbook to docusaurus - Migrated documentation from mdbook to docusaurus - Updated protocol_library.md with detailed formatting and resolved merge conflicts - Added proper markdown links and structure - Incorporated new procedures from next branch - Added build and deployment workflows for docs * chore(Makefile): add serve-docs command and implement checking for NPM dependency * docs(README): add section explaining external documentation * chore(docs): convert .json docusaurus category files to yml format * fix(docs): fix broken links * chore: use specific ref for trigger-deploy-docs.yml workflow * docs: implement custom styling * Update crates/miden-lib/src/account/interface/mod.rs Co-authored-by: Philipp Gackstatter * refactor: truncate stack explicitly in MINT note before call * fix: fix comment to reference correct faucet metadata slot index * refactor: update to use procedure_digest! macro for faucets * Update crates/miden-lib/src/account/faucets/network_fungible.rs Co-authored-by: Philipp Gackstatter * Update crates/miden-lib/src/account/faucets/network_fungible.rs Co-authored-by: Philipp Gackstatter * refactor: use BasicFungibleFaucet inside NetworkFungibleFaucet to reduce duplication * fix: update to use fully qualified path of FungibleAsset::MAX_AMOUNT for ntx faucet doc comment * refactor: store owner_account_id as AccountId in NetworkFungibleFaucet * refactor: add padding to stack comments in /facuets/mod.masm * refactor: revert send_note test to use NoteType::Public * refactor: simplify create_network_fungible_faucet signature --------- Co-authored-by: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com> Co-authored-by: Marti Co-authored-by: Philipp Gackstatter Co-authored-by: Tomas Fabrizio Orsi Co-authored-by: keinberger --- CHANGELOG.md | 1 + .../network_fungible_faucet.masm | 6 + .../contracts/faucets/basic_fungible.masm | 83 +--- .../asm/miden/contracts/faucets/mod.masm | 120 +++++ .../contracts/faucets/network_fungible.masm | 88 ++++ crates/miden-lib/asm/note_scripts/BURN.masm | 28 ++ crates/miden-lib/asm/note_scripts/MINT.masm | 61 +++ .../miden-lib/src/account/components/mod.rs | 19 + .../src/account/faucets/basic_fungible.rs | 415 +++++++++++++++++ crates/miden-lib/src/account/faucets/mod.rs | 424 +----------------- .../src/account/faucets/network_fungible.rs | 276 ++++++++++++ .../src/account/interface/component.rs | 9 + crates/miden-lib/src/account/interface/mod.rs | 14 + .../src/errors/note_script_errors.rs | 9 + crates/miden-lib/src/note/well_known_note.rs | 69 +++ .../src/mock_chain/chain_builder.rs | 74 ++- crates/miden-testing/tests/scripts/faucet.rs | 249 +++++++++- .../miden-testing/tests/scripts/send_note.rs | 2 +- 18 files changed, 1444 insertions(+), 503 deletions(-) create mode 100644 crates/miden-lib/asm/account_components/network_fungible_faucet.masm create mode 100644 crates/miden-lib/asm/miden/contracts/faucets/mod.masm create mode 100644 crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm create mode 100644 crates/miden-lib/asm/note_scripts/BURN.masm create mode 100644 crates/miden-lib/asm/note_scripts/MINT.masm create mode 100644 crates/miden-lib/src/account/faucets/basic_fungible.rs create mode 100644 crates/miden-lib/src/account/faucets/network_fungible.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index fcbce0cdf6..f3fb15cb90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). - [BREAKING] Introduce `VaultKey` newtype wrapper for asset vault keys ([#1978]https://github.com/0xMiden/miden-base/pull/1978). - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). +- Added `network_fungible_faucet` and `MINT` & `BURN` notes ([#1925](https://github.com/0xMiden/miden-base/pull/1925)) - Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). - Added `Display` trait for `AddressInterface` ([#2016](https://github.com/0xMiden/miden-base/pull/2016)). - Added `has_procedure` procedure to the `miden::account` module ([#2017](https://github.com/0xMiden/miden-base/pull/2017)). diff --git a/crates/miden-lib/asm/account_components/network_fungible_faucet.masm b/crates/miden-lib/asm/account_components/network_fungible_faucet.masm new file mode 100644 index 0000000000..b88db104ce --- /dev/null +++ b/crates/miden-lib/asm/account_components/network_fungible_faucet.masm @@ -0,0 +1,6 @@ +# The MASM code of the Network Fungible Faucet Account Component. +# +# See the `NetworkFungibleFaucet` Rust type's documentation for more details. + +export.::miden::contracts::faucets::network_fungible::distribute +export.::miden::contracts::faucets::network_fungible::burn diff --git a/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm b/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm index 69a6363b71..2cebea9008 100644 --- a/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm +++ b/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm @@ -9,28 +9,32 @@ # - token_symbol as three chars encoded in a Felt. use.miden::account +use.miden::active_note +use.miden::contracts::faucets use.miden::faucet use.miden::output_note -# CONSTANTS +# CONSTANTS # ================================================================================================= + const.PRIVATE_NOTE=2 -# ERRORS +# ERRORS # ================================================================================================= - const.ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED="distribute would cause the maximum supply to be exceeded" +const.ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS="burn requires exactly 1 note asset" + # CONSTANTS # ================================================================================================= # The slot in this component's storage layout where the metadata is stored. const.METADATA_SLOT=0 -#! Distributes freshly minted fungible assets to the provided recipient. +#! Distributes freshly minted fungible assets to the provided recipient by creating a note. #! #! Inputs: [amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] -#! Outputs: [note_idx, pad(15)] +#! Outputs: [pad(16)] #! #! Where: #! - amount is the amount to be minted and sent. @@ -40,74 +44,31 @@ const.METADATA_SLOT=0 #! - execution_hint is the execution hint of the note that holds the asset. #! - RECIPIENT is the recipient of the asset, i.e., #! hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment). -#! - note_idx is the index of the output note. -#! This cannot directly be accessed from another context. #! #! Panics if: #! - the transaction is being executed against an account that is not a fungible asset faucet. #! - the total issuance after minting is greater than the maximum allowed supply. #! #! Invocation: call -export.distribute.4 - # get max supply of this faucet. We assume it is stored at pos 3 of slot 1 - push.METADATA_SLOT exec.account::get_item drop drop drop - # => [max_supply, amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] - - # get total issuance of this faucet so far and add amount to be minted - exec.faucet::get_total_issuance - # => [total_issuance, max_supply, amount, tag, aux, note_type, execution_hint, RECIPIENT, - # pad(7)] - - # compute maximum amount that can be minted, max_mint_amount = max_supply - total_issuance - sub - # => [max_supply - total_issuance, amount, tag, aux, note_type, execution_hint, RECIPIENT, - # pad(7)] - - # check that amount =< max_supply - total_issuance, fails if otherwise - dup.1 gte assert.err=ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED - # => [amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] - - # creating the asset - exec.faucet::create_fungible_asset - # => [ASSET, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] - - # mint the asset; this is needed to satisfy asset preservation logic. - exec.faucet::mint - # => [ASSET, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] - - # store and drop the ASSET - loc_storew.0 dropw - # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] - - # create a note - exec.output_note::create - # => [note_idx, pad(15)] - - # load the ASSET and add it to the note - movdn.4 loc_loadw.0 exec.output_note::add_asset movup.4 - # => [note_idx, ASSET, pad(11)] +export.distribute + exec.faucets::distribute + # => [pad(16)] end -#! Burns fungible assets. +#! Burns the fungible asset from the active note. #! -#! Inputs: [ASSET, pad(12)] -#! Outputs: [pad(16)] +#! This procedure retrieves the asset from the active note and burns it. The note must contain +#! exactly one asset, which must be a fungible asset issued by this faucet. #! -#! Where: -#! - ASSET is the fungible asset to be burned. +#! This is a re-export of basic_fungible::burn. +#! +#! Inputs: [pad(16)] +#! Outputs: [pad(16)] #! #! Panics if: +#! - the procedure is not called from a note context (active_note::get_assets will fail). +#! - the note does not contain exactly one asset. #! - the transaction is executed against an account which is not a fungible asset faucet. #! - the transaction is executed against a faucet which is not the origin of the specified asset. #! - the amount about to be burned is greater than the outstanding supply of the asset. -#! -#! Invocation: call -export.burn - # burning the asset - exec.faucet::burn - # => [ASSET, pad(12)] - - # clear the stack - dropw - # => [pad(16)] -end +export.faucets::burn diff --git a/crates/miden-lib/asm/miden/contracts/faucets/mod.masm b/crates/miden-lib/asm/miden/contracts/faucets/mod.masm new file mode 100644 index 0000000000..d0e4d04694 --- /dev/null +++ b/crates/miden-lib/asm/miden/contracts/faucets/mod.masm @@ -0,0 +1,120 @@ +use.miden::account +use.miden::active_note +use.miden::faucet +use.miden::output_note + +# CONSTANTS +# ================================================================================================= + +const.PRIVATE_NOTE=2 + +# ERRORS +# ================================================================================================= +const.ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED="distribute would cause the maximum supply to be exceeded" + +const.ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS="burn requires exactly 1 note asset" + +# CONSTANTS +# ================================================================================================= + +# The slot in this component's storage layout where the metadata is stored. +const.METADATA_SLOT=0 + +#! Distributes freshly minted fungible assets to the provided recipient by creating a note. +#! +#! Inputs: [amount, tag, aux, note_type, execution_hint, RECIPIENT] +#! Outputs: [] +#! +#! Where: +#! - amount is the amount to be minted and sent. +#! - tag is the tag to be included in the note. +#! - aux is the auxiliary data to be included in the note. +#! - note_type is the type of the note that holds the asset. +#! - execution_hint is the execution hint of the note that holds the asset. +#! - RECIPIENT is the recipient of the asset, i.e., +#! hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment). +#! +#! Panics if: +#! - the transaction is being executed against an account that is not a fungible asset faucet. +#! - the total issuance after minting is greater than the maximum allowed supply. +#! +#! Invocation: exec +export.distribute + # get max supply of this faucet. We assume it is stored at pos 3 of slot 0 + push.METADATA_SLOT exec.account::get_item drop drop drop + # => [max_supply, amount, tag, aux, note_type, execution_hint, RECIPIENT] + + # get total issuance of this faucet so far and add amount to be minted + exec.faucet::get_total_issuance + # => [total_issuance, max_supply, amount, tag, aux, note_type, execution_hint, RECIPIENT] + + # compute maximum amount that can be minted, max_mint_amount = max_supply - total_issuance + sub + # => [max_supply - total_issuance, amount, tag, aux, note_type, execution_hint, RECIPIENT] + + # check that amount =< max_supply - total_issuance, fails if otherwise + dup.1 gte assert.err=ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED + # => [amount, tag, aux, note_type, execution_hint, RECIPIENT] + + # creating the asset + exec.faucet::create_fungible_asset + # => [ASSET, tag, aux, note_type, execution_hint, RECIPIENT] + + # mint the asset; this is needed to satisfy asset preservation logic. + exec.faucet::mint + # => [ASSET, tag, aux, note_type, execution_hint, RECIPIENT] + + # store and drop the ASSET + movdnw.2 + # => [tag, aux, note_type, execution_hint, RECIPIENT, ASSET] + + # create a note + exec.output_note::create + # => [note_idx, ASSET] + + # load the ASSET and add it to the note + movdn.4 exec.output_note::add_asset + # => [ASSET, note_idx] + + dropw drop + # => [] +end + +#! Burns the fungible asset from the active note. +#! +#! This procedure retrieves the asset from the active note and burns it. The note must contain +#! exactly one asset, which must be a fungible asset issued by this faucet. +#! +#! Inputs: [pad(16)] +#! Outputs: [pad(16)] +#! +#! Where: +#! - ASSET is the fungible asset that was burned. +#! +#! Panics if: +#! - the procedure is not called from a note context (active_note::get_assets will fail). +#! - the note does not contain exactly one asset. +#! - the transaction is executed against an account which is not a fungible asset faucet. +#! - the transaction is executed against a faucet which is not the origin of the specified asset. +#! - the amount about to be burned is greater than the outstanding supply of the asset. +#! +#! Invocation: call +export.burn + # Get the assets from the note. This will fail if not called from a note context. + push.0 exec.active_note::get_assets + # => [num_assets, dest_ptr, pad(16)] + + # Verify we have exactly one asset + assert.err=ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS + # => [dest_ptr, pad(16)] + + mem_loadw + # => [ASSET, pad(16)] + + # burning the asset + exec.faucet::burn + # => [ASSET, pad(16)] + + dropw + # => [pad(16)] +end diff --git a/crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm b/crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm new file mode 100644 index 0000000000..c660573e00 --- /dev/null +++ b/crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm @@ -0,0 +1,88 @@ +use.miden::account +use.miden::account_id +use.miden::active_note +use.miden::contracts::faucets +use.miden::contracts::faucets::basic_fungible + +# CONSTANTS +# ================================================================================================ + +# The slot in this component's storage layout where the owner config is stored. +const.OWNER_CONFIG_SLOT=1 + +# ERRORS +const.ERR_ONLY_OWNER_CAN_MINT="note sender is not the owner of the faucet who can mint assets" + +#! Checks if the note sender is the owner of this faucet. +#! +#! Inputs: [] +#! Outputs: [is_owner] +#! +#! Where: +#! - is_owner is 1 if the sender is the owner, 0 otherwise. +proc.is_owner + push.OWNER_CONFIG_SLOT + # => [owner_config_slot] + + exec.account::get_item + # => [owner_prefix, owner_suffix, 0, 0] + + exec.active_note::get_sender + # => [sender_prefix, sender_suffix, owner_prefix, owner_suffix, 0, 0] + + exec.account_id::is_equal + # => [are_equal, 0, 0] + + movdn.2 drop drop + # => [is_owner] +end + +#! Distributes freshly minted fungible assets to the provided recipient. +#! +#! This procedure first checks if the note sender is the owner of the faucet, and then +#! mints the asset and creates an output note with that asset for the recipient. +#! +#! Inputs: [amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] +#! Outputs: [pad(16)] +#! +#! Where: +#! - amount is the amount to be minted and sent. +#! - tag is the tag to be included in the note. +#! - aux is the auxiliary data to be included in the note. +#! - note_type is the type of the note that holds the asset. +#! - execution_hint is the execution hint of the note that holds the asset. +#! - RECIPIENT is the recipient of the asset. +#! +#! Panics if: +#! - the note sender is not the owner of this faucet. +#! - any of the validations in faucets::distribute fail. +#! +#! Invocation: call +export.distribute + exec.is_owner + # => [is_owner, amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] + + assert.err=ERR_ONLY_OWNER_CAN_MINT + # => [amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] + + exec.faucets::distribute + # => [pad(16)] +end + +#! Burns the fungible asset from the active note. +#! +#! This procedure retrieves the asset from the active note and burns it. The note must contain +#! exactly one asset, which must be a fungible asset issued by this faucet. +#! +#! Inputs: [pad(16)] +#! Outputs: [pad(16)] +#! +#! Panics if: +#! - the procedure is not called from a note context (active_note::get_assets will fail). +#! - the note does not contain exactly one asset. +#! - the transaction is executed against an account which is not a fungible asset faucet. +#! - the transaction is executed against a faucet which is not the origin of the specified asset. +#! - the amount about to be burned is greater than the outstanding supply of the asset. +#! +#! Invocation: call +export.faucets::burn diff --git a/crates/miden-lib/asm/note_scripts/BURN.masm b/crates/miden-lib/asm/note_scripts/BURN.masm new file mode 100644 index 0000000000..42b68ed11b --- /dev/null +++ b/crates/miden-lib/asm/note_scripts/BURN.masm @@ -0,0 +1,28 @@ +use.miden::contracts::faucets + +#! BURN script: burns the asset from the note by calling the faucet's burn procedure. +#! This note can be executed against any faucet account that exposes the faucets::burn procedure +#! (e.g., basic fungible faucet or network fungible faucet). +#! +#! The burn procedure in the faucet already handles all necessary validations including: +#! - Checking that the note contains exactly one asset +#! - Verifying the asset is a fungible asset issued by this faucet +#! - Ensuring the amount to burn doesn't exceed the outstanding supply +#! +#! Requires that the account exposes: +#! - burn procedure (from the faucets interface). +#! +#! Inputs: [ARGS, pad(12)] +#! Outputs: [pad(16)] +#! +#! Panics if: +#! - account does not expose burn procedure. +#! - any of the validations in the burn procedure fail. +begin + dropw + # => [pad(16)] + + # Call the faucet's burn procedure which handles all validations + call.faucets::burn + # => [pad(16)] +end diff --git a/crates/miden-lib/asm/note_scripts/MINT.masm b/crates/miden-lib/asm/note_scripts/MINT.masm new file mode 100644 index 0000000000..ed5056a9fb --- /dev/null +++ b/crates/miden-lib/asm/note_scripts/MINT.masm @@ -0,0 +1,61 @@ +use.miden::active_note +use.miden::contracts::faucets::network_fungible->network_faucet + +# CONSTANTS +# ================================================================================================= + +const.MINT_NOTE_INPUTS_NUMBER=9 + +# ERRORS +# ================================================================================================= +const.ERR_MINT_WRONG_NUMBER_OF_INPUTS="MINT script expects exactly 9 note inputs" + +#! Network Faucet MINT script: mints assets by calling the network faucet's distribute function. +#! This note is intended to be executed against a network fungible faucet account. +#! +#! Requires that the account exposes: +#! - miden::contracts::faucets::network_fungible::distribute procedure. +#! +#! Inputs: [ARGS, pad(12)] +#! Outputs: [pad(16)] +#! +#! Note inputs are assumed to be as follows (in order): +#! - RECIPIENT: The recipient account ID (4 elements) +#! - Output note config (4 elements): +#! - execution_hint: Execution hint for the output note +#! - note_type: Type of the output note +#! - aux: Auxiliary data for the output note +#! - tag: Note tag for the output note +#! - amount: The amount to mint +#! +#! Panics if: +#! - account does not expose distribute procedure. +#! - the number of inputs is not exactly 9. +begin + dropw + # => [pad(16)] + + # Load note inputs into memory starting at address 0 + push.0 exec.active_note::get_inputs + # => [num_inputs, inputs_ptr, pad(16)] + + # Verify we have the correct number of inputs + eq.MINT_NOTE_INPUTS_NUMBER assert.err=ERR_MINT_WRONG_NUMBER_OF_INPUTS drop + # => [pad(16)] + + # Load amount + mem_loadw.0 + # => [RECIPIENT, pad(12)] + + swapw mem_loadw.4 + # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] + + mem_load.8 + # => [amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] + + movup.9 drop + # => [amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(7)] + + call.network_faucet::distribute + # => [pad(16)] +end diff --git a/crates/miden-lib/src/account/components/mod.rs b/crates/miden-lib/src/account/components/mod.rs index e433c9ad36..96f5b6c149 100644 --- a/crates/miden-lib/src/account/components/mod.rs +++ b/crates/miden-lib/src/account/components/mod.rs @@ -33,6 +33,15 @@ static BASIC_FUNGIBLE_FAUCET_LIBRARY: LazyLock = LazyLock::new(|| { Library::read_from_bytes(bytes).expect("Shipped Basic Fungible Faucet library is well-formed") }); +// Initialize the Network Fungible Faucet library only once. +static NETWORK_FUNGIBLE_FAUCET_LIBRARY: LazyLock = LazyLock::new(|| { + let bytes = include_bytes!(concat!( + env!("OUT_DIR"), + "/assets/account_components/network_fungible_faucet.masl" + )); + Library::read_from_bytes(bytes).expect("Shipped Network Fungible Faucet library is well-formed") +}); + // Initialize the Rpo Falcon 512 ACL library only once. static RPO_FALCON_512_ACL_LIBRARY: LazyLock = LazyLock::new(|| { let bytes = include_bytes!(concat!( @@ -67,6 +76,11 @@ pub fn basic_fungible_faucet_library() -> Library { BASIC_FUNGIBLE_FAUCET_LIBRARY.clone() } +/// Returns the Network Fungible Faucet Library. +pub fn network_fungible_faucet_library() -> Library { + NETWORK_FUNGIBLE_FAUCET_LIBRARY.clone() +} + /// Returns the Rpo Falcon 512 Library. pub fn rpo_falcon_512_library() -> Library { RPO_FALCON_512_LIBRARY.clone() @@ -94,6 +108,7 @@ pub fn rpo_falcon_512_multisig_library() -> Library { pub enum WellKnownComponent { BasicWallet, BasicFungibleFaucet, + NetworkFungibleFaucet, AuthRpoFalcon512, AuthRpoFalcon512Acl, AuthRpoFalcon512Multisig, @@ -106,6 +121,7 @@ impl WellKnownComponent { let library = match self { Self::BasicWallet => BASIC_WALLET_LIBRARY.as_ref(), Self::BasicFungibleFaucet => BASIC_FUNGIBLE_FAUCET_LIBRARY.as_ref(), + Self::NetworkFungibleFaucet => NETWORK_FUNGIBLE_FAUCET_LIBRARY.as_ref(), Self::AuthRpoFalcon512 => RPO_FALCON_512_LIBRARY.as_ref(), Self::AuthRpoFalcon512Acl => RPO_FALCON_512_ACL_LIBRARY.as_ref(), Self::AuthRpoFalcon512Multisig => RPO_FALCON_512_MULTISIG_LIBRARY.as_ref(), @@ -149,6 +165,8 @@ impl WellKnownComponent { }, Self::BasicFungibleFaucet => component_interface_vec .push(AccountComponentInterface::BasicFungibleFaucet(storage_offset)), + Self::NetworkFungibleFaucet => component_interface_vec + .push(AccountComponentInterface::NetworkFungibleFaucet(storage_offset)), Self::AuthRpoFalcon512 => component_interface_vec .push(AccountComponentInterface::AuthRpoFalcon512(storage_offset)), Self::AuthRpoFalcon512Acl => component_interface_vec @@ -170,6 +188,7 @@ impl WellKnownComponent { ) { Self::BasicWallet.extract_component(procedures_map, component_interface_vec); Self::BasicFungibleFaucet.extract_component(procedures_map, component_interface_vec); + Self::NetworkFungibleFaucet.extract_component(procedures_map, component_interface_vec); Self::AuthRpoFalcon512.extract_component(procedures_map, component_interface_vec); Self::AuthRpoFalcon512Acl.extract_component(procedures_map, component_interface_vec); Self::AuthRpoFalcon512Multisig.extract_component(procedures_map, component_interface_vec); diff --git a/crates/miden-lib/src/account/faucets/basic_fungible.rs b/crates/miden-lib/src/account/faucets/basic_fungible.rs new file mode 100644 index 0000000000..c222925c90 --- /dev/null +++ b/crates/miden-lib/src/account/faucets/basic_fungible.rs @@ -0,0 +1,415 @@ +use miden_objects::account::{ + Account, + AccountBuilder, + AccountComponent, + AccountStorage, + AccountStorageMode, + AccountType, + StorageSlot, +}; +use miden_objects::asset::{FungibleAsset, TokenSymbol}; +use miden_objects::{Felt, FieldElement, Word}; + +use super::FungibleFaucetError; +use crate::account::AuthScheme; +use crate::account::auth::{AuthRpoFalcon512Acl, AuthRpoFalcon512AclConfig}; +use crate::account::components::basic_fungible_faucet_library; +use crate::account::interface::{AccountComponentInterface, AccountInterface}; +use crate::procedure_digest; + +// BASIC FUNGIBLE FAUCET ACCOUNT COMPONENT +// ================================================================================================ + +// Initialize the digest of the `distribute` procedure of the Basic Fungible Faucet only once. +procedure_digest!( + BASIC_FUNGIBLE_FAUCET_DISTRIBUTE, + BasicFungibleFaucet::DISTRIBUTE_PROC_NAME, + basic_fungible_faucet_library +); + +// Initialize the digest of the `burn` procedure of the Basic Fungible Faucet only once. +procedure_digest!( + BASIC_FUNGIBLE_FAUCET_BURN, + BasicFungibleFaucet::BURN_PROC_NAME, + basic_fungible_faucet_library +); + +/// An [`AccountComponent`] implementing a basic fungible faucet. +/// +/// It reexports the procedures from `miden::contracts::faucets::basic_fungible`. When linking +/// against this component, the `miden` library (i.e. [`MidenLib`](crate::MidenLib)) must be +/// available to the assembler which is the case when using +/// [`TransactionKernel::assembler()`][kasm]. The procedures of this component are: +/// - `distribute`, which mints an assets and create a note for the provided recipient. +/// - `burn`, which burns the provided asset. +/// +/// The `distribute` procedure can be called from a transaction script and requires authentication +/// via the authentication component. The `burn` procedure can only be called from a note script +/// and requires the calling note to contain the asset to be burned. +/// This component must be combined with an authentication component. +/// +/// This component supports accounts of type [`AccountType::FungibleFaucet`]. +/// +/// [kasm]: crate::transaction::TransactionKernel::assembler +pub struct BasicFungibleFaucet { + symbol: TokenSymbol, + decimals: u8, + max_supply: Felt, +} + +impl BasicFungibleFaucet { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The maximum number of decimals supported by the component. + pub const MAX_DECIMALS: u8 = 12; + + const DISTRIBUTE_PROC_NAME: &str = "distribute"; + const BURN_PROC_NAME: &str = "burn"; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`BasicFungibleFaucet`] component from the given pieces of metadata. + /// + /// # Errors: + /// Returns an error if: + /// - the decimals parameter exceeds maximum value of [`Self::MAX_DECIMALS`]. + /// - the max supply parameter exceeds maximum possible amount for a fungible asset + /// ([`FungibleAsset::MAX_AMOUNT`]) + pub fn new( + symbol: TokenSymbol, + decimals: u8, + max_supply: Felt, + ) -> Result { + // First check that the metadata is valid. + if decimals > Self::MAX_DECIMALS { + return Err(FungibleFaucetError::TooManyDecimals { + actual: decimals as u64, + max: Self::MAX_DECIMALS, + }); + } else if max_supply.as_int() > FungibleAsset::MAX_AMOUNT { + return Err(FungibleFaucetError::MaxSupplyTooLarge { + actual: max_supply.as_int(), + max: FungibleAsset::MAX_AMOUNT, + }); + } + + Ok(Self { symbol, decimals, max_supply }) + } + + /// Attempts to create a new [`BasicFungibleFaucet`] component from the associated account + /// interface and storage. + /// + /// # Errors: + /// Returns an error if: + /// - the provided [`AccountInterface`] does not contain a + /// [`AccountComponentInterface::BasicFungibleFaucet`] component. + /// - the decimals parameter exceeds maximum value of [`Self::MAX_DECIMALS`]. + /// - the max supply value exceeds maximum possible amount for a fungible asset of + /// [`FungibleAsset::MAX_AMOUNT`]. + /// - the token symbol encoded value exceeds the maximum value of + /// [`TokenSymbol::MAX_ENCODED_VALUE`]. + fn try_from_interface( + interface: AccountInterface, + storage: &AccountStorage, + ) -> Result { + for component in interface.components().iter() { + if let AccountComponentInterface::BasicFungibleFaucet(offset) = component { + // obtain metadata from storage using offset provided by BasicFungibleFaucet + // interface + let faucet_metadata = storage + .get_item(*offset) + .map_err(|_| FungibleFaucetError::InvalidStorageOffset(*offset))?; + let [max_supply, decimals, token_symbol, _] = *faucet_metadata; + + // verify metadata values + let token_symbol = TokenSymbol::try_from(token_symbol) + .map_err(FungibleFaucetError::InvalidTokenSymbol)?; + let decimals = decimals.as_int().try_into().map_err(|_| { + FungibleFaucetError::TooManyDecimals { + actual: decimals.as_int(), + max: Self::MAX_DECIMALS, + } + })?; + + return BasicFungibleFaucet::new(token_symbol, decimals, max_supply); + } + } + + Err(FungibleFaucetError::NoAvailableInterface) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the symbol of the faucet. + pub fn symbol(&self) -> TokenSymbol { + self.symbol + } + + /// Returns the decimals of the faucet. + pub fn decimals(&self) -> u8 { + self.decimals + } + + /// Returns the max supply of the faucet. + pub fn max_supply(&self) -> Felt { + self.max_supply + } + + /// Returns the digest of the `distribute` account procedure. + pub fn distribute_digest() -> Word { + *BASIC_FUNGIBLE_FAUCET_DISTRIBUTE + } + + /// Returns the digest of the `burn` account procedure. + pub fn burn_digest() -> Word { + *BASIC_FUNGIBLE_FAUCET_BURN + } +} + +impl From for AccountComponent { + fn from(faucet: BasicFungibleFaucet) -> Self { + // Note: data is stored as [a0, a1, a2, a3] but loaded onto the stack as + // [a3, a2, a1, a0, ...] + let metadata = Word::new([ + faucet.max_supply, + Felt::from(faucet.decimals), + faucet.symbol.into(), + Felt::ZERO, + ]); + + AccountComponent::new(basic_fungible_faucet_library(), vec![StorageSlot::Value(metadata)]) + .expect("basic fungible faucet component should satisfy the requirements of a valid account component") + .with_supported_type(AccountType::FungibleFaucet) + } +} + +impl TryFrom for BasicFungibleFaucet { + type Error = FungibleFaucetError; + + fn try_from(account: Account) -> Result { + let account_interface = AccountInterface::from(&account); + + BasicFungibleFaucet::try_from_interface(account_interface, account.storage()) + } +} + +impl TryFrom<&Account> for BasicFungibleFaucet { + type Error = FungibleFaucetError; + + fn try_from(account: &Account) -> Result { + let account_interface = AccountInterface::from(account); + + BasicFungibleFaucet::try_from_interface(account_interface, account.storage()) + } +} + +/// Creates a new faucet account with basic fungible faucet interface, +/// account storage type, specified authentication scheme, and provided meta data (token symbol, +/// decimals, max supply). +/// +/// The basic faucet interface exposes two procedures: +/// - `distribute`, which mints an assets and create a note for the provided recipient. +/// - `burn`, which burns the provided asset. +/// +/// The `distribute` procedure can be called from a transaction script and requires authentication +/// via the specified authentication scheme. The `burn` procedure can only be called from a note +/// script and requires the calling note to contain the asset to be burned. +/// +/// The storage layout of the faucet account is: +/// - Slot 0: Reserved slot for faucets. +/// - Slot 1: Public Key of the authentication component. +/// - Slot 2: [num_tracked_procs, allow_unauthorized_output_notes, allow_unauthorized_input_notes, +/// 0]. +/// - Slot 3: A map with tracked procedure roots. +/// - Slot 4: Token metadata of the faucet. +pub fn create_basic_fungible_faucet( + init_seed: [u8; 32], + symbol: TokenSymbol, + decimals: u8, + max_supply: Felt, + account_storage_mode: AccountStorageMode, + auth_scheme: AuthScheme, +) -> Result { + let distribute_proc_root = BasicFungibleFaucet::distribute_digest(); + + let auth_component: AccountComponent = match auth_scheme { + AuthScheme::RpoFalcon512 { pub_key } => AuthRpoFalcon512Acl::new( + pub_key, + AuthRpoFalcon512AclConfig::new() + .with_auth_trigger_procedures(vec![distribute_proc_root]) + .with_allow_unauthorized_input_notes(true), + ) + .map_err(FungibleFaucetError::AccountError)? + .into(), + AuthScheme::NoAuth => { + return Err(FungibleFaucetError::UnsupportedAuthScheme( + "basic fungible faucets cannot be created with NoAuth authentication scheme".into(), + )); + }, + AuthScheme::RpoFalcon512Multisig { threshold: _, pub_keys: _ } => { + return Err(FungibleFaucetError::UnsupportedAuthScheme( + "basic fungible faucets do not support multisig authentication".into(), + )); + }, + AuthScheme::Unknown => { + return Err(FungibleFaucetError::UnsupportedAuthScheme( + "basic fungible faucets cannot be created with Unknown authentication scheme" + .into(), + )); + }, + }; + + let account = AccountBuilder::new(init_seed) + .account_type(AccountType::FungibleFaucet) + .storage_mode(account_storage_mode) + .with_auth_component(auth_component) + .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply)?) + .build() + .map_err(FungibleFaucetError::AccountError)?; + + Ok(account) +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + use miden_objects::{FieldElement, ONE, Word}; + + use super::{ + AccountBuilder, + AccountStorageMode, + AccountType, + AuthScheme, + BasicFungibleFaucet, + Felt, + FungibleFaucetError, + TokenSymbol, + create_basic_fungible_faucet, + }; + use crate::account::auth::AuthRpoFalcon512; + use crate::account::wallets::BasicWallet; + + #[test] + fn faucet_contract_creation() { + let pub_key_word = Word::new([ONE; 4]); + let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key: pub_key_word.into() }; + + // we need to use an initial seed to create the wallet account + let init_seed: [u8; 32] = [ + 90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85, + 183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16, + ]; + + let max_supply = Felt::new(123); + let token_symbol_string = "POL"; + let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap(); + let decimals = 2u8; + let storage_mode = AccountStorageMode::Private; + + let faucet_account = create_basic_fungible_faucet( + init_seed, + token_symbol, + decimals, + max_supply, + storage_mode, + auth_scheme, + ) + .unwrap(); + + // The reserved faucet slot should be initialized to an empty word. + assert_eq!(faucet_account.storage().get_item(0).unwrap(), Word::empty()); + + // The falcon auth component is added first so its assigned storage slot for the public key + // will be 1. + assert_eq!(faucet_account.storage().get_item(1).unwrap(), pub_key_word); + + // Slot 2 stores [num_tracked_procs, allow_unauthorized_output_notes, + // allow_unauthorized_input_notes, 0]. With 1 tracked procedure (distribute), + // allow_unauthorized_output_notes=false, and allow_unauthorized_input_notes=true, + // this should be [1, 0, 1, 0]. + assert_eq!( + faucet_account.storage().get_item(2).unwrap(), + [Felt::ONE, Felt::ZERO, Felt::ONE, Felt::ZERO].into() + ); + + // The procedure root map in slot 3 should contain the distribute procedure root. + let distribute_root = BasicFungibleFaucet::distribute_digest(); + assert_eq!( + faucet_account + .storage() + .get_map_item(3, [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO].into()) + .unwrap(), + distribute_root + ); + + // Check that faucet metadata was initialized to the given values. The faucet component is + // added second, so its assigned storage slot for the metadata will be 2. + assert_eq!( + faucet_account.storage().get_item(4).unwrap(), + [Felt::new(123), Felt::new(2), token_symbol.into(), Felt::ZERO].into() + ); + + assert!(faucet_account.is_faucet()); + + assert_eq!(faucet_account.account_type(), AccountType::FungibleFaucet); + + // Verify the faucet can be extracted and has correct metadata + let faucet_component = BasicFungibleFaucet::try_from(faucet_account.clone()).unwrap(); + assert_eq!(faucet_component.symbol(), token_symbol); + assert_eq!(faucet_component.decimals(), decimals); + assert_eq!(faucet_component.max_supply(), max_supply); + } + + #[test] + fn faucet_create_from_account() { + // prepare the test data + let mock_word = Word::from([0, 1, 2, 3u32]); + let mock_public_key = miden_objects::account::PublicKeyCommitment::from(mock_word); + let mock_seed = mock_word.as_bytes(); + + // valid account + let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol"); + let faucet_account = AccountBuilder::new(mock_seed) + .account_type(AccountType::FungibleFaucet) + .with_component( + BasicFungibleFaucet::new(token_symbol, 10, Felt::new(100)) + .expect("failed to create a fungible faucet component"), + ) + .with_auth_component(AuthRpoFalcon512::new(mock_public_key)) + .build_existing() + .expect("failed to create wallet account"); + + let basic_ff = BasicFungibleFaucet::try_from(faucet_account) + .expect("basic fungible faucet creation failed"); + assert_eq!(basic_ff.symbol(), token_symbol); + assert_eq!(basic_ff.decimals(), 10); + assert_eq!(basic_ff.max_supply(), Felt::new(100)); + + // invalid account: basic fungible faucet component is missing + let invalid_faucet_account = AccountBuilder::new(mock_seed) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(AuthRpoFalcon512::new(mock_public_key)) + // we need to add some other component so the builder doesn't fail + .with_component(BasicWallet) + .build_existing() + .expect("failed to create wallet account"); + + let err = BasicFungibleFaucet::try_from(invalid_faucet_account) + .err() + .expect("basic fungible faucet creation should fail"); + assert_matches!(err, FungibleFaucetError::NoAvailableInterface); + } + + /// Check that the obtaining of the basic fungible faucet procedure digests does not panic. + #[test] + fn get_faucet_procedures() { + let _distribute_digest = BasicFungibleFaucet::distribute_digest(); + let _burn_digest = BasicFungibleFaucet::burn_digest(); + } +} diff --git a/crates/miden-lib/src/account/faucets/mod.rs b/crates/miden-lib/src/account/faucets/mod.rs index b2a129c62c..f09f361044 100644 --- a/crates/miden-lib/src/account/faucets/mod.rs +++ b/crates/miden-lib/src/account/faucets/mod.rs @@ -1,217 +1,16 @@ use alloc::string::String; -use miden_objects::account::{ - Account, - AccountBuilder, - AccountComponent, - AccountStorage, - AccountStorageMode, - AccountType, - StorageSlot, -}; -use miden_objects::asset::{FungibleAsset, TokenSymbol}; -use miden_objects::{AccountError, Felt, FieldElement, TokenSymbolError, Word}; +use miden_objects::account::{Account, AccountType}; +use miden_objects::{AccountError, Felt, TokenSymbolError}; use thiserror::Error; -use super::AuthScheme; -use super::interface::{AccountComponentInterface, AccountInterface}; -use crate::account::auth::{ - AuthRpoFalcon512Acl, - AuthRpoFalcon512AclConfig, - AuthRpoFalcon512Multisig, - AuthRpoFalcon512MultisigConfig, -}; -use crate::account::components::basic_fungible_faucet_library; -use crate::procedure_digest; use crate::transaction::memory::FAUCET_STORAGE_DATA_SLOT; -// BASIC FUNGIBLE FAUCET ACCOUNT COMPONENT -// ================================================================================================ - -// Initialize the digest of the `distribute` procedure of the Basic Fungible Faucet only once. -procedure_digest!( - BASIC_FUNGIBLE_FAUCET_DISTRIBUTE, - BasicFungibleFaucet::DISTRIBUTE_PROC_NAME, - basic_fungible_faucet_library -); - -// Initialize the digest of the `burn` procedure of the Basic Fungible Faucet only once. -procedure_digest!( - BASIC_FUNGIBLE_FAUCET_BURN, - BasicFungibleFaucet::BURN_PROC_NAME, - basic_fungible_faucet_library -); - -/// An [`AccountComponent`] implementing a basic fungible faucet. -/// -/// It reexports the procedures from `miden::contracts::faucets::basic_fungible`. When linking -/// against this component, the `miden` library (i.e. [`MidenLib`](crate::MidenLib)) must be -/// available to the assembler which is the case when using -/// [`TransactionKernel::assembler()`][kasm]. The procedures of this component are: -/// - `distribute`, which mints an assets and create a note for the provided recipient. -/// - `burn`, which burns the provided asset. -/// -/// `distribute` requires authentication while `burn` does not require authentication and can be -/// called by anyone. Thus, this component must be combined with a component providing -/// authentication. -/// -/// This component supports accounts of type [`AccountType::FungibleFaucet`]. -/// -/// [kasm]: crate::transaction::TransactionKernel::assembler -pub struct BasicFungibleFaucet { - symbol: TokenSymbol, - decimals: u8, - max_supply: Felt, -} - -impl BasicFungibleFaucet { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - - /// The maximum number of decimals supported by the component. - pub const MAX_DECIMALS: u8 = 12; - - const DISTRIBUTE_PROC_NAME: &str = "distribute"; - const BURN_PROC_NAME: &str = "burn"; - - // CONSTRUCTORS - // -------------------------------------------------------------------------------------------- - - /// Creates a new [`BasicFungibleFaucet`] component from the given pieces of metadata. - /// - /// # Errors: - /// Returns an error if: - /// - the decimals parameter exceeds maximum value of [`Self::MAX_DECIMALS`]. - /// - the max supply parameter exceeds maximum possible amount for a fungible asset - /// ([`FungibleAsset::MAX_AMOUNT`]) - pub fn new( - symbol: TokenSymbol, - decimals: u8, - max_supply: Felt, - ) -> Result { - // First check that the metadata is valid. - if decimals > Self::MAX_DECIMALS { - return Err(FungibleFaucetError::TooManyDecimals { - actual: decimals as u64, - max: Self::MAX_DECIMALS, - }); - } else if max_supply.as_int() > FungibleAsset::MAX_AMOUNT { - return Err(FungibleFaucetError::MaxSupplyTooLarge { - actual: max_supply.as_int(), - max: FungibleAsset::MAX_AMOUNT, - }); - } - - Ok(Self { symbol, decimals, max_supply }) - } - - /// Attempts to create a new [`BasicFungibleFaucet`] component from the associated account - /// interface and storage. - /// - /// # Errors: - /// Returns an error if: - /// - the provided [`AccountInterface`] does not contain a - /// [`AccountComponentInterface::BasicFungibleFaucet`] component. - /// - the decimals parameter exceeds maximum value of [`Self::MAX_DECIMALS`]. - /// - the max supply value exceeds maximum possible amount for a fungible asset of - /// [`FungibleAsset::MAX_AMOUNT`]. - /// - the token symbol encoded value exceeds the maximum value of - /// [`TokenSymbol::MAX_ENCODED_VALUE`]. - fn try_from_interface( - interface: AccountInterface, - storage: &AccountStorage, - ) -> Result { - for component in interface.components().iter() { - if let AccountComponentInterface::BasicFungibleFaucet(offset) = component { - // obtain metadata from storage using offset provided by BasicFungibleFaucet - // interface - let faucet_metadata = storage - .get_item(*offset) - .map_err(|_| FungibleFaucetError::InvalidStorageOffset(*offset))?; - let [max_supply, decimals, token_symbol, _] = *faucet_metadata; - - // verify metadata values - let token_symbol = TokenSymbol::try_from(token_symbol) - .map_err(FungibleFaucetError::InvalidTokenSymbol)?; - let decimals = decimals.as_int().try_into().map_err(|_| { - FungibleFaucetError::TooManyDecimals { - actual: decimals.as_int(), - max: Self::MAX_DECIMALS, - } - })?; +mod basic_fungible; +mod network_fungible; - return BasicFungibleFaucet::new(token_symbol, decimals, max_supply); - } - } - - Err(FungibleFaucetError::NoAvailableInterface) - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns the symbol of the faucet. - pub fn symbol(&self) -> TokenSymbol { - self.symbol - } - - /// Returns the decimals of the faucet. - pub fn decimals(&self) -> u8 { - self.decimals - } - - /// Returns the max supply of the faucet. - pub fn max_supply(&self) -> Felt { - self.max_supply - } - - /// Returns the digest of the `distribute` account procedure. - pub fn distribute_digest() -> Word { - *BASIC_FUNGIBLE_FAUCET_DISTRIBUTE - } - - /// Returns the digest of the `burn` account procedure. - pub fn burn_digest() -> Word { - *BASIC_FUNGIBLE_FAUCET_BURN - } -} - -impl From for AccountComponent { - fn from(faucet: BasicFungibleFaucet) -> Self { - // Note: data is stored as [a0, a1, a2, a3] but loaded onto the stack as - // [a3, a2, a1, a0, ...] - let metadata = Word::new([ - faucet.max_supply, - Felt::from(faucet.decimals), - faucet.symbol.into(), - Felt::ZERO, - ]); - - AccountComponent::new(basic_fungible_faucet_library(), vec![StorageSlot::Value(metadata)]) - .expect("basic fungible faucet component should satisfy the requirements of a valid account component") - .with_supported_type(AccountType::FungibleFaucet) - } -} - -impl TryFrom for BasicFungibleFaucet { - type Error = FungibleFaucetError; - - fn try_from(account: Account) -> Result { - let account_interface = AccountInterface::from(&account); - - BasicFungibleFaucet::try_from_interface(account_interface, account.storage()) - } -} - -impl TryFrom<&Account> for BasicFungibleFaucet { - type Error = FungibleFaucetError; - - fn try_from(account: &Account) -> Result { - let account_interface = AccountInterface::from(account); - - BasicFungibleFaucet::try_from_interface(account_interface, account.storage()) - } -} +pub use basic_fungible::{BasicFungibleFaucet, create_basic_fungible_faucet}; +pub use network_fungible::{NetworkFungibleFaucet, create_network_fungible_faucet}; // FUNGIBLE FAUCET // ================================================================================================ @@ -246,75 +45,6 @@ impl FungibleFaucetExt for Account { } } -/// Creates a new faucet account with basic fungible faucet interface, -/// account storage type, specified authentication scheme, and provided meta data (token symbol, -/// decimals, max supply). -/// -/// The basic faucet interface exposes two procedures: -/// - `distribute`, which mints an assets and create a note for the provided recipient. -/// - `burn`, which burns the provided asset. -/// -/// `distribute` requires authentication. The authentication procedure is defined by the specified -/// authentication scheme. `burn` does not require authentication and can be called by anyone. -/// -/// The storage layout of the faucet account is: -/// - Slot 0: Reserved slot for faucets. -/// - Slot 1: Public Key of the authentication component. -/// - Slot 2: [num_tracked_procs, allow_unauthorized_output_notes, allow_unauthorized_input_notes, -/// 0]. -/// - Slot 3: A map with tracked procedure roots. -/// - Slot 4: Token metadata of the faucet. -pub fn create_basic_fungible_faucet( - init_seed: [u8; 32], - symbol: TokenSymbol, - decimals: u8, - max_supply: Felt, - account_storage_mode: AccountStorageMode, - auth_scheme: AuthScheme, -) -> Result { - let distribute_proc_root = BasicFungibleFaucet::distribute_digest(); - - let auth_component: AccountComponent = match auth_scheme { - AuthScheme::RpoFalcon512 { pub_key } => AuthRpoFalcon512Acl::new( - pub_key, - AuthRpoFalcon512AclConfig::new() - .with_auth_trigger_procedures(vec![distribute_proc_root]) - .with_allow_unauthorized_input_notes(true), - ) - .map_err(FungibleFaucetError::AccountError)? - .into(), - AuthScheme::RpoFalcon512Multisig { threshold, pub_keys } => { - // TODO this means burning the asset requires m/n approvals. We should address this. - let config = AuthRpoFalcon512MultisigConfig::new(pub_keys, threshold) - .map_err(FungibleFaucetError::AccountError)?; - AuthRpoFalcon512Multisig::new(config) - .map_err(FungibleFaucetError::AccountError)? - .into() - }, - AuthScheme::NoAuth => { - return Err(FungibleFaucetError::UnsupportedAuthScheme( - "basic fungible faucets cannot be created with NoAuth authentication scheme".into(), - )); - }, - AuthScheme::Unknown => { - return Err(FungibleFaucetError::UnsupportedAuthScheme( - "basic fungible faucets cannot be created with Unknown authentication scheme" - .into(), - )); - }, - }; - - let account = AccountBuilder::new(init_seed) - .account_type(AccountType::FungibleFaucet) - .storage_mode(account_storage_mode) - .with_auth_component(auth_component) - .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply)?) - .build() - .map_err(FungibleFaucetError::AccountError)?; - - Ok(account) -} - // FUNGIBLE FAUCET ERROR // ================================================================================================ @@ -340,145 +70,3 @@ pub enum FungibleFaucetError { #[error("account is not a fungible faucet account")] NotAFungibleFaucetAccount, } - -// TESTS -// ================================================================================================ - -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use miden_objects::account::PublicKeyCommitment; - use miden_objects::{FieldElement, ONE, Word}; - - use super::{ - AccountBuilder, - AccountStorageMode, - AccountType, - AuthScheme, - BasicFungibleFaucet, - Felt, - FungibleFaucetError, - TokenSymbol, - create_basic_fungible_faucet, - }; - use crate::account::auth::AuthRpoFalcon512; - use crate::account::wallets::BasicWallet; - - #[test] - fn faucet_contract_creation() { - let pub_key_word = Word::new([ONE; 4]); - let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key: pub_key_word.into() }; - - // we need to use an initial seed to create the wallet account - let init_seed: [u8; 32] = [ - 90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85, - 183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16, - ]; - - let max_supply = Felt::new(123); - let token_symbol_string = "POL"; - let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap(); - let decimals = 2u8; - let storage_mode = AccountStorageMode::Private; - - let faucet_account = create_basic_fungible_faucet( - init_seed, - token_symbol, - decimals, - max_supply, - storage_mode, - auth_scheme, - ) - .unwrap(); - - // The reserved faucet slot should be initialized to an empty word. - assert_eq!(faucet_account.storage().get_item(0).unwrap(), Word::empty()); - - // The falcon auth component is added first so its assigned storage slot for the public key - // will be 1. - assert_eq!(faucet_account.storage().get_item(1).unwrap(), pub_key_word); - - // Slot 2 stores [num_tracked_procs, allow_unauthorized_output_notes, - // allow_unauthorized_input_notes, 0]. With 1 tracked procedure (distribute), - // allow_unauthorized_output_notes=false, and allow_unauthorized_input_notes=true, - // this should be [1, 0, 1, 0]. - assert_eq!( - faucet_account.storage().get_item(2).unwrap(), - [Felt::ONE, Felt::ZERO, Felt::ONE, Felt::ZERO].into() - ); - - // The procedure root map in slot 3 should contain the distribute procedure root. - let distribute_root = BasicFungibleFaucet::distribute_digest(); - assert_eq!( - faucet_account - .storage() - .get_map_item(3, [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO].into()) - .unwrap(), - distribute_root - ); - - // Check that faucet metadata was initialized to the given values. The faucet component is - // added second, so its assigned storage slot for the metadata will be 2. - assert_eq!( - faucet_account.storage().get_item(4).unwrap(), - [Felt::new(123), Felt::new(2), token_symbol.into(), Felt::ZERO].into() - ); - - assert!(faucet_account.is_faucet()); - - assert_eq!(faucet_account.account_type(), AccountType::FungibleFaucet); - - // Verify the faucet can be extracted and has correct metadata - let faucet_component = BasicFungibleFaucet::try_from(faucet_account.clone()).unwrap(); - assert_eq!(faucet_component.symbol(), token_symbol); - assert_eq!(faucet_component.decimals(), decimals); - assert_eq!(faucet_component.max_supply(), max_supply); - } - - #[test] - fn faucet_create_from_account() { - // prepare the test data - let mock_word = Word::from([0, 1, 2, 3u32]); - let mock_public_key = PublicKeyCommitment::from(mock_word); - let mock_seed = mock_word.as_bytes(); - - // valid account - let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol"); - let faucet_account = AccountBuilder::new(mock_seed) - .account_type(AccountType::FungibleFaucet) - .with_component( - BasicFungibleFaucet::new(token_symbol, 10, Felt::new(100)) - .expect("failed to create a fungible faucet component"), - ) - .with_auth_component(AuthRpoFalcon512::new(mock_public_key)) - .build_existing() - .expect("failed to create wallet account"); - - let basic_ff = BasicFungibleFaucet::try_from(faucet_account) - .expect("basic fungible faucet creation failed"); - assert_eq!(basic_ff.symbol, token_symbol); - assert_eq!(basic_ff.decimals, 10); - assert_eq!(basic_ff.max_supply, Felt::new(100)); - - // invalid account: basic fungible faucet component is missing - let invalid_faucet_account = AccountBuilder::new(mock_seed) - .account_type(AccountType::FungibleFaucet) - .with_auth_component(AuthRpoFalcon512::new(mock_public_key)) - // we need to add some other component so the builder doesn't fail - .with_component(BasicWallet) - .build_existing() - .expect("failed to create wallet account"); - - let err = BasicFungibleFaucet::try_from(invalid_faucet_account) - .err() - .expect("basic fungible faucet creation should fail"); - assert_matches!(err, FungibleFaucetError::NoAvailableInterface); - } - - /// Check that the obtaining of the basic fungible faucet procedure digests does not panic. - #[test] - fn get_faucet_procedures() { - let _distribute_digest = BasicFungibleFaucet::distribute_digest(); - let _burn_digest = BasicFungibleFaucet::burn_digest(); - } -} diff --git a/crates/miden-lib/src/account/faucets/network_fungible.rs b/crates/miden-lib/src/account/faucets/network_fungible.rs new file mode 100644 index 0000000000..3407fd71fc --- /dev/null +++ b/crates/miden-lib/src/account/faucets/network_fungible.rs @@ -0,0 +1,276 @@ +use miden_objects::account::{ + Account, + AccountBuilder, + AccountComponent, + AccountId, + AccountStorage, + AccountStorageMode, + AccountType, + StorageSlot, +}; +use miden_objects::asset::TokenSymbol; +use miden_objects::{Felt, FieldElement, Word}; + +use super::{BasicFungibleFaucet, FungibleFaucetError}; +use crate::account::auth::NoAuth; +use crate::account::components::network_fungible_faucet_library; +use crate::account::interface::{AccountComponentInterface, AccountInterface}; +use crate::procedure_digest; + +// NETWORK FUNGIBLE FAUCET ACCOUNT COMPONENT +// ================================================================================================ + +// Initialize the digest of the `distribute` procedure of the Network Fungible Faucet only once. +procedure_digest!( + NETWORK_FUNGIBLE_FAUCET_DISTRIBUTE, + NetworkFungibleFaucet::DISTRIBUTE_PROC_NAME, + network_fungible_faucet_library +); + +// Initialize the digest of the `burn` procedure of the Network Fungible Faucet only once. +procedure_digest!( + NETWORK_FUNGIBLE_FAUCET_BURN, + NetworkFungibleFaucet::BURN_PROC_NAME, + network_fungible_faucet_library +); + +/// An [`AccountComponent`] implementing a network fungible faucet. +/// +/// It reexports the procedures from `miden::contracts::faucets::network_fungible`. When linking +/// against this component, the `miden` library (i.e. [`MidenLib`](crate::MidenLib)) must be +/// available to the assembler which is the case when using +/// [`TransactionKernel::assembler()`][kasm]. The procedures of this component are: +/// - `distribute`, which mints an assets and create a note for the provided recipient. +/// - `burn`, which burns the provided asset. +/// +/// Both `distribute` and `burn` can only be called from note scripts. `distribute` requires +/// authentication while `burn` does not require authentication and can be called by anyone. +/// Thus, this component must be combined with a component providing authentication. +/// +/// This component supports accounts of type [`AccountType::FungibleFaucet`]. +/// +/// Unlike [`super::BasicFungibleFaucet`], this component uses two storage slots: +/// - First slot: Token metadata `[max_supply, decimals, token_symbol, 0]` +/// - Second slot: Owner account ID as a single Word +/// +/// [kasm]: crate::transaction::TransactionKernel::assembler +pub struct NetworkFungibleFaucet { + faucet: BasicFungibleFaucet, + owner_account_id: AccountId, +} + +impl NetworkFungibleFaucet { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The maximum number of decimals supported by the component. + pub const MAX_DECIMALS: u8 = 12; + + const DISTRIBUTE_PROC_NAME: &str = "distribute"; + const BURN_PROC_NAME: &str = "burn"; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`NetworkFungibleFaucet`] component from the given pieces of metadata. + /// + /// # Errors: + /// Returns an error if: + /// - the decimals parameter exceeds maximum value of [`Self::MAX_DECIMALS`]. + /// - the max supply parameter exceeds maximum possible amount for a fungible asset + /// ([`miden_objects::asset::FungibleAsset::MAX_AMOUNT`]) + pub fn new( + symbol: TokenSymbol, + decimals: u8, + max_supply: Felt, + owner_account_id: AccountId, + ) -> Result { + // Create the basic fungible faucet (this validates the metadata) + let faucet = BasicFungibleFaucet::new(symbol, decimals, max_supply)?; + + Ok(Self { faucet, owner_account_id }) + } + + /// Attempts to create a new [`NetworkFungibleFaucet`] component from the associated account + /// interface and storage. + /// + /// # Errors: + /// Returns an error if: + /// - the provided [`AccountInterface`] does not contain a + /// [`AccountComponentInterface::NetworkFungibleFaucet`] component. + /// - the decimals parameter exceeds maximum value of [`Self::MAX_DECIMALS`]. + /// - the max supply value exceeds maximum possible amount for a fungible asset of + /// [`miden_objects::asset::FungibleAsset::MAX_AMOUNT`]. + /// - the token symbol encoded value exceeds the maximum value of + /// [`TokenSymbol::MAX_ENCODED_VALUE`]. + fn try_from_interface( + interface: AccountInterface, + storage: &AccountStorage, + ) -> Result { + for component in interface.components().iter() { + if let AccountComponentInterface::NetworkFungibleFaucet(offset) = component { + // obtain metadata from storage using offset provided by NetworkFungibleFaucet + // interface + let faucet_metadata = storage + .get_item(*offset) + .map_err(|_| FungibleFaucetError::InvalidStorageOffset(*offset))?; + let [max_supply, decimals, token_symbol, _] = *faucet_metadata; + + // obtain owner account ID from the next storage slot + let owner_account_id_word: Word = storage + .get_item(*offset + 1) + .map_err(|_| FungibleFaucetError::InvalidStorageOffset(*offset + 1))?; + + // Convert Word back to AccountId + // Storage format: [0, 0, suffix, prefix] + let prefix = owner_account_id_word[3]; + let suffix = owner_account_id_word[2]; + let owner_account_id = AccountId::new_unchecked([prefix, suffix]); + + // verify metadata values and create BasicFungibleFaucet + let token_symbol = TokenSymbol::try_from(token_symbol) + .map_err(FungibleFaucetError::InvalidTokenSymbol)?; + let decimals = decimals.as_int().try_into().map_err(|_| { + FungibleFaucetError::TooManyDecimals { + actual: decimals.as_int(), + max: Self::MAX_DECIMALS, + } + })?; + + let faucet = BasicFungibleFaucet::new(token_symbol, decimals, max_supply)?; + + return Ok(Self { faucet, owner_account_id }); + } + } + + Err(FungibleFaucetError::NoAvailableInterface) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the symbol of the faucet. + pub fn symbol(&self) -> TokenSymbol { + self.faucet.symbol() + } + + /// Returns the decimals of the faucet. + pub fn decimals(&self) -> u8 { + self.faucet.decimals() + } + + /// Returns the max supply of the faucet. + pub fn max_supply(&self) -> Felt { + self.faucet.max_supply() + } + + /// Returns the owner account ID of the faucet. + pub fn owner_account_id(&self) -> AccountId { + self.owner_account_id + } + + /// Returns the digest of the `distribute` account procedure. + pub fn distribute_digest() -> Word { + *NETWORK_FUNGIBLE_FAUCET_DISTRIBUTE + } + + /// Returns the digest of the `burn` account procedure. + pub fn burn_digest() -> Word { + *NETWORK_FUNGIBLE_FAUCET_BURN + } +} + +impl From for AccountComponent { + fn from(network_faucet: NetworkFungibleFaucet) -> Self { + // Note: data is stored as [a0, a1, a2, a3] but loaded onto the stack as + // [a3, a2, a1, a0, ...] + let metadata = Word::new([ + network_faucet.faucet.max_supply(), + Felt::from(network_faucet.faucet.decimals()), + network_faucet.faucet.symbol().into(), + Felt::ZERO, + ]); + + // Convert AccountId to Word representation for storage + let owner_account_id_word: Word = [ + Felt::new(0), + Felt::new(0), + network_faucet.owner_account_id.suffix(), + network_faucet.owner_account_id.prefix().as_felt(), + ] + .into(); + + // Second storage slot stores the owner account ID + let owner_slot = StorageSlot::Value(owner_account_id_word); + + AccountComponent::new( + network_fungible_faucet_library(), + vec![StorageSlot::Value(metadata), owner_slot] + ) + .expect("network fungible faucet component should satisfy the requirements of a valid account component") + .with_supported_type(AccountType::FungibleFaucet) + } +} + +impl TryFrom for NetworkFungibleFaucet { + type Error = FungibleFaucetError; + + fn try_from(account: Account) -> Result { + let account_interface = AccountInterface::from(&account); + + NetworkFungibleFaucet::try_from_interface(account_interface, account.storage()) + } +} + +impl TryFrom<&Account> for NetworkFungibleFaucet { + type Error = FungibleFaucetError; + + fn try_from(account: &Account) -> Result { + let account_interface = AccountInterface::from(account); + + NetworkFungibleFaucet::try_from_interface(account_interface, account.storage()) + } +} + +/// Creates a new faucet account with network fungible faucet interface and provided metadata +/// (token symbol, decimals, max supply, owner account ID). +/// +/// The network faucet interface exposes two procedures: +/// - `distribute`, which mints an assets and create a note for the provided recipient. +/// - `burn`, which burns the provided asset. +/// +/// Both `distribute` and `burn` can only be called from note scripts. `distribute` requires +/// authentication using the NoAuth scheme. `burn` does not require authentication and can be +/// called by anyone. +/// +/// Network fungible faucets always use: +/// - [`AccountStorageMode::Network`] for storage +/// - [`NoAuth`] for authentication +/// +/// The storage layout of the network faucet account is: +/// - Slot 0: Reserved slot for faucets. +/// - Slot 1: Public Key of the authentication component. +/// - Slot 2: [num_tracked_procs, allow_unauthorized_output_notes, allow_unauthorized_input_notes, +/// 0]. +/// - Slot 3: A map with tracked procedure roots. +/// - Slot 4: Token metadata of the faucet. +/// - Slot 5: Owner account ID. +pub fn create_network_fungible_faucet( + init_seed: [u8; 32], + symbol: TokenSymbol, + decimals: u8, + max_supply: Felt, + owner_account_id: AccountId, +) -> Result { + let auth_component: AccountComponent = NoAuth::new().into(); + + let account = AccountBuilder::new(init_seed) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Network) + .with_auth_component(auth_component) + .with_component(NetworkFungibleFaucet::new(symbol, decimals, max_supply, owner_account_id)?) + .build() + .map_err(FungibleFaucetError::AccountError)?; + + Ok(account) +} diff --git a/crates/miden-lib/src/account/interface/component.rs b/crates/miden-lib/src/account/interface/component.rs index 938a1034d7..27f47d771f 100644 --- a/crates/miden-lib/src/account/interface/component.rs +++ b/crates/miden-lib/src/account/interface/component.rs @@ -30,6 +30,12 @@ pub enum AccountComponentInterface { /// slot has a format of `[max_supply, faucet_decimals, token_symbol, 0]`. BasicFungibleFaucet(u8), /// Exposes procedures from the + /// [`NetworkFungibleFaucet`][crate::account::faucets::NetworkFungibleFaucet] module. + /// + /// Internal value holds the storage slot index where faucet metadata is stored. This metadata + /// slot has a format of `[max_supply, faucet_decimals, token_symbol, 0]`. + NetworkFungibleFaucet(u8), + /// Exposes procedures from the /// [`AuthRpoFalcon512`][crate::account::auth::AuthRpoFalcon512] module. /// /// Internal value holds the storage slot index where the public key for the RpoFalcon512 @@ -69,6 +75,9 @@ impl AccountComponentInterface { AccountComponentInterface::BasicFungibleFaucet(_) => { "Basic Fungible Faucet".to_string() }, + AccountComponentInterface::NetworkFungibleFaucet(_) => { + "Network Fungible Faucet".to_string() + }, AccountComponentInterface::AuthRpoFalcon512(_) => "RPO Falcon512".to_string(), AccountComponentInterface::AuthRpoFalcon512Acl(_) => "RPO Falcon512 ACL".to_string(), AccountComponentInterface::AuthRpoFalcon512Multisig(_) => { diff --git a/crates/miden-lib/src/account/interface/mod.rs b/crates/miden-lib/src/account/interface/mod.rs index 7d6666a6b7..ed928ff10b 100644 --- a/crates/miden-lib/src/account/interface/mod.rs +++ b/crates/miden-lib/src/account/interface/mod.rs @@ -15,6 +15,7 @@ use crate::AuthScheme; use crate::account::components::{ basic_fungible_faucet_library, basic_wallet_library, + network_fungible_faucet_library, no_auth_library, rpo_falcon_512_acl_library, rpo_falcon_512_library, @@ -140,6 +141,11 @@ impl AccountInterface { component_proc_digests .extend(basic_fungible_faucet_library().mast_forest().procedure_digests()); }, + AccountComponentInterface::NetworkFungibleFaucet(_) => { + component_proc_digests.extend( + network_fungible_faucet_library().mast_forest().procedure_digests(), + ); + }, AccountComponentInterface::AuthRpoFalcon512(_) => { component_proc_digests .extend(rpo_falcon_512_library().mast_forest().procedure_digests()); @@ -252,6 +258,14 @@ impl AccountInterface { matches!(component_interface, AccountComponentInterface::BasicFungibleFaucet(_)) }) { basic_fungible_faucet.send_note_body(*self.id(), output_notes) + } else if let Some(_network_fungible_faucet) = + self.components().iter().find(|component_interface| { + matches!(component_interface, AccountComponentInterface::NetworkFungibleFaucet(_)) + }) + { + // Network fungible faucet doesn't support send_note_body, because minting + // is done via a MINT note. + Err(AccountInterfaceError::UnsupportedAccountInterface) } else if self.components().contains(&AccountComponentInterface::BasicWallet) { AccountComponentInterface::BasicWallet.send_note_body(*self.id(), output_notes) } else { diff --git a/crates/miden-lib/src/errors/note_script_errors.rs b/crates/miden-lib/src/errors/note_script_errors.rs index 0de640e957..2ee15d05e9 100644 --- a/crates/miden-lib/src/errors/note_script_errors.rs +++ b/crates/miden-lib/src/errors/note_script_errors.rs @@ -13,9 +13,18 @@ use crate::errors::MasmError; /// Error Message: "auth procedure had been called from outside the epilogue" pub const ERR_AUTH_PROCEDURE_CALLED_FROM_WRONG_CONTEXT: MasmError = MasmError::from_static_str("auth procedure had been called from outside the epilogue"); +/// Error Message: "burn requires exactly 1 note asset" +pub const ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS: MasmError = MasmError::from_static_str("burn requires exactly 1 note asset"); + /// Error Message: "number of approvers must be equal to or greater than threshold" pub const ERR_MALFORMED_MULTISIG_CONFIG: MasmError = MasmError::from_static_str("number of approvers must be equal to or greater than threshold"); +/// Error Message: "MINT script expects exactly 9 note inputs" +pub const ERR_MINT_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("MINT script expects exactly 9 note inputs"); + +/// Error Message: "note sender is not the owner of the faucet who can mint assets" +pub const ERR_ONLY_OWNER_CAN_MINT: MasmError = MasmError::from_static_str("note sender is not the owner of the faucet who can mint assets"); + /// Error Message: "failed to reclaim P2IDE note because the reclaiming account is not the sender" pub const ERR_P2IDE_RECLAIM_ACCT_IS_NOT_SENDER: MasmError = MasmError::from_static_str("failed to reclaim P2IDE note because the reclaiming account is not the sender"); /// Error Message: "P2IDE reclaim is disabled" diff --git a/crates/miden-lib/src/note/well_known_note.rs b/crates/miden-lib/src/note/well_known_note.rs index eb94770b10..3a783b8840 100644 --- a/crates/miden-lib/src/note/well_known_note.rs +++ b/crates/miden-lib/src/note/well_known_note.rs @@ -4,6 +4,7 @@ use miden_objects::utils::Deserializable; use miden_objects::utils::sync::LazyLock; use miden_objects::vm::Program; +use crate::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet}; use crate::account::interface::{AccountComponentInterface, AccountInterface}; use crate::account::wallets::BasicWallet; @@ -31,6 +32,20 @@ static SWAP_SCRIPT: LazyLock = LazyLock::new(|| { NoteScript::new(program) }); +// Initialize the MINT note script only once +static MINT_SCRIPT: LazyLock = LazyLock::new(|| { + let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/MINT.masb")); + let program = Program::read_from_bytes(bytes).expect("Shipped MINT script is well-formed"); + NoteScript::new(program) +}); + +// Initialize the BURN note script only once +static BURN_SCRIPT: LazyLock = LazyLock::new(|| { + let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/BURN.masb")); + let program = Program::read_from_bytes(bytes).expect("Shipped BURN script is well-formed"); + NoteScript::new(program) +}); + /// Returns the P2ID (Pay-to-ID) note script. fn p2id() -> NoteScript { P2ID_SCRIPT.clone() @@ -61,6 +76,26 @@ fn swap_root() -> Word { SWAP_SCRIPT.root() } +/// Returns the MINT (Mint note) note script. +fn mint() -> NoteScript { + MINT_SCRIPT.clone() +} + +/// Returns the MINT (Mint note) note script root. +fn mint_root() -> Word { + MINT_SCRIPT.root() +} + +/// Returns the BURN (Burn note) note script. +fn burn() -> NoteScript { + BURN_SCRIPT.clone() +} + +/// Returns the BURN (Burn note) note script root. +fn burn_root() -> Word { + BURN_SCRIPT.root() +} + // WELL KNOWN NOTE // ================================================================================================ @@ -69,6 +104,8 @@ pub enum WellKnownNote { P2ID, P2IDE, SWAP, + MINT, + BURN, } impl WellKnownNote { @@ -84,6 +121,12 @@ impl WellKnownNote { /// Expected number of inputs of the SWAP note. const SWAP_NUM_INPUTS: usize = 10; + /// Expected number of inputs of the MINT note. + const MINT_NUM_INPUTS: usize = 9; + + /// Expected number of inputs of the BURN note. + const BURN_NUM_INPUTS: usize = 0; + // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -101,6 +144,12 @@ impl WellKnownNote { if note_script_root == swap_root() { return Some(Self::SWAP); } + if note_script_root == mint_root() { + return Some(Self::MINT); + } + if note_script_root == burn_root() { + return Some(Self::BURN); + } None } @@ -114,6 +163,8 @@ impl WellKnownNote { Self::P2ID => Self::P2ID_NUM_INPUTS, Self::P2IDE => Self::P2IDE_NUM_INPUTS, Self::SWAP => Self::SWAP_NUM_INPUTS, + Self::MINT => Self::MINT_NUM_INPUTS, + Self::BURN => Self::BURN_NUM_INPUTS, } } @@ -123,6 +174,8 @@ impl WellKnownNote { Self::P2ID => p2id(), Self::P2IDE => p2ide(), Self::SWAP => swap(), + Self::MINT => mint(), + Self::BURN => burn(), } } @@ -132,6 +185,8 @@ impl WellKnownNote { Self::P2ID => p2id_root(), Self::P2IDE => p2ide_root(), Self::SWAP => swap_root(), + Self::MINT => mint_root(), + Self::BURN => burn_root(), } } @@ -155,6 +210,20 @@ impl WellKnownNote { interface_proc_digests.contains(&BasicWallet::receive_asset_digest()) && interface_proc_digests.contains(&BasicWallet::move_asset_to_note_digest()) }, + Self::MINT => { + // MINT notes work only with network fungible faucets. The network faucet uses + // note-based authentication (checking if the note sender equals the faucet owner) + // to authorize minting, while basic faucets have different mint procedures that + // are not compatible with MINT notes. + interface_proc_digests.contains(&NetworkFungibleFaucet::distribute_digest()) + }, + Self::BURN => { + // BURN notes work with both basic and network fungible faucets because both + // faucet types export the same `burn` procedure with identical MAST roots. + // This allows a single BURN note script to work with either faucet type. + interface_proc_digests.contains(&BasicFungibleFaucet::burn_digest()) + || interface_proc_digests.contains(&NetworkFungibleFaucet::burn_digest()) + }, } } } diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 77e1ae9332..6eef096494 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -2,8 +2,18 @@ use alloc::collections::BTreeMap; use alloc::vec::Vec; use anyhow::Context; + +// CONSTANTS +// ================================================================================================ + +/// Default number of decimals for faucets created in tests. +const DEFAULT_FAUCET_DECIMALS: u8 = 10; + +// IMPORTS +// ================================================================================================ + use itertools::Itertools; -use miden_lib::account::faucets::BasicFungibleFaucet; +use miden_lib::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet}; use miden_lib::account::wallets::BasicWallet; use miden_lib::note::{create_p2id_note, create_p2ide_note, create_swap_note}; use miden_lib::testing::account_component::MockAccountComponent; @@ -284,8 +294,9 @@ impl MockChainBuilder { let max_supply_felt = max_supply.try_into().map_err(|_| { anyhow::anyhow!("max supply value cannot be converted to Felt: {max_supply}") })?; - let basic_faucet = BasicFungibleFaucet::new(token_symbol, 10, max_supply_felt) - .context("failed to create BasicFungibleFaucet")?; + let basic_faucet = + BasicFungibleFaucet::new(token_symbol, DEFAULT_FAUCET_DECIMALS, max_supply_felt) + .context("failed to create BasicFungibleFaucet")?; let account_builder = AccountBuilder::new(self.rng.random()) .storage_mode(AccountStorageMode::Public) @@ -295,9 +306,11 @@ impl MockChainBuilder { self.add_account_from_builder(auth_method, account_builder, AccountState::New) } - /// Adds an existing public [`BasicFungibleFaucet`] account to the initial chain state and - /// registers the authenticator (if the given [`Auth`] results in the creation of one). - pub fn add_existing_faucet( + /// Adds an existing [`BasicFungibleFaucet`] account to the initial chain state and + /// registers the authenticator. + /// + /// Basic fungible faucets always use `AccountStorageMode::Public` and require authentication. + pub fn add_existing_basic_faucet( &mut self, auth_method: Auth, token_symbol: &str, @@ -305,8 +318,9 @@ impl MockChainBuilder { total_issuance: Option, ) -> anyhow::Result { let token_symbol = TokenSymbol::new(token_symbol).context("invalid argument")?; - let basic_faucet = BasicFungibleFaucet::new(token_symbol, 10u8, Felt::new(max_supply)) - .context("invalid argument")?; + let basic_faucet = + BasicFungibleFaucet::new(token_symbol, DEFAULT_FAUCET_DECIMALS, Felt::new(max_supply)) + .context("invalid argument")?; let account_builder = AccountBuilder::new(self.rng.random()) .storage_mode(AccountStorageMode::Public) @@ -332,6 +346,50 @@ impl MockChainBuilder { Ok(account) } + /// Adds an existing [`NetworkFungibleFaucet`] account to the initial chain state. + /// + /// Network fungible faucets always use `AccountStorageMode::Network` and `Auth::NoAuth`. + pub fn add_existing_network_faucet( + &mut self, + token_symbol: &str, + max_supply: u64, + owner_account_id: AccountId, + total_issuance: Option, + ) -> anyhow::Result { + let token_symbol = TokenSymbol::new(token_symbol).context("invalid argument")?; + let network_faucet = NetworkFungibleFaucet::new( + token_symbol, + DEFAULT_FAUCET_DECIMALS, + Felt::new(max_supply), + owner_account_id, + ) + .context("invalid argument")?; + + let account_builder = AccountBuilder::new(self.rng.random()) + .storage_mode(AccountStorageMode::Network) + .with_component(network_faucet) + .account_type(AccountType::FungibleFaucet); + + // Network faucets always use Noop auth (no authentication) + let mut account = + self.add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists)?; + + // The faucet's reserved slot is initialized to an empty word by default. + // If total_issuance is set, overwrite it and reinsert the account. + if let Some(issuance) = total_issuance { + account + .storage_mut() + .set_item( + memory::FAUCET_STORAGE_DATA_SLOT, + Word::from([ZERO, ZERO, ZERO, Felt::new(issuance)]), + ) + .context("failed to set faucet storage")?; + self.accounts.insert(account.id(), account.clone()); + } + + Ok(account) + } + /// Creates a new public account with an [`MockAccountComponent`] and registers the /// authenticator (if any). pub fn create_new_mock_account(&mut self, auth_method: Auth) -> anyhow::Result { diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index d54c48e408..bff4e51858 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -1,15 +1,35 @@ extern crate alloc; -use miden_lib::account::faucets::FungibleFaucetExt; +use core::slice; + +use miden_lib::account::faucets::{BasicFungibleFaucet, FungibleFaucetExt, NetworkFungibleFaucet}; use miden_lib::errors::tx_kernel_errors::ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED; +use miden_lib::note::well_known_note::WellKnownNote; use miden_lib::utils::ScriptBuilder; -use miden_objects::account::Account; +use miden_objects::account::{ + Account, + AccountId, + AccountIdVersion, + AccountStorageMode, + AccountType, +}; use miden_objects::asset::{Asset, FungibleAsset}; -use miden_objects::note::{NoteAssets, NoteExecutionHint, NoteId, NoteMetadata, NoteTag, NoteType}; +use miden_objects::note::{ + Note, + NoteAssets, + NoteExecutionHint, + NoteId, + NoteInputs, + NoteMetadata, + NoteRecipient, + NoteTag, + NoteType, +}; use miden_objects::transaction::{ExecutedTransaction, OutputNote}; use miden_objects::{Felt, Word}; use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; +use crate::scripts::swap::create_p2id_note_exact; use crate::{get_note_with_fungible_asset_and_script, prove_and_verify_transaction}; // Shared test utilities for faucet tests @@ -104,7 +124,7 @@ pub fn verify_minted_output_note( #[tokio::test] async fn minting_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result<()> { let mut builder = MockChain::builder(); - let faucet = builder.add_existing_faucet(Auth::BasicAuth, "TST", 200, None)?; + let faucet = builder.add_existing_basic_faucet(Auth::BasicAuth, "TST", 200, None)?; let mut mock_chain = builder.build()?; let params = FaucetTestParams { @@ -133,7 +153,7 @@ async fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() -> anyho // CONSTRUCT AND EXECUTE TX (Failure) // -------------------------------------------------------------------------------------------- let mut builder = MockChain::builder(); - let faucet = builder.add_existing_faucet(Auth::BasicAuth, "TST", 200, None)?; + let faucet = builder.add_existing_basic_faucet(Auth::BasicAuth, "TST", 200, None)?; let mock_chain = builder.build()?; let recipient = Word::from([0, 1, 2, 3u32]); @@ -220,7 +240,7 @@ async fn minting_fungible_asset_on_new_faucet_succeeds() -> anyhow::Result<()> { #[tokio::test] async fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::Result<()> { let mut builder = MockChain::builder(); - let faucet = builder.add_existing_faucet(Auth::BasicAuth, "TST", 200, Some(100))?; + let faucet = builder.add_existing_basic_faucet(Auth::BasicAuth, "TST", 200, Some(100))?; let fungible_asset = FungibleAsset::new(faucet.id(), 100).unwrap(); @@ -229,19 +249,13 @@ async fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::R # burn the asset begin dropw - - # pad the stack before call - padw padw padw padw - # => [pad(16)] - - exec.::miden::active_note::get_assets drop - mem_loadw - # => [ASSET, pad(12)] + # => [] call.::miden::contracts::faucets::basic_fungible::burn + # => [ASSET] # truncate the stack - dropw dropw dropw dropw + dropw end "; @@ -275,3 +289,208 @@ async fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::R assert_eq!(executed_transaction.input_notes().get_note(0).id(), note.id()); Ok(()) } + +// TESTS NETWORK FAUCET +// ================================================================================================ + +/// Tests minting on network faucet +#[tokio::test] +async fn network_faucet_mint() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let faucet_owner_account_id = AccountId::dummy( + [1; 15], + AccountIdVersion::Version0, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + ); + + let faucet = + builder.add_existing_network_faucet("NET", 1000, faucet_owner_account_id, Some(50))?; + + // Create a target account to consume the minted note + let mut target_account = builder.add_existing_wallet(Auth::IncrNonce)?; + + // The Network Fungible Faucet component is added as the second component after auth, so its + // storage slot offset will be 2. Check that max_supply at the word's index 0 is 200. + assert_eq!(faucet.storage().get_item(1).unwrap()[0], Felt::new(1000)); + + // Check that the creator account ID is stored in slot 2 (second storage slot of the component) + // The owner_account_id is stored as Word [0, 0, suffix, prefix] + let stored_owner_id = faucet.storage().get_item(2).unwrap(); + assert_eq!(stored_owner_id[3], faucet_owner_account_id.prefix().as_felt()); + assert_eq!(stored_owner_id[2], Felt::new(faucet_owner_account_id.suffix().as_int())); + + // Check that the faucet reserved slot has been correctly initialized. + // The already issued amount should be 50. + assert_eq!(faucet.get_token_issuance().unwrap(), Felt::new(50)); + + // CREATE MINT NOTE USING STANDARD NOTE + // -------------------------------------------------------------------------------------------- + + let amount = Felt::new(75); + let mint_asset: Asset = FungibleAsset::new(faucet.id(), amount.into()).unwrap().into(); + let tag = NoteTag::for_local_use_case(0, 0).unwrap(); + let aux = Felt::new(27); + let note_execution_hint = NoteExecutionHint::on_block_slot(5, 6, 7); + let note_type = NoteType::Private; + let serial_num = Word::default(); + + let p2id_mint_output_note = create_p2id_note_exact( + faucet.id(), + target_account.id(), + vec![mint_asset], + note_type, + aux, + serial_num, + ) + .unwrap(); + let recipient = p2id_mint_output_note.recipient().digest(); + + // Use the standard MINT note script + let note_script = WellKnownNote::MINT.script(); + + // Create the note inputs for MINT note (reversed order) + let inputs = NoteInputs::new(vec![ + recipient[0], + recipient[1], + recipient[2], + recipient[3], + note_execution_hint.into(), + note_type.into(), + aux, + tag.into(), + amount, + ])?; + + // Create the MINT note using the standard script + let mint_note_metadata = + NoteMetadata::new(faucet_owner_account_id, note_type, tag, note_execution_hint, aux)?; + let mint_note_assets = NoteAssets::new(vec![])?; // Empty assets for mint note + let serial_num = Word::from([1, 2, 3, 4u32]); // Random serial number + let mint_note_recipient = NoteRecipient::new(serial_num, note_script, inputs); + let mint_note = Note::new(mint_note_assets, mint_note_metadata, mint_note_recipient); + + // Add the MINT note to the mock chain + builder.add_output_note(OutputNote::Full(mint_note.clone())); + let mut mock_chain = builder.build()?; + + // EXECUTE MINT NOTE AGAINST NETWORK FAUCET + // -------------------------------------------------------------------------------------------- + let tx_context = mock_chain.build_tx_context(faucet.id(), &[mint_note.id()], &[])?.build()?; + let executed_transaction = tx_context.execute().await?; + + // Check that a P2ID note was created by the faucet + assert_eq!(executed_transaction.output_notes().num_notes(), 1); + let output_note = executed_transaction.output_notes().get_note(0); + + // Verify the output note contains the minted fungible asset + let expected_asset = FungibleAsset::new(faucet.id(), amount.into())?; + let assets = NoteAssets::new(vec![expected_asset.into()])?; + let expected_note_id = NoteId::new(recipient, assets.commitment()); + + assert_eq!(output_note.id(), expected_note_id); + assert_eq!(output_note.metadata().sender(), faucet.id()); + + // Apply the transaction to the mock chain + mock_chain.add_pending_executed_transaction(&executed_transaction)?; + mock_chain.prove_next_block()?; + + // CONSUME THE OUTPUT NOTE WITH TARGET ACCOUNT + // -------------------------------------------------------------------------------------------- + // Execute transaction to consume the output note with the target account + let consume_tx_context = mock_chain + .build_tx_context(target_account.id(), &[], slice::from_ref(&p2id_mint_output_note))? + .build()?; + let consume_executed_transaction = consume_tx_context.execute().await?; + + // Apply the delta to the target account and verify the asset was added to the account's vault + target_account.apply_delta(consume_executed_transaction.account_delta())?; + + // Verify the account's vault now contains the expected fungible asset + let balance = target_account.vault().get_balance(faucet.id())?; + assert_eq!(balance, expected_asset.amount(),); + + Ok(()) +} + +// TESTS FOR FAUCET PROCEDURE COMPATIBILITY +// ================================================================================================ + +/// Tests that basic and network fungible faucets have the same burn procedure digest. +/// This is required for BURN notes to work with both faucet types. +#[test] +fn test_faucet_burn_procedures_are_identical() { + // Both faucet types must export the same burn procedure with identical MAST roots + // so that a single BURN note script can work with either faucet type + assert_eq!( + BasicFungibleFaucet::burn_digest(), + NetworkFungibleFaucet::burn_digest(), + "Basic and network fungible faucets must have the same burn procedure digest" + ); +} + +/// Tests burning on network faucet +#[tokio::test] +async fn network_faucet_burn() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let faucet_owner_account_id = AccountId::dummy( + [1; 15], + AccountIdVersion::Version0, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + ); + + let mut faucet = + builder.add_existing_network_faucet("NET", 200, faucet_owner_account_id, Some(100))?; + + let burn_amount = 100u64; + let fungible_asset = FungibleAsset::new(faucet.id(), burn_amount).unwrap(); + + // CREATE BURN NOTE USING STANDARD NOTE SCRIPT + // -------------------------------------------------------------------------------------------- + // Use the standard BURN note script + let note_script = WellKnownNote::BURN.script(); + + // Create the burn note using the standard script + let burn_note_metadata = NoteMetadata::new( + faucet_owner_account_id, + NoteType::Public, + NoteTag::for_local_use_case(0, 0)?, + NoteExecutionHint::Always, + Felt::new(0), + )?; + let burn_note_assets = NoteAssets::new(vec![fungible_asset.into()])?; + let serial_num = Word::from([5, 6, 7, 8u32]); + let inputs = NoteInputs::new(vec![]).unwrap(); + let burn_note_recipient = NoteRecipient::new(serial_num, note_script, inputs); + let note = Note::new(burn_note_assets, burn_note_metadata, burn_note_recipient); + + builder.add_output_note(OutputNote::Full(note.clone())); + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + // Check the initial token issuance before burning + let initial_issuance = faucet.get_token_issuance().unwrap(); + assert_eq!(initial_issuance, Felt::new(100)); + + // EXECUTE BURN NOTE AGAINST NETWORK FAUCET + // -------------------------------------------------------------------------------------------- + let tx_context = mock_chain.build_tx_context(faucet.id(), &[note.id()], &[])?.build()?; + let executed_transaction = tx_context.execute().await?; + + // Check that the burn was successful - no output notes should be created for burn + assert_eq!(executed_transaction.output_notes().num_notes(), 0); + + // Verify the transaction was executed successfully + assert_eq!(executed_transaction.account_delta().nonce_delta(), Felt::new(1)); + assert_eq!(executed_transaction.input_notes().get_note(0).id(), note.id()); + + // Apply the delta to the faucet account and verify the token issuance decreased + faucet.apply_delta(executed_transaction.account_delta())?; + let final_issuance = faucet.get_token_issuance().unwrap(); + assert_eq!(final_issuance, Felt::new(initial_issuance.as_int() - burn_amount)); + + Ok(()) +} diff --git a/crates/miden-testing/tests/scripts/send_note.rs b/crates/miden-testing/tests/scripts/send_note.rs index 69158e1156..e18f44942d 100644 --- a/crates/miden-testing/tests/scripts/send_note.rs +++ b/crates/miden-testing/tests/scripts/send_note.rs @@ -92,7 +92,7 @@ async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { async fn test_send_note_script_basic_fungible_faucet() -> anyhow::Result<()> { let mut builder = MockChain::builder(); let sender_basic_fungible_faucet_account = - builder.add_existing_faucet(Auth::BasicAuth, "POL", 200, None)?; + builder.add_existing_basic_faucet(Auth::BasicAuth, "POL", 200, None)?; let mock_chain = builder.build()?; let sender_account_interface = AccountInterface::from(&sender_basic_fungible_faucet_account); From a9177f92928c5aa3208531796424d1a22c307a1a Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 30 Oct 2025 19:18:46 +0800 Subject: [PATCH 112/133] feat: represent new accounts as `AccountDelta`s (#1896) * feat: Prepare for initializing empty slots * feat: Add account code tracking in delta for new accounts * feat: Implement delta <-> account conversion * feat: Set initial slots as empty in kernel * feat: Implement delta modification during prologue * feat: Return error for account -> delta with seed * chore: Improve account delta to account conversion * chore: Document slot_type * chore: add changelog * fix: typos and doc links * feat: Insert storage map entries into delta * chore: Test storage values on new account * chore: assert proven tx delta is present * chore: document `add_storage_map_entries_to_delta` * chore: remove TODO * chore: Set initial values as empty in storage delta tracker * fix: slot index increment * chore: Roll back account update details changes for priv accounts * fix: should loop flag computation * fix: only pre-insert entries when account is new * chore: remove duplicate account delta check * fix: typo * feat: Do not set empty initial slots * Revert "feat: Do not set empty initial slots" This reverts commit 893ef918f62dcfe7a77a1a9549d86db44977fa3b. * Reapply "feat: Do not set empty initial slots" This reverts commit ca2e9b93c60e795d28965133e56b9007d0c3d05b. * feat: Implement `account_delta::insert_new_account` * feat: Use `cdropw` in delta commitment computation * chore: Update docs of insert_new_account * fix: typo * chore: Remove duplicate changelog entries * chore: Use named TODOs * feat: Improve delta docs * feat: Move tx account update validation to that type * feat: Inline validation into `TxAccountUpdate::new` * fix: noop tx tests including empty transactions * feat: Validate account delta commitment in proven transaction * chore: Cleanup tests * chore: Rename `init_slot_ptr` to `slot_ptr` * fix: duplicating remaining entries instead of slot idx * chore: Stronger doc wording * chore: Better wording for `TryFrom<&AccountDelta> for Account` * chore: Add is_full_state error condition to docs * chore: Make merging full state deltas an error * chore: Introduce helpfer function for empty header creation * chore: Rename mem_copy_native_account_initial_storage_slots * feat: Validate that advice-provided entries match map root exactly * chore: Remove TODO that was converted to issue * chore: Remove unused get_map_item_raw_unchecked * chore: expand comment in delta * chore: Move `impl TryFrom for AccountDelta` behind testing * feat: Move validation code into `insert_and_validate_storage_map` --- CHANGELOG.md | 5 +- .../asm/kernels/transaction/api.masm | 9 + .../asm/kernels/transaction/lib/account.masm | 147 +++++++++- .../transaction/lib/account_delta.masm | 16 +- .../asm/kernels/transaction/lib/memory.masm | 2 +- .../asm/kernels/transaction/lib/prologue.masm | 13 + crates/miden-lib/asm/miden/account.masm | 19 ++ .../miden-lib/src/errors/tx_kernel_errors.rs | 2 + crates/miden-lib/src/transaction/inputs.rs | 13 + .../src/transaction/kernel_procedures.rs | 4 +- crates/miden-objects/src/account/delta/mod.rs | 199 +++++++++---- .../src/account/delta/storage.rs | 13 +- crates/miden-objects/src/account/mod.rs | 87 +++++- .../miden-objects/src/account/storage/mod.rs | 7 +- crates/miden-objects/src/errors.rs | 18 +- .../src/testing/block_note_tree.rs | 5 +- .../src/transaction/proven_tx.rs | 266 ++++++++++-------- .../src/kernel_tests/batch/proposed_batch.rs | 8 +- .../src/kernel_tests/tx/test_account.rs | 72 ++++- crates/miden-testing/src/mock_chain/chain.rs | 25 +- .../src/mock_chain/chain_builder.rs | 13 +- crates/miden-testing/tests/scripts/faucet.rs | 12 +- .../src/host/account_delta_tracker.rs | 23 +- crates/miden-tx/src/host/mod.rs | 5 +- .../src/host/storage_delta_tracker.rs | 70 ++++- crates/miden-tx/src/prover/mod.rs | 76 +---- 26 files changed, 831 insertions(+), 298 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3fb15cb90..7aa7994a6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,11 @@ - Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). - [BREAKING] Move account seed into `PartialAccount` ([#1875](https://github.com/0xMiden/miden-base/pull/1875), [#2003](https://github.com/0xMiden/miden-base/pull/2003)). - [BREAKING] Enabled lazy loading of assets and storage map items for foreign accounts during transaction execution ([#1888](https://github.com/0xMiden/miden-base/pull/1888)). +- Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) +- [BREAKING] Represent new accounts as account deltas ([#1896](https://github.com/0xMiden/miden-base/pull/1896)). +- Implement `SlotName` for named storage slots ([#1932](https://github.com/0xMiden/miden-base/issues/1932)) - Added `get_initial_item` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). -- Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)). - Added `update_signers_and_threshold` procedure to update owner public keys and threshold config in multisig authentication component ([#1707](https://github.com/0xMiden/miden-base/issues/1707)). -- Implement `SlotName` for named storage slots ([#1932](https://github.com/0xMiden/miden-base/issues/1932)). - [BREAKING] Removed `get_falcon_signature` from `miden-tx` crate ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Created a `Signature` wrapper to simplify the preparation of "native" signatures for use in the VM ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Added per-procedure approval thresholds to `AuthRpoFalcon512Multisig` auth component ([#1968](https://github.com/0xMiden/miden-base/pull/1968)). diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 4960df09bc..c1cb411809 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -128,6 +128,15 @@ end #! Computes the commitment to the native account's delta. #! +#! The commitment to an empty delta is defined as the empty word. +#! +#! During an account-creating transaction (when the initial nonce is 0), this procedure may not +#! return the empty word even if the initial storage commitment and the current storage commitment +#! are identical (storage hasn't changed). This is because the delta for a new account must +#! represent its entire newly created state, and the initial storage in a transaction is +#! initialized to the the storage that the account ID commits to, which may be non-empty. This +#! does not have any consequences other than being inconsistent in this edge case. +#! #! Inputs: [pad(16)] #! Outputs: [DELTA_COMMITMENT, pad(12)] #! diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 8e10d51967..848df57b27 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -42,6 +42,8 @@ const.ERR_ACCOUNT_TOO_MANY_STORAGE_SLOTS="number of account storage slots exceed const.ERR_ACCOUNT_STORAGE_COMMITMENT_MISMATCH="computed account storage commitment does not match recorded account storage commitment" +const.ERR_ACCOUNT_STORAGE_MAP_ENTRIES_DO_NOT_MATCH_MAP_ROOT="storage map entries provided as advice inputs do not have the same storage map root as the root of the map the new account commits to" + const.ERR_ACCOUNT_INVALID_STORAGE_OFFSET_FOR_SIZE="storage size can only be zero if storage offset is also zero" const.ERR_FOREIGN_ACCOUNT_ID_IS_ZERO="ID of the provided foreign account equals zero" @@ -1186,11 +1188,6 @@ export.save_account_storage_data # verify hashed account storage slots match account storage commitment assert_eqw.err=ERR_ACCOUNT_STORAGE_COMMITMENT_MISMATCH # OS => [] - - # duplicate the initial storage slots in memory to enable a diff computation - # for the account delta - exec.memory::mem_copy_initial_storage_slots - # OS => [] end #! Saves account procedure data into memory and validates that the code commitment matches the @@ -1263,6 +1260,142 @@ export.save_account_procedure_data # OS => [] end +#! Writes the initial storage values from the advice inputs into the account delta and validates +#! that they match what the account commits to. +#! +#! This is only relevant for new accounts and only does actual work for storage maps, since +#! value slots aren't explicitly tracked by the delta. +#! +#! Inputs: [] +#! Outputs: [] +export.insert_new_storage + exec.memory::get_num_storage_slots + # => [num_slots] + + # loop if there are storage slots + dup neq.0 + # => [should_loop, num_slots] + + while.true + sub.1 + # => [slot_idx] + + dup exec.get_storage_slot_type + # => [slot_type, slot_idx] + + exec.constants::get_storage_slot_type_map eq + # => [is_map_slot_type, slot_idx] + + if.true + # add map entries to account storage + dup exec.insert_and_validate_storage_map + # => [slot_idx] + end + # => [slot_idx] + + dup neq.0 + # => [should_continue, slot_idx] + end + # => [slot_idx] + + drop + # => [] +end + +#! Inserts the entries of the provided storage map root into the account. +#! +#! These entries must be present in the advice provider with the map root as the key. Each entry is +#! is inserted using set_map_item on an initially empty SMT root. This does two important things: +#! - It allows checking whether the root of the SMT with all entries inserted matches the root of +#! the map the account commits to. +#! - It inserts all entries into the in-kernel delta, where the initial value of each entry will +#! be set to the empty word so the delta for this map is computed as if the map had initially +#! been empty. +#! +#! Inputs: +#! Operand stack: [slot_idx] +#! Advice map: { MAP_ROOT: [MAP_ENTRIES] } +#! Outputs: [] +proc.insert_and_validate_storage_map + dup exec.memory::get_account_storage_slots_section_ptr + # => [storage_slots_ptr, slot_idx, slot_idx] + + exec.get_item_raw + # => [MAP_ROOT, slot_idx] + + # overwrite the map root with the root of an empty SMT, so we can insert the entries of + # the map into an empty map and then check whether the resulting root matches MAP_ROOT. + exec.constants::get_empty_smt_root + dup.8 + # => [slot_idx, EMPTY_SMT_ROOT, MAP_ROOT, slot_idx] + + exec.set_item_raw dropw + # => [MAP_ROOT, slot_idx] + + adv.push_mapvaln + # OS => [MAP_ROOT, slot_idx] + # AS => [num_elements, [MAP_ENTRIES]] + + movup.4 + # OS => [slot_idx, MAP_ROOT] + # AS => [num_elements, [MAP_ENTRIES]] + + adv_push.1 + # OS => [num_elements, slot_idx, MAP_ROOT] + # AS => [[MAP_ENTRIES]] + + push.8 u32assert2.err="number of storage map elements should fit into a u32" + # OS => [8, num_elements, slot_idx, MAP_ROOT] + # AS => [[MAP_ENTRIES]] + + # check that num_elements % 8 = 0 so we can use an equality check for the loop condition + # this also computes number_entries which is num_elements / 8. + u32divmod eq.0 assert.err="number of storage map elements must be a multiple of 8" + # OS => [num_entries, slot_idx, MAP_ROOT] + # AS => [[MAP_ENTRIES]] + + # loop if there are more than 0 storage map elements + dup neq.0 + # OS => [should_loop, num_entries, slot_idx, MAP_ROOT] + # AS => [[MAP_ENTRIES]] + + while.true + sub.1 + # => [remaining_entries, slot_idx, MAP_ROOT] + + # push a key-value pair (8 felts) to the operand stack + adv_push.8 + # => [KEY, VALUE, remaining_entries, slot_idx, MAP_ROOT] + + dup.9 + # => [slot_idx, KEY, VALUE, remaining_entries, slot_idx, MAP_ROOT] + + # insert the key-value pair into account storage + # this could be optimized to avoid the map slot type assertion and reading and writing the + # root on every call + exec.set_map_item dropw dropw + # => [remaining_entries, slot_idx, MAP_ROOT] + + dup neq.0 + # => [should_continue, remaining_entries, slot_idx, MAP_ROOT] + end + # OS => [remaining_entries, slot_idx, MAP_ROOT] + # AS => [] + + drop + # => [slot_idx, MAP_ROOT] + + # load the root after all entries have been inserted + exec.memory::get_account_storage_slots_section_ptr + exec.get_item_raw + # => [CURRENT_MAP_ROOT, MAP_ROOT] + + # after inserting all entries, the storage map root must match the map root that was committed + # to as part of account creation + assert_eqw.err=ERR_ACCOUNT_STORAGE_MAP_ENTRIES_DO_NOT_MATCH_MAP_ROOT + # => [] +end + # HELPER PROCEDURES # ================================================================================================= @@ -1284,7 +1417,9 @@ export.get_item_raw # => [VALUE] end -#! Shared procedure for getting a map item from a storage slot. +#! Shared procedure for getting a map item from a storage slot without checking the index. +#! +#! WARNING: Must be called with an index that is in bounds. #! #! Inputs: [KEY, ROOT, index] #! Outputs: [VALUE] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm index 3eae11c8bf..43f75f3657 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm @@ -176,8 +176,22 @@ proc.update_value_slot_delta dup.4 exec.account::get_initial_item # => [INIT_VALUE, CURRENT_VALUE, slot_idx, RATE, RATE, PERM] + # if account is new, replace INIT_VALUE with EMPTY_WORD + # we need to do this specifically for new accounts because for those, get_initial_item returns + # the same as get_item but we want the delta for value slots to be from an empty value to the + # final value + # use get_init_nonce so the delta is still correctly computed when the nonce has already been + # incremented + padw exec.memory::get_init_nonce eq.0 + # => [is_account_new, EMPTY_WORD, INIT_VALUE, CURRENT_VALUE, slot_idx, RATE, RATE, PERM] + + # If is_account_new EMPTY_WORD remains. + # If !is_account_new INIT_VALUE remains. + cdropw + # => [INIT_VALUE', CURRENT_VALUE, slot_idx, RATE, RATE, PERM] + exec.word::test_eq not - # => [was_changed, INIT_VALUE, CURRENT_VALUE, slot_idx, RATE, RATE, PERM] + # => [was_changed, INIT_VALUE', CURRENT_VALUE, slot_idx, RATE, RATE, PERM] # only include in delta if the slot's value has changed if.true diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index b599dbad47..8ad5007566 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -1466,7 +1466,7 @@ end #! #! Inputs: [] #! Outputs: [] -export.mem_copy_initial_storage_slots +export.mem_copy_native_account_initial_storage_slots exec.get_native_account_initial_storage_slots_ptr exec.get_native_account_storage_slots_ptr # => [storage_slots_section_ptr, initial_storage_slots_ptr] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm index 7ae0e3ecb5..6e7fdbfd85 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -4,6 +4,7 @@ use.std::crypto::hashes::rpo use.std::word use.$kernel::account +use.$kernel::account_delta use.$kernel::account_id use.$kernel::asset_vault use.$kernel::constants @@ -444,6 +445,11 @@ proc.process_account_data exec.account::save_account_procedure_data # => [ACCOUNT_COMMITMENT] + # duplicate the initial storage slots in memory to enable a diff computation + # for the account delta + exec.memory::mem_copy_native_account_initial_storage_slots + # => [ACCOUNT_COMMITMENT] + # copy the initial account vault root to the input vault root to support transaction asset # invariant checking # this account vault root is also stored as an initial one in the global inputs @@ -465,6 +471,13 @@ proc.process_account_data # process conditional logic depending on whether the account is new or existing if.true + # insert the initial storage values of the new account into the delta + # this is required only for new accounts because the delta at the end of an + # account-creating transaction needs to represent the entire account, while + # this is not a requirement for existing accounts + exec.account::insert_new_storage + # => [ACCOUNT_COMMITMENT] + # set the initial account commitment exec.memory::set_init_account_commitment dropw # => [] diff --git a/crates/miden-lib/asm/miden/account.masm b/crates/miden-lib/asm/miden/account.masm index 6a573cbaf0..3b4a74c08e 100644 --- a/crates/miden-lib/asm/miden/account.masm +++ b/crates/miden-lib/asm/miden/account.masm @@ -208,6 +208,15 @@ end #! is called, otherwise it will panic. This means it can only be called from an auth procedure, #! since only auth procedures are allowed to increment the nonce. #! +#! The commitment to an empty delta is defined as the empty word. +#! +#! During an account-creating transaction (when the initial nonce is 0), this procedure will not +#! return the empty word even if the initial storage commitment and the current storage commitment +#! are identical (storage hasn't changed). This is because the delta for a new account must +#! represent its entire newly created state, and the initial storage in a transaction is +#! initialized to the the storage that the account ID commits to, which may be non-empty. This +#! does not have any consequences other than being inconsistent in this edge case. +#! #! Inputs: [] #! Outputs: [DELTA_COMMITMENT] #! @@ -454,6 +463,11 @@ end #! Returns the storage commitment of the native account at the beginning of the transaction. #! +#! During an account-creating transaction (when the initial nonce is 0), this procedure and +#! compute_storage_commitment will return the same value at the beginning of the transaction +#! (before any note or transaction scripts were executed). Despite that, the account delta may +#! not be empty. See account::compute_delta_commitment for more. +#! #! Inputs: [] #! Outputs: [INIT_STORAGE_COMMITMENT] #! @@ -483,6 +497,11 @@ end #! recompute it: recomputation is performed only if the account's storage has been changed, #! otherwise the cached value is returned. #! +#! During an account-creating transaction (when the initial nonce is 0), this procedure and +#! get_initial_storage_commitment will return the same value at the beginning of the transaction +#! (before any note or transaction scripts were executed). Despite that, the account delta may +#! not be empty. See account::compute_delta_commitment for more. +#! #! Inputs: [] #! Outputs: [STORAGE_COMMITMENT] #! diff --git a/crates/miden-lib/src/errors/tx_kernel_errors.rs b/crates/miden-lib/src/errors/tx_kernel_errors.rs index 290947b265..4b0778c4e0 100644 --- a/crates/miden-lib/src/errors/tx_kernel_errors.rs +++ b/crates/miden-lib/src/errors/tx_kernel_errors.rs @@ -54,6 +54,8 @@ pub const ERR_ACCOUNT_STACK_OVERFLOW: MasmError = MasmError::from_static_str("de pub const ERR_ACCOUNT_STACK_UNDERFLOW: MasmError = MasmError::from_static_str("failed to end foreign context because the current account is the native account"); /// Error Message: "computed account storage commitment does not match recorded account storage commitment" pub const ERR_ACCOUNT_STORAGE_COMMITMENT_MISMATCH: MasmError = MasmError::from_static_str("computed account storage commitment does not match recorded account storage commitment"); +/// Error Message: "storage map entries provided as advice inputs do not have the same storage map root as the root of the map the new account commits to" +pub const ERR_ACCOUNT_STORAGE_MAP_ENTRIES_DO_NOT_MATCH_MAP_ROOT: MasmError = MasmError::from_static_str("storage map entries provided as advice inputs do not have the same storage map root as the root of the map the new account commits to"); /// Error Message: "provided storage slot index is out of bounds" pub const ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS: MasmError = MasmError::from_static_str("provided storage slot index is out of bounds"); /// Error Message: "number of account procedures exceeds the maximum limit of 256" diff --git a/crates/miden-lib/src/transaction/inputs.rs b/crates/miden-lib/src/transaction/inputs.rs index 1486205eac..cb0237dd47 100644 --- a/crates/miden-lib/src/transaction/inputs.rs +++ b/crates/miden-lib/src/transaction/inputs.rs @@ -55,6 +55,19 @@ impl TransactionAdviceInputs { inputs.add_map_entry(account_id_key, seed.to_vec()); } + // if the account is new, insert the storage map entries into the advice provider. + if partial_native_acc.is_new() { + for storage_map in partial_native_acc.storage().maps() { + let map_entries = storage_map + .entries() + .flat_map(|(key, value)| { + value.as_elements().iter().chain(key.as_elements().iter()).copied() + }) + .collect(); + inputs.add_map_entry(storage_map.root(), map_entries); + } + } + // Extend with extra user-supplied advice. inputs.extend(tx_inputs.tx_args().advice_inputs().clone()); diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index a840ba6e7c..fddf09b3d4 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -54,7 +54,7 @@ pub const KERNEL_PROCEDURES: [Word; 54] = [ // account_has_non_fungible_asset word!("0xf975c799cffebf8565a8479475fe04c1832ef2a7484c4a2a42bbf7d8a340d649"), // account_compute_delta_commitment - word!("0x57165e9bc287a6f7b96be5f20a3f6752129afc756f1a6ab6c55959f7e436d0e5"), + word!("0xf92256166420f15937d4e5b358cef0affae1d658140ae2bfa4f50a050eac5af3"), // account_get_num_procedures word!("0x53b5ec38b7841948762c258010e6e07ad93963bcaac2d83813f8edb6710dc720"), // account_get_procedure_root @@ -108,7 +108,7 @@ pub const KERNEL_PROCEDURES: [Word; 54] = [ // tx_get_block_timestamp word!("0x7903185b847517debb6c2072364e3e757b99ee623e97c2bd0a4661316c5c5418"), // tx_start_foreign_context - word!("0xabd1952a963dac6441e309628fd375424c6d997a91e667fd7dceb7b20eaca40e"), + word!("0x17bd522cf79f5cb2b09362d328c517c25efab6f1b778cfc566eb7c732622ccaf"), // tx_end_foreign_context word!("0xaa0018aa8da890b73511879487f65553753fb7df22de380dd84c11e6f77eec6f"), // tx_get_expiration_delta diff --git a/crates/miden-objects/src/account/delta/mod.rs b/crates/miden-objects/src/account/delta/mod.rs index 9a4088f39c..df1e245791 100644 --- a/crates/miden-objects/src/account/delta/mod.rs +++ b/crates/miden-objects/src/account/delta/mod.rs @@ -1,10 +1,18 @@ use alloc::string::ToString; use alloc::vec::Vec; -use crate::account::{Account, AccountId}; +use crate::account::{ + Account, + AccountCode, + AccountId, + AccountStorage, + StorageSlot, + StorageSlotType, +}; +use crate::asset::AssetVault; use crate::crypto::SequentialCommit; use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; -use crate::{AccountDeltaError, Felt, Word, ZERO}; +use crate::{AccountDeltaError, AccountError, Felt, Word, ZERO}; mod storage; pub use storage::{AccountStorageDelta, StorageMapDelta}; @@ -24,12 +32,19 @@ pub use vault::{ /// one or more transaction. /// /// The differences are represented as follows: -/// - storage: an [AccountStorageDelta] that contains the changes to the account storage. -/// - vault: an [AccountVaultDelta] object that contains the changes to the account vault. +/// - storage: an [`AccountStorageDelta`] that contains the changes to the account storage. +/// - vault: an [`AccountVaultDelta`] object that contains the changes to the account vault. /// - nonce: if the nonce of the account has changed, the _delta_ of the nonce is stored, i.e. the /// value by which the nonce increased. +/// - code: an [`AccountCode`] for new accounts and `None` for others. +/// +/// The presence of the code in a delta signals if the delta is a _full state_ or _partial state_ +/// delta. A full state delta must be converted into an [`Account`] object, while a partial state +/// delta must be applied to an existing [`Account`]. /// -/// TODO: add ability to trace account code updates. +/// TODO(code_upgrades): The ability to track account code updates is an outstanding feature. For +/// that reason, the account code is not considered as part of the "nonce must be incremented if +/// state changed" check. #[derive(Clone, Debug, PartialEq, Eq)] pub struct AccountDelta { /// The ID of the account to which this delta applies. If the delta is created during @@ -39,6 +54,8 @@ pub struct AccountDelta { storage: AccountStorageDelta, /// The delta of the account's asset vault. vault: AccountVaultDelta, + /// The code of a new account (`Some`) or `None` for existing accounts. + code: Option, /// The value by which the nonce was incremented. Must be greater than zero if storage or vault /// are non-empty. nonce_delta: Felt, @@ -47,12 +64,12 @@ pub struct AccountDelta { impl AccountDelta { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- + /// Returns new [AccountDelta] instantiated from the provided components. /// /// # Errors /// - /// - Returns an error if storage or vault were updated, but the nonce was either not updated or - /// set to 0. + /// - Returns an error if storage or vault were updated, but the nonce_delta is 0. pub fn new( account_id: AccountId, storage: AccountStorageDelta, @@ -62,7 +79,13 @@ impl AccountDelta { // nonce must be updated if either account storage or vault were updated validate_nonce(nonce_delta, &storage, &vault)?; - Ok(Self { account_id, storage, vault, nonce_delta }) + Ok(Self { + account_id, + storage, + vault, + code: None, + nonce_delta, + }) } // PUBLIC MUTATORS @@ -80,6 +103,17 @@ impl AccountDelta { }); } + // TODO(code_upgrades): This should go away once we have proper account code updates in + // deltas. Then, the two code updates can be merged. For now, code cannot be merged + // and this should never happen. + if self.is_full_state() && other.is_full_state() { + return Err(AccountDeltaError::MergingFullStateDeltas); + } + + if let Some(code) = other.code { + self.code = Some(code); + } + self.nonce_delta = new_nonce_delta; self.storage.merge(other.storage)?; @@ -91,6 +125,12 @@ impl AccountDelta { &mut self.vault } + /// Sets the [`AccountCode`] of the delta. + pub fn with_code(mut self, code: Option) -> Self { + self.code = code; + self + } + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -99,6 +139,17 @@ impl AccountDelta { self.storage.is_empty() && self.vault.is_empty() && self.nonce_delta == ZERO } + /// Returns `true` if this delta is a "full state" delta, `false` otherwise, i.e. if it is a + /// "partial state" delta. + /// + /// See the type-level docs for more on this distinction. + pub fn is_full_state(&self) -> bool { + // TODO(code_upgrades): Change this to another detection mechanism once we have code upgrade + // support, at which point the presence of code may not be enough of an indication + // that a delta can be converted to a full account. + self.code.is_some() + } + /// Returns storage updates for this account delta. pub fn storage(&self) -> &AccountStorageDelta { &self.storage @@ -119,16 +170,23 @@ impl AccountDelta { self.account_id } + /// Returns a reference to the account code of this delta, if present. + pub fn code(&self) -> Option<&AccountCode> { + self.code.as_ref() + } + /// Converts this storage delta into individual delta components. - pub fn into_parts(self) -> (AccountStorageDelta, AccountVaultDelta, Felt) { - (self.storage, self.vault, self.nonce_delta) + pub fn into_parts(self) -> (AccountStorageDelta, AccountVaultDelta, Option, Felt) { + (self.storage, self.vault, self.code, self.nonce_delta) } /// Computes the commitment to the account delta. /// + /// # Computation + /// /// The delta is a sequential hash over a vector of field elements which starts out empty and - /// is appended to in the following way. Whenever sorting is expected, it is that of a link map - /// key. The WORD layout is in memory-order. + /// is appended to in the following way. Whenever sorting is expected, it is that of a + /// [`LexicographicWord`](crate::LexicographicWord). The WORD layout is in memory-order. /// /// - Append `[[nonce_delta, 0, account_id_suffix, account_id_prefix], EMPTY_WORD]`, where /// account_id_{prefix,suffix} are the prefix and suffix felts of the native account id and @@ -169,12 +227,12 @@ impl AccountDelta { /// /// # Security /// - /// The general concern with the commitment is that two deltas must never has to the same - /// commitment. E.g. a commitment of a delta that changes a key-value pair in a storage map - /// slot should be different from a delta that adds a non-fungible asset to the vault. If - /// not, a delta can be crafted in the VM that sets a map key but a malicious actor crafts a - /// delta outside the VM that adds a non-fungible asset. To prevent that, a couple of - /// measures are taken. + /// The general concern with the commitment is that two distinct deltas must never hash to the + /// same commitment. E.g. a commitment of a delta that changes a key-value pair in a storage + /// map slot should be different from a delta that adds a non-fungible asset to the vault. + /// If not, a delta can be crafted in the VM that sets a map key but a malicious actor + /// crafts a delta outside the VM that adds a non-fungible asset. To prevent that, a couple + /// of measures are taken. /// /// - Because multiple unrelated contexts (e.g. vaults and storage slots) are hashed in the same /// hasher, domain separators are used to disambiguate. For each changed asset and each @@ -255,6 +313,57 @@ impl AccountDelta { } } +impl TryFrom<&AccountDelta> for Account { + type Error = AccountError; + + /// Converts an [`AccountDelta`] into an [`Account`]. + /// + /// Conceptually, this applies the delta onto an empty account. + /// + /// # Errors + /// + /// Returns an error if: + /// - If the delta is not a full state delta. See [`AccountDelta`] for details. + /// - If any vault delta operation removes an asset. + /// - If any vault delta operation adds an asset that would overflow the maximum representable + /// amount. + /// - If any storage delta update violates account storage constraints. + fn try_from(delta: &AccountDelta) -> Result { + if !delta.is_full_state() { + return Err(AccountError::PartialStateDeltaToAccount); + } + + let Some(code) = delta.code().cloned() else { + return Err(AccountError::PartialStateDeltaToAccount); + }; + + let mut vault = AssetVault::default(); + vault.apply_delta(delta.vault()).map_err(AccountError::AssetVaultUpdateError)?; + + // Once we support addition and removal of storage slots, we may be able to change + // this to create an empty account and use `Account::apply_delta` instead. + // For now, we need to create the initial storage of the account with the same slot types. + let mut empty_storage_slots = Vec::new(); + for slot_idx in 0..u8::MAX { + let slot = match delta.storage().slot_type(slot_idx) { + Some(StorageSlotType::Value) => StorageSlot::empty_value(), + Some(StorageSlotType::Map) => StorageSlot::empty_map(), + None => break, + }; + empty_storage_slots.push(slot); + } + let mut storage = AccountStorage::new(empty_storage_slots) + .expect("storage delta should contain a valid number of slots"); + storage.apply_delta(delta.storage())?; + + // The nonce of the account is the initial nonce of 0 plus the nonce_delta, so the + // nonce_delta itself. + let nonce = delta.nonce_delta(); + + Account::new(delta.id(), vault, storage, code, nonce, None) + } +} + impl SequentialCommit for AccountDelta { type Commitment = Word; @@ -304,21 +413,18 @@ impl SequentialCommit for AccountDelta { /// In particular, private account changes aren't tracked at all; they are represented as /// [`AccountUpdateDetails::Private`]. /// -/// New non-private accounts are included in full and changes to a non-private account are tracked -/// as an [`AccountDelta`]. +/// Non-private accounts are tracked as an [`AccountDelta`]. If the account is new, the delta can be +/// converted into an [`Account`]. If not, the delta can be applied to the existing account using +/// [`Account::apply_delta`]. /// /// Note that these details can represent the changes from one or more transactions in which case -/// the delta is either applied to the new account or deltas are merged together using -/// [`AccountDelta::merge`]. +/// the deltas of each transaction are merged together using [`AccountDelta::merge`]. #[derive(Clone, Debug, PartialEq, Eq)] pub enum AccountUpdateDetails { - /// Account is private (no on-chain state change). + /// The state update details of a private account is not publicly accessible. Private, - /// The whole state is needed for new accounts. - New(Account), - - /// For existing accounts, only the delta is needed. + /// The state update details of non-private accounts. Delta(AccountDelta), } @@ -336,16 +442,6 @@ impl AccountUpdateDetails { (AccountUpdateDetails::Private, AccountUpdateDetails::Private) => { AccountUpdateDetails::Private }, - (AccountUpdateDetails::New(mut account), AccountUpdateDetails::Delta(delta)) => { - account.apply_delta(&delta).map_err(|err| { - AccountDeltaError::AccountDeltaApplicationFailed { - account_id: account.id(), - source: err, - } - })?; - - AccountUpdateDetails::New(account) - }, (AccountUpdateDetails::Delta(mut delta), AccountUpdateDetails::Delta(new_delta)) => { delta.merge(new_delta)?; AccountUpdateDetails::Delta(delta) @@ -365,7 +461,6 @@ impl AccountUpdateDetails { pub(crate) const fn as_tag_str(&self) -> &'static str { match self { AccountUpdateDetails::Private => "private", - AccountUpdateDetails::New(_) => "new", AccountUpdateDetails::Delta(_) => "delta", } } @@ -379,6 +474,7 @@ impl Serializable for AccountDelta { self.account_id.write_into(target); self.storage.write_into(target); self.vault.write_into(target); + self.code.write_into(target); self.nonce_delta.write_into(target); } @@ -386,6 +482,7 @@ impl Serializable for AccountDelta { self.account_id.get_size_hint() + self.storage.get_size_hint() + self.vault.get_size_hint() + + self.code.get_size_hint() + self.nonce_delta.get_size_hint() } } @@ -395,12 +492,19 @@ impl Deserializable for AccountDelta { let account_id = AccountId::read_from(source)?; let storage = AccountStorageDelta::read_from(source)?; let vault = AccountVaultDelta::read_from(source)?; + let code = >::read_from(source)?; let nonce_delta = Felt::read_from(source)?; validate_nonce(nonce_delta, &storage, &vault) .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; - Ok(Self { account_id, storage, vault, nonce_delta }) + Ok(Self { + account_id, + storage, + vault, + code, + nonce_delta, + }) } } @@ -410,12 +514,8 @@ impl Serializable for AccountUpdateDetails { AccountUpdateDetails::Private => { 0_u8.write_into(target); }, - AccountUpdateDetails::New(account) => { - 1_u8.write_into(target); - account.write_into(target); - }, AccountUpdateDetails::Delta(delta) => { - 2_u8.write_into(target); + 1_u8.write_into(target); delta.write_into(target); }, } @@ -427,7 +527,6 @@ impl Serializable for AccountUpdateDetails { match self { AccountUpdateDetails::Private => u8_size, - AccountUpdateDetails::New(account) => u8_size + account.get_size_hint(), AccountUpdateDetails::Delta(account_delta) => u8_size + account_delta.get_size_hint(), } } @@ -437,10 +536,9 @@ impl Deserializable for AccountUpdateDetails { fn read_from(source: &mut R) -> Result { match u8::read_from(source)? { 0 => Ok(Self::Private), - 1 => Ok(Self::New(Account::read_from(source)?)), - 2 => Ok(Self::Delta(AccountDelta::read_from(source)?)), - v => Err(DeserializationError::InvalidValue(format!( - "Unknown variant {v} for AccountDetails" + 1 => Ok(Self::Delta(AccountDelta::read_from(source)?)), + variant => Err(DeserializationError::InvalidValue(format!( + "Unknown variant {variant} for AccountDetails" ))), } } @@ -632,8 +730,5 @@ mod tests { let update_details_delta = AccountUpdateDetails::Delta(account_delta); assert_eq!(update_details_delta.to_bytes().len(), update_details_delta.get_size_hint()); - - let update_details_new = AccountUpdateDetails::New(account); - assert_eq!(update_details_new.to_bytes().len(), update_details_new.get_size_hint()); } } diff --git a/crates/miden-objects/src/account/delta/storage.rs b/crates/miden-objects/src/account/delta/storage.rs index ef271c97dd..17779a6441 100644 --- a/crates/miden-objects/src/account/delta/storage.rs +++ b/crates/miden-objects/src/account/delta/storage.rs @@ -12,7 +12,7 @@ use super::{ Serializable, Word, }; -use crate::account::StorageMap; +use crate::account::{StorageMap, StorageSlotType}; use crate::{EMPTY_WORD, Felt, LexicographicWord, ZERO}; // ACCOUNT STORAGE DELTA @@ -59,6 +59,17 @@ impl AccountStorageDelta { Ok(delta) } + /// Returns the slot type of the provided slot index or `None` if no such slot exists. + pub(crate) fn slot_type(&self, slot_index: u8) -> Option { + if self.values().contains_key(&slot_index) { + Some(StorageSlotType::Value) + } else if self.maps().contains_key(&slot_index) { + Some(StorageSlotType::Map) + } else { + None + } + } + /// Returns a reference to the updated values in this storage delta. pub fn values(&self) -> &BTreeMap { &self.values diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index 4341e19ce2..f600a3f221 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -1,6 +1,9 @@ +use alloc::collections::BTreeMap; use alloc::string::ToString; -use crate::asset::AssetVault; +use miden_core::LexicographicWord; + +use crate::asset::{Asset, AssetVault}; use crate::utils::serde::{ ByteReader, ByteWriter, @@ -343,12 +346,19 @@ impl Account { /// to the values specified by the delta. /// /// # Errors + /// /// Returns an error if: + /// - [`AccountDelta::is_full_state`] returns `true`, i.e. represents the state of an entire + /// account. Only partial state deltas can be applied to an account. /// - Applying vault sub-delta to the vault of this account fails. /// - Applying storage sub-delta to the storage of this account fails. /// - The nonce specified in the provided delta smaller than or equal to the current account /// nonce. pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), AccountError> { + if delta.is_full_state() { + return Err(AccountError::ApplyFullStateDeltaToAccount); + } + // update vault; we don't check vault delta validity here because `AccountDelta` can contain // only valid vault deltas self.vault @@ -410,6 +420,81 @@ impl Account { } } +#[cfg(any(test, feature = "testing"))] +impl TryFrom for AccountDelta { + type Error = AccountError; + + /// Converts an [`Account`] into an [`AccountDelta`]. + /// + /// # Errors + /// + /// Returns an error if: + /// - the account has a seed. Accounts with seeds have a nonce of 0. Representing such accounts + /// as deltas is not possible because deltas with a non-empty state change need a nonce_delta + /// greater than 0. + fn try_from(account: Account) -> Result { + let Account { id, vault, storage, code, nonce, seed } = account; + + if seed.is_some() { + return Err(AccountError::DeltaFromAccountWithSeed); + } + + let mut value_slots = BTreeMap::new(); + let mut map_slots = BTreeMap::new(); + + for (slot_idx, slot) in (0..u8::MAX).zip(storage.into_slots().into_iter()) { + match slot { + StorageSlot::Value(word) => { + value_slots.insert(slot_idx, word); + }, + StorageSlot::Map(storage_map) => { + let map_delta = StorageMapDelta::new( + storage_map + .into_entries() + .into_iter() + .map(|(key, value)| (LexicographicWord::from(key), value)) + .collect(), + ); + map_slots.insert(slot_idx, map_delta); + }, + } + } + let storage_delta = AccountStorageDelta::from_parts(value_slots, map_slots) + .expect("value and map slots from account storage should not overlap"); + + let mut fungible_delta = FungibleAssetDelta::default(); + let mut non_fungible_delta = NonFungibleAssetDelta::default(); + for asset in vault.assets() { + // SAFETY: All assets in the account vault should be representable in the delta. + match asset { + Asset::Fungible(fungible_asset) => { + fungible_delta + .add(fungible_asset) + .expect("delta should allow representing valid fungible assets"); + }, + Asset::NonFungible(non_fungible_asset) => { + non_fungible_delta + .add(non_fungible_asset) + .expect("delta should allow representing valid non-fungible assets"); + }, + } + } + let vault_delta = AccountVaultDelta::new(fungible_delta, non_fungible_delta); + + // The nonce of the account is the nonce delta since adding the nonce_delta to 0 would + // result in the nonce. + let nonce_delta = nonce; + + // SAFETY: As checked earlier, the nonce delta should be greater than 0 allowing for + // non-empty state changes. + let delta = AccountDelta::new(id, storage_delta, vault_delta, nonce_delta) + .expect("nonce_delta should be greater than 0") + .with_code(Some(code)); + + Ok(delta) + } +} + // SERIALIZATION // ================================================================================================ diff --git a/crates/miden-objects/src/account/storage/mod.rs b/crates/miden-objects/src/account/storage/mod.rs index c6e2584f80..168f12ee85 100644 --- a/crates/miden-objects/src/account/storage/mod.rs +++ b/crates/miden-objects/src/account/storage/mod.rs @@ -39,7 +39,7 @@ pub use partial::PartialStorage; /// - [StorageSlot::Map]: contains a [StorageMap] which is a key-value map where both keys and /// values are [Word]s. The value of a storage slot containing a map is the commitment to the /// underlying map. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct AccountStorage { slots: Vec, } @@ -116,6 +116,11 @@ impl AccountStorage { &self.slots } + /// Consumes self and returns the storage slots of the account storage. + pub fn into_slots(self) -> Vec { + self.slots + } + /// Returns an [AccountStorageHeader] for this account storage. pub fn to_header(&self) -> AccountStorageHeader { AccountStorageHeader::new( diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index 7bb5909813..e46cd46bfd 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -136,6 +136,10 @@ pub enum AccountError { ExistingAccountWithSeed, #[error("account ID seed was not provided for a new account")] NewAccountMissingSeed, + #[error( + "an account with a seed cannot be converted into a delta since it represents an unregistered account" + )] + DeltaFromAccountWithSeed, #[error("seed converts to an invalid account ID")] SeedConvertsToInvalidAccountId(#[source] AccountIdError), #[error("storage map root {0} not found in the account storage")] @@ -163,6 +167,12 @@ pub enum AccountError { account_type: AccountType, component_index: usize, }, + #[error( + "failed to apply full state delta to existing account; full state deltas can be converted to accounts directly" + )] + ApplyFullStateDeltaToAccount, + #[error("only account deltas representing a full account can be converted to a full account")] + PartialStateDeltaToAccount, #[error("maximum number of storage map leaves exceeded")] MaxNumStorageMapLeavesExceeded(#[source] MerkleError), /// This variant can be used by methods that are not inherent to the account but want to return @@ -352,6 +362,8 @@ pub enum AccountDeltaError { }, #[error("account ID {0} in fungible asset delta is not of type fungible faucet")] NotAFungibleFaucetId(AccountId), + #[error("cannot merge two full state deltas")] + MergingFullStateDeltas, } // STORAGE MAP ERROR @@ -691,8 +703,8 @@ pub enum ProvenTransactionError { PrivateAccountWithDetails(AccountId), #[error("account {0} with public state is missing its account details")] PublicStateAccountMissingDetails(AccountId), - #[error("new account {0} with public state is missing its account details")] - NewPublicStateAccountRequiresFullDetails(AccountId), + #[error("new account {id} with public state must be accompanied by a full state delta")] + NewPublicStateAccountRequiresFullStateDelta { id: AccountId, source: AccountError }, #[error( "existing account {0} with public state should only provide delta updates instead of full details" )] @@ -708,6 +720,8 @@ pub enum ProvenTransactionError { }, #[error("proven transaction neither changed the account state, nor consumed any notes")] EmptyTransaction, + #[error("failed to validate account delta in transaction account update")] + AccountDeltaCommitmentMismatch(#[source] Box), } // PROPOSED BATCH ERROR diff --git a/crates/miden-objects/src/testing/block_note_tree.rs b/crates/miden-objects/src/testing/block_note_tree.rs index 0b0b343acd..9779c06c57 100644 --- a/crates/miden-objects/src/testing/block_note_tree.rs +++ b/crates/miden-objects/src/testing/block_note_tree.rs @@ -16,8 +16,9 @@ impl BlockNoteTree { pub fn from_note_batches(notes: &[OutputNoteBatch]) -> Result { let iter = notes.iter().enumerate().flat_map(|(batch_idx, batch_notes)| { batch_notes.iter().map(move |(note_idx_in_batch, note)| { - let block_note_index = - BlockNoteIndex::new(batch_idx, *note_idx_in_batch).expect("TODO"); + // SAFETY: This is only called from test code. Reconsider if this changes. + let block_note_index = BlockNoteIndex::new(batch_idx, *note_idx_in_batch) + .expect("output note batch indices should fit into a block"); (block_note_index, note.id(), *note.metadata()) }) }); diff --git a/crates/miden-objects/src/transaction/proven_tx.rs b/crates/miden-objects/src/transaction/proven_tx.rs index 5bfef2d792..3b002c80c9 100644 --- a/crates/miden-objects/src/transaction/proven_tx.rs +++ b/crates/miden-objects/src/transaction/proven_tx.rs @@ -1,7 +1,9 @@ +use alloc::boxed::Box; use alloc::string::ToString; use alloc::vec::Vec; use super::{InputNote, ToInputNoteCommitments}; +use crate::account::Account; use crate::account::delta::AccountUpdateDetails; use crate::asset::FungibleAsset; use crate::block::BlockNumber; @@ -22,7 +24,7 @@ use crate::utils::serde::{ Serializable, }; use crate::vm::ExecutionProof; -use crate::{ACCOUNT_UPDATE_MAX_SIZE, EMPTY_WORD, ProvenTransactionError, Word}; +use crate::{ACCOUNT_UPDATE_MAX_SIZE, ProvenTransactionError, Word}; // PROVEN TRANSACTION // ================================================================================================ @@ -138,75 +140,46 @@ impl ProvenTransaction { /// # Errors /// /// Returns an error if: - /// - The size of the serialized account update exceeds [`ACCOUNT_UPDATE_MAX_SIZE`]. /// - The transaction is empty, which is the case if the account state is unchanged or the /// number of input notes is zero. - /// - The transaction was executed against a _new_ account with public state and its account ID - /// does not match the ID in the account update. - /// - The transaction was executed against a _new_ account with public state and its commitment - /// does not match the final state commitment of the account update. - /// - The transaction was executed against a private account and the account update is _not_ of - /// type [`AccountUpdateDetails::Private`]. - /// - The transaction was executed against an account with public state and the update is of - /// type [`AccountUpdateDetails::Private`]. - /// - The transaction was executed against an _existing_ account with public state and the - /// update is of type [`AccountUpdateDetails::New`]. - /// - The transaction creates a _new_ account with public state and the update is of type - /// [`AccountUpdateDetails::Delta`]. - fn validate(self) -> Result { - // If the account's state is public, then the account update details must be present. - if self.account_id().has_public_state() { - self.account_update.validate()?; - - // check that either the account state was changed or at least one note was consumed, - // otherwise this transaction is empty - if self.account_update.initial_state_commitment() - == self.account_update.final_state_commitment() - && self.input_notes.commitment() == EMPTY_WORD - { - return Err(ProvenTransactionError::EmptyTransaction); - } + /// - The commitment computed on the actual account delta contained in [`TxAccountUpdate`] does + /// not match its declared account delta commitment. + fn validate(mut self) -> Result { + // Check that either the account state was changed or at least one note was consumed, + // otherwise this transaction is considered empty. + if self.account_update.initial_state_commitment() + == self.account_update.final_state_commitment() + && self.input_notes.commitment().is_empty() + { + return Err(ProvenTransactionError::EmptyTransaction); + } - let is_new_account = self.account_update.initial_state_commitment() == Word::empty(); - match self.account_update.details() { - AccountUpdateDetails::Private => { - return Err(ProvenTransactionError::PublicStateAccountMissingDetails( - self.account_id(), - )); - }, - AccountUpdateDetails::New(account) => { - if !is_new_account { - return Err( - ProvenTransactionError::ExistingPublicStateAccountRequiresDeltaDetails( - self.account_id(), - ), - ); - } - if account.id() != self.account_id() { - return Err(ProvenTransactionError::AccountIdMismatch { - tx_account_id: self.account_id(), - details_account_id: account.id(), - }); - } - if account.commitment() != self.account_update.final_state_commitment() { - return Err(ProvenTransactionError::AccountFinalCommitmentMismatch { - tx_final_commitment: self.account_update.final_state_commitment(), - details_commitment: account.commitment(), - }); - } - }, - AccountUpdateDetails::Delta(_) => { - if is_new_account { - return Err( - ProvenTransactionError::NewPublicStateAccountRequiresFullDetails( - self.account_id(), - ), - ); - } - }, - } - } else if !self.account_update.is_private() { - return Err(ProvenTransactionError::PrivateAccountWithDetails(self.account_id())); + match &mut self.account_update.details { + // The delta commitment cannot be validated for private account updates. It will be + // validated as part of transaction proof verification implicitly. + AccountUpdateDetails::Private => (), + AccountUpdateDetails::Delta(post_fee_account_delta) => { + // Add the removed fee to the post fee delta to get the pre-fee delta, against which + // the delta commitment needs to be validated. + post_fee_account_delta.vault_mut().add_asset(self.fee.into()).map_err(|err| { + ProvenTransactionError::AccountDeltaCommitmentMismatch(Box::from(err)) + })?; + + let expected_commitment = self.account_update.account_delta_commitment; + let actual_commitment = post_fee_account_delta.to_commitment(); + if expected_commitment != actual_commitment { + return Err(ProvenTransactionError::AccountDeltaCommitmentMismatch(Box::from( + format!( + "expected account delta commitment {expected_commitment} but found {actual_commitment}" + ), + ))); + } + + // Remove the added fee again to recreate the post fee delta. + post_fee_account_delta.vault_mut().remove_asset(self.fee.into()).map_err( + |err| ProvenTransactionError::AccountDeltaCommitmentMismatch(Box::from(err)), + )?; + }, } Ok(self) @@ -379,21 +352,21 @@ impl ProvenTransactionBuilder { /// - The total number of output notes is greater than /// [`MAX_OUTPUT_NOTES_PER_TX`](crate::constants::MAX_OUTPUT_NOTES_PER_TX). /// - The vector of output notes contains duplicates. - /// - The size of the serialized account update exceeds [`ACCOUNT_UPDATE_MAX_SIZE`]. /// - The transaction is empty, which is the case if the account state is unchanged or the /// number of input notes is zero. + /// - The commitment computed on the actual account delta contained in [`TxAccountUpdate`] does + /// not match its declared account delta commitment. + /// - The size of the serialized account update exceeds [`ACCOUNT_UPDATE_MAX_SIZE`]. /// - The transaction was executed against a _new_ account with public state and its account ID /// does not match the ID in the account update. /// - The transaction was executed against a _new_ account with public state and its commitment /// does not match the final state commitment of the account update. + /// - The transaction creates a _new_ account with public state and the update is of type + /// [`AccountUpdateDetails::Delta`] but the account delta is not a full state delta. /// - The transaction was executed against a private account and the account update is _not_ of /// type [`AccountUpdateDetails::Private`]. /// - The transaction was executed against an account with public state and the update is of /// type [`AccountUpdateDetails::Private`]. - /// - The transaction was executed against an _existing_ account with public state and the - /// update is of type [`AccountUpdateDetails::New`]. - /// - The transaction creates a _new_ account with public state and the update is of type - /// [`AccountUpdateDetails::Delta`]. pub fn build(self) -> Result { let input_notes = InputNotes::new(self.input_notes).map_err(ProvenTransactionError::InputNotesError)?; @@ -411,7 +384,7 @@ impl ProvenTransactionBuilder { self.final_account_commitment, self.account_delta_commitment, self.account_update_details, - ); + )?; let proven_transaction = ProvenTransaction { id, @@ -465,20 +438,86 @@ pub struct TxAccountUpdate { impl TxAccountUpdate { /// Returns a new [TxAccountUpdate] instantiated from the specified components. - pub const fn new( + /// + /// Returns an error if: + /// - The size of the serialized account update exceeds [`ACCOUNT_UPDATE_MAX_SIZE`]. + /// - The transaction was executed against a _new_ account with public state and its account ID + /// does not match the ID in the account update. + /// - The transaction was executed against a _new_ account with public state and its commitment + /// does not match the final state commitment of the account update. + /// - The transaction creates a _new_ account with public state and the update is of type + /// [`AccountUpdateDetails::Delta`] but the account delta is not a full state delta. + /// - The transaction was executed against a private account and the account update is _not_ of + /// type [`AccountUpdateDetails::Private`]. + /// - The transaction was executed against an account with public state and the update is of + /// type [`AccountUpdateDetails::Private`]. + pub fn new( account_id: AccountId, init_state_commitment: Word, final_state_commitment: Word, account_delta_commitment: Word, details: AccountUpdateDetails, - ) -> Self { - Self { + ) -> Result { + let account_update = Self { account_id, init_state_commitment, final_state_commitment, account_delta_commitment, details, + }; + + let account_update_size = account_update.details.get_size_hint(); + if account_update_size > ACCOUNT_UPDATE_MAX_SIZE as usize { + return Err(ProvenTransactionError::AccountUpdateSizeLimitExceeded { + account_id, + update_size: account_update_size, + }); + } + + if account_id.is_private() { + if account_update.details.is_private() { + return Ok(account_update); + } else { + return Err(ProvenTransactionError::PrivateAccountWithDetails(account_id)); + } } + + match account_update.details() { + AccountUpdateDetails::Private => { + return Err(ProvenTransactionError::PublicStateAccountMissingDetails( + account_update.account_id(), + )); + }, + AccountUpdateDetails::Delta(delta) => { + let is_new_account = account_update.initial_state_commitment().is_empty(); + if is_new_account { + // Validate that for new accounts, the full account state can be constructed + // from the delta. This will fail if it is not such a full state delta. + let account = Account::try_from(delta).map_err(|err| { + ProvenTransactionError::NewPublicStateAccountRequiresFullStateDelta { + id: delta.id(), + source: err, + } + })?; + + if account.id() != account_id { + return Err(ProvenTransactionError::AccountIdMismatch { + tx_account_id: account_id, + details_account_id: account.id(), + }); + } + + if account.commitment() != account_update.final_state_commitment { + return Err(ProvenTransactionError::AccountFinalCommitmentMismatch { + tx_final_commitment: account_update.final_state_commitment, + details_commitment: account.commitment(), + }); + } + } + }, + } + + Ok(account_update) } /// Returns the ID of the updated account. @@ -513,21 +552,6 @@ impl TxAccountUpdate { pub fn is_private(&self) -> bool { self.details.is_private() } - - /// Validates the following properties of the account update: - /// - /// - The size of the serialized account update does not exceed [`ACCOUNT_UPDATE_MAX_SIZE`]. - pub fn validate(&self) -> Result<(), ProvenTransactionError> { - let account_update_size = self.details().get_size_hint(); - if account_update_size > ACCOUNT_UPDATE_MAX_SIZE as usize { - Err(ProvenTransactionError::AccountUpdateSizeLimitExceeded { - account_id: self.account_id(), - update_size: account_update_size, - }) - } else { - Ok(()) - } - } } impl Serializable for TxAccountUpdate { @@ -542,13 +566,20 @@ impl Serializable for TxAccountUpdate { impl Deserializable for TxAccountUpdate { fn read_from(source: &mut R) -> Result { - Ok(Self { - account_id: AccountId::read_from(source)?, - init_state_commitment: Word::read_from(source)?, - final_state_commitment: Word::read_from(source)?, - account_delta_commitment: Word::read_from(source)?, - details: AccountUpdateDetails::read_from(source)?, - }) + let account_id = AccountId::read_from(source)?; + let init_state_commitment = Word::read_from(source)?; + let final_state_commitment = Word::read_from(source)?; + let account_delta_commitment = Word::read_from(source)?; + let details = AccountUpdateDetails::read_from(source)?; + + Self::new( + account_id, + init_state_commitment, + final_state_commitment, + account_delta_commitment, + details, + ) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } @@ -661,6 +692,7 @@ mod tests { use super::ProvenTransaction; use crate::account::delta::AccountUpdateDetails; use crate::account::{ + Account, AccountDelta, AccountId, AccountIdVersion, @@ -676,6 +708,8 @@ mod tests { ACCOUNT_ID_PRIVATE_SENDER, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, }; + use crate::testing::add_component::AddComponent; + use crate::testing::noop_auth_component::NoopAuthComponent; use crate::transaction::{ProvenTransactionBuilder, TxAccountUpdate}; use crate::utils::Serializable; use crate::{ @@ -705,26 +739,27 @@ mod tests { } #[test] - fn account_update_size_limit_not_exceeded() { - // A small delta does not exceed the limit. - let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap(); - let storage_delta = AccountStorageDelta::from_iters( - [1, 2, 3, 4], - [(2, Word::from([1, 1, 1, 1u32])), (3, Word::from([1, 1, 0, 1u32]))], - [], - ); - let delta = AccountDelta::new(account_id, storage_delta, AccountVaultDelta::default(), ONE) - .unwrap(); + fn account_update_size_limit_not_exceeded() -> anyhow::Result<()> { + // A small account's delta does not exceed the limit. + let account = Account::builder([9; 32]) + .account_type(AccountType::RegularAccountUpdatableCode) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(NoopAuthComponent) + .with_component(AddComponent) + .build_existing()?; + let delta = AccountDelta::try_from(account.clone())?; + let details = AccountUpdateDetails::Delta(delta); + TxAccountUpdate::new( - AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(), - EMPTY_WORD, - EMPTY_WORD, - EMPTY_WORD, + account.id(), + account.commitment(), + account.commitment(), + Word::empty(), details, - ) - .validate() - .unwrap(); + )?; + + Ok(()) } #[test] @@ -754,7 +789,6 @@ mod tests { EMPTY_WORD, details, ) - .validate() .unwrap_err(); assert!( diff --git a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs index 8535e16315..058d6a7f80 100644 --- a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs +++ b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs @@ -701,19 +701,21 @@ fn expired_transaction() -> anyhow::Result<()> { /// _before_ a state-updating transaction with state commitments X -> Y against account A. #[test] fn noop_tx_before_state_updating_tx_against_same_account() -> anyhow::Result<()> { - let TestSetup { mut chain, account1, .. } = setup_chain(); + let TestSetup { mut chain, account1, note1, .. } = setup_chain(); let block1 = chain.block_header(1); let block2 = chain.prove_next_block()?; let random_final_state_commitment = Word::from([1, 2, 3, 4u32]); let note = mock_note(40); + // consume a random note to make the transaction non-empty let noop_tx1 = MockProvenTxBuilder::with_account( account1.id(), account1.commitment(), account1.commitment(), ) .ref_block_commitment(block1.commitment()) + .authenticated_notes(vec![note1]) .output_notes(vec![OutputNote::Full(note.clone())]) .build()?; @@ -750,7 +752,7 @@ fn noop_tx_before_state_updating_tx_against_same_account() -> anyhow::Result<()> /// _after_ a state-updating transaction with state commitments X -> Y against account A. #[test] fn noop_tx_after_state_updating_tx_against_same_account() -> anyhow::Result<()> { - let TestSetup { mut chain, account1, .. } = setup_chain(); + let TestSetup { mut chain, account1, note1, .. } = setup_chain(); let block1 = chain.block_header(1); let block2 = chain.prove_next_block()?; @@ -767,12 +769,14 @@ fn noop_tx_after_state_updating_tx_against_same_account() -> anyhow::Result<()> .unauthenticated_notes(vec![note.clone()]) .build()?; + // consume a random note to make the transaction non-empty let noop_tx2 = MockProvenTxBuilder::with_account( account1.id(), random_final_state_commitment, random_final_state_commitment, ) .ref_block_commitment(block1.commitment()) + .authenticated_notes(vec![note1]) .output_notes(vec![OutputNote::Full(note.clone())]) .build()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 31a3a7e71f..67dff516e7 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -1,4 +1,5 @@ use alloc::sync::Arc; +use alloc::vec::Vec; use std::collections::BTreeMap; use anyhow::Context; @@ -28,6 +29,7 @@ use miden_objects::account::{ AccountStorage, AccountStorageMode, AccountType, + StorageMap, StorageSlot, }; use miden_objects::assembly::diagnostics::{IntoDiagnostic, NamedSource, Report, WrapErr, miette}; @@ -49,6 +51,7 @@ use miden_processor::{EMPTY_WORD, ExecutionError, MastNodeExt, Word}; use miden_tx::{LocalTransactionProver, TransactionExecutorError}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; +use winter_rand_utils::rand_value; use super::{Felt, StackInputs, ZERO}; use crate::executor::CodeExecutor; @@ -149,7 +152,7 @@ pub async fn compute_current_commitment() -> miette::Result<()> { .execute() .await .into_diagnostic() - .wrap_err("failed to execute code")?; + .wrap_err("failed to execute transaction")?; Ok(()) } @@ -995,7 +998,7 @@ async fn test_compute_storage_commitment() -> anyhow::Result<()> { async fn proven_tx_storage_map_matches_executed_tx_for_new_account() -> anyhow::Result<()> { // Build a public account so the proven transaction includes the account update. let mock_slots = AccountStorage::mock_storage_slots(); - let mut account = AccountBuilder::new([1; 32]) + let account = AccountBuilder::new([1; 32]) .storage_mode(AccountStorageMode::Public) .with_auth_component(Auth::IncrNonce) .with_component(MockAccountComponent::with_slots(mock_slots.clone())) @@ -1047,15 +1050,72 @@ async fn proven_tx_storage_map_matches_executed_tx_for_new_account() -> anyhow:: let proven_tx = LocalTransactionProver::default().prove_dummy(tx.clone())?; - let AccountUpdateDetails::New(new_account) = proven_tx.account_update().details() else { + let AccountUpdateDetails::Delta(delta) = proven_tx.account_update().details() else { panic!("expected delta"); }; - account.apply_delta(tx.account_delta())?; + let proven_tx_account = Account::try_from(delta)?; + let exec_tx_account = Account::try_from(tx.account_delta())?; - for (idx, slot) in new_account.storage().slots().iter().enumerate() { - assert_eq!(slot, &account.storage().slots()[idx], "slot {idx} did not match"); + assert_eq!(proven_tx_account.storage(), exec_tx_account.storage()); + + Ok(()) +} + +/// Tests that an account with a non-empty map can be created. +/// +/// In particular, this tests the account delta logic for (non-empty) storage slots for _new_ +/// accounts. +#[tokio::test] +async fn prove_account_creation_with_non_empty_storage() -> anyhow::Result<()> { + let slot0 = StorageSlot::Value(Word::from([1, 2, 3, 4u32])); + let slot1 = StorageSlot::Value(Word::from([10, 20, 30, 40u32])); + let mut map_entries = Vec::new(); + for _ in 0..10 { + map_entries.push((rand_value::(), rand_value::())); } + let map_slot = StorageSlot::Map(StorageMap::with_entries(map_entries.clone())?); + + let account = AccountBuilder::new([6; 32]) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(Auth::IncrNonce) + .with_component(MockAccountComponent::with_slots(vec![ + slot0.clone(), + slot1.clone(), + map_slot, + ])) + .build()?; + + let tx = TransactionContextBuilder::new(account) + .build()? + .execute() + .await + .context("failed to execute account-creating transaction")?; + + assert_eq!(tx.account_delta().nonce_delta(), Felt::new(1)); + + assert_eq!(tx.account_delta().storage().values().get(&0).unwrap(), &slot0.value()); + assert_eq!(tx.account_delta().storage().values().get(&1).unwrap(), &slot1.value()); + + assert_eq!( + tx.account_delta().storage().maps().get(&2).unwrap().entries(), + &BTreeMap::from_iter( + map_entries + .into_iter() + .map(|(key, value)| { (LexicographicWord::new(key), value) }) + ) + ); + + assert!(tx.account_delta().vault().is_empty()); + assert_eq!(tx.final_account().nonce(), Felt::new(1)); + + let proven_tx = LocalTransactionProver::default().prove(tx.clone())?; + + // The delta should be present on the proven tx. + let AccountUpdateDetails::Delta(delta) = proven_tx.account_update().details() else { + panic!("expected delta"); + }; + assert_eq!(delta, tx.account_delta()); Ok(()) } diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 5de13af506..643a9ce2a3 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -866,17 +866,22 @@ impl MockChain { for account_update in proven_block.updated_accounts() { match account_update.details() { - AccountUpdateDetails::New(account) => { - self.committed_accounts.insert(account.id(), account.clone()); - }, AccountUpdateDetails::Delta(account_delta) => { - let committed_account = - self.committed_accounts.get_mut(&account_update.account_id()).ok_or_else( - || anyhow::anyhow!("account delta in block for non-existent account"), - )?; - committed_account - .apply_delta(account_delta) - .context("failed to apply account delta")?; + if account_delta.is_full_state() { + let account = Account::try_from(account_delta) + .context("failed to convert full state delta into full account")?; + self.committed_accounts.insert(account.id(), account.clone()); + } else { + let committed_account = self + .committed_accounts + .get_mut(&account_update.account_id()) + .ok_or_else(|| { + anyhow::anyhow!("account delta in block for non-existent account") + })?; + committed_account + .apply_delta(account_delta) + .context("failed to apply account delta")?; + } }, // No state to keep for private accounts other than the commitment on the account // tree diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 6eef096494..93aeafa53a 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -22,6 +22,7 @@ use miden_objects::account::delta::AccountUpdateDetails; use miden_objects::account::{ Account, AccountBuilder, + AccountDelta, AccountId, AccountStorageMode, AccountType, @@ -174,11 +175,13 @@ impl MockChainBuilder { .accounts .into_values() .map(|account| { - BlockAccountUpdate::new( - account.id(), - account.commitment(), - AccountUpdateDetails::New(account), - ) + let account_id = account.id(); + let account_commitment = account.commitment(); + let account_delta = AccountDelta::try_from(account) + .expect("chain builder should only store existing accounts without seeds"); + let update_details = AccountUpdateDetails::Delta(account_delta); + + BlockAccountUpdate::new(account_id, account_commitment, update_details) }) .collect(); diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index bff4e51858..160f29bc1e 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -1,6 +1,7 @@ extern crate alloc; use core::slice; +use std::sync::Arc; use miden_lib::account::faucets::{BasicFungibleFaucet, FungibleFaucetExt, NetworkFungibleFaucet}; use miden_lib::errors::tx_kernel_errors::ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED; @@ -13,6 +14,7 @@ use miden_objects::account::{ AccountStorageMode, AccountType, }; +use miden_objects::assembly::DefaultSourceManager; use miden_objects::asset::{Asset, FungibleAsset}; use miden_objects::note::{ Note, @@ -83,9 +85,15 @@ pub async fn execute_mint_transaction( faucet: Account, params: &FaucetTestParams, ) -> anyhow::Result { + let source_manager = Arc::new(DefaultSourceManager::default()); let tx_script_code = create_mint_script_code(params); - let tx_script = ScriptBuilder::default().compile_tx_script(tx_script_code)?; - let tx_context = mock_chain.build_tx_context(faucet, &[], &[])?.tx_script(tx_script).build()?; + let tx_script = ScriptBuilder::with_source_manager(source_manager.clone()) + .compile_tx_script(tx_script_code)?; + let tx_context = mock_chain + .build_tx_context(faucet, &[], &[])? + .tx_script(tx_script) + .with_source_manager(source_manager) + .build()?; Ok(tx_context.execute().await?) } diff --git a/crates/miden-tx/src/host/account_delta_tracker.rs b/crates/miden-tx/src/host/account_delta_tracker.rs index 7035e051fd..279d806f05 100644 --- a/crates/miden-tx/src/host/account_delta_tracker.rs +++ b/crates/miden-tx/src/host/account_delta_tracker.rs @@ -1,4 +1,10 @@ -use miden_objects::account::{AccountDelta, AccountId, AccountStorageHeader, AccountVaultDelta}; +use miden_objects::account::{ + AccountCode, + AccountDelta, + AccountId, + AccountVaultDelta, + PartialAccount, +}; use miden_objects::{Felt, FieldElement, ZERO}; use crate::host::storage_delta_tracker::StorageDeltaTracker; @@ -20,16 +26,24 @@ pub struct AccountDeltaTracker { account_id: AccountId, storage: StorageDeltaTracker, vault: AccountVaultDelta, + code: Option, nonce_delta: Felt, } impl AccountDeltaTracker { /// Returns a new [AccountDeltaTracker] instantiated for the specified account. - pub fn new(account_id: AccountId, storage_header: AccountStorageHeader) -> Self { + pub fn new(account: &PartialAccount) -> Self { + let code = if account.is_new() { + Some(account.code().clone()) + } else { + None + }; + Self { - account_id, - storage: StorageDeltaTracker::new(storage_header), + account_id: account.id(), + storage: StorageDeltaTracker::new(account), vault: AccountVaultDelta::default(), + code, nonce_delta: ZERO, } } @@ -72,5 +86,6 @@ impl AccountDeltaTracker { AccountDelta::new(account_id, storage_delta, vault_delta, nonce_delta) .expect("account delta created in delta tracker should be valid") + .with_code(self.code) } } diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 4961e78cdb..9613872edd 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -143,10 +143,7 @@ where scripts_mast_store, initial_account_header: account.into(), initial_account_storage_header: account.storage().header().clone(), - account_delta: AccountDeltaTracker::new( - account.id(), - account.storage().header().clone(), - ), + account_delta: AccountDeltaTracker::new(account), acct_procedure_index_map, output_notes: BTreeMap::default(), input_notes, diff --git a/crates/miden-tx/src/host/storage_delta_tracker.rs b/crates/miden-tx/src/host/storage_delta_tracker.rs index 317cb9d569..c5dabc8a4d 100644 --- a/crates/miden-tx/src/host/storage_delta_tracker.rs +++ b/crates/miden-tx/src/host/storage_delta_tracker.rs @@ -1,7 +1,13 @@ use alloc::collections::BTreeMap; use miden_objects::Word; -use miden_objects::account::{AccountStorageDelta, AccountStorageHeader}; +use miden_objects::account::{ + AccountStorageDelta, + AccountStorageHeader, + PartialAccount, + StorageMap, + StorageSlotType, +}; /// Keeps track of the initial storage of an account during transaction execution. /// @@ -30,13 +36,53 @@ impl StorageDeltaTracker { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Constructs a new initial account storage from a storage header. - pub fn new(storage_header: AccountStorageHeader) -> Self { - Self { - storage_header, + /// Constructs a new initial storage delta from the provided account. + /// + /// If the account is new, inserts the storage entries into the delta analogously to the + /// transaction kernel delta. + pub fn new(account: &PartialAccount) -> Self { + let initial_storage_header = if account.is_new() { + empty_storage_header_from_account(account) + } else { + account.storage().header().clone() + }; + + let mut storage_delta_tracker = Self { + storage_header: initial_storage_header, init_maps: BTreeMap::new(), delta: AccountStorageDelta::new(), + }; + + // Insert account storage into delta if it is new to match the kernel behavior. + if account.is_new() { + (0..u8::MAX).zip(account.storage().header().slots()).for_each( + |(slot_idx, (slot_type, value))| match slot_type { + StorageSlotType::Value => { + // Note that we can insert the value unconditionally as the delta will be + // normalized before the commitment is computed. + storage_delta_tracker.set_item(slot_idx, Word::empty(), *value); + }, + StorageSlotType::Map => { + let storage_map = account + .storage() + .maps() + .find(|map| map.root() == *value) + .expect("storage map should be present in partial storage"); + + storage_map.entries().for_each(|(key, value)| { + storage_delta_tracker.set_map_item( + slot_idx, + *key, + Word::empty(), + *value, + ); + }); + }, + }, + ); } + + storage_delta_tracker } // PUBLIC MUTATORS @@ -117,3 +163,17 @@ impl StorageDeltaTracker { .expect("storage delta should still be valid since no new values were added") } } + +/// Creates empty slots of the same slot types as the to-be-created account. +fn empty_storage_header_from_account(account: &PartialAccount) -> AccountStorageHeader { + let slots = account + .storage() + .header() + .slots() + .map(|(slot_type, _)| match slot_type { + StorageSlotType::Value => (*slot_type, Word::empty()), + StorageSlotType::Map => (*slot_type, StorageMap::new().root()), + }) + .collect(); + AccountStorageHeader::new(slots) +} diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index 8be7be0c06..29c3d5f576 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -2,20 +2,9 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_lib::transaction::TransactionKernel; -use miden_objects::AccountError; use miden_objects::account::delta::AccountUpdateDetails; -use miden_objects::account::{ - Account, - AccountDelta, - AccountStorage, - PartialAccount, - PartialStorage, - PartialStorageMap, - StorageMap, - StorageSlot, - StorageSlotType, -}; -use miden_objects::asset::{Asset, AssetVault}; +use miden_objects::account::{AccountDelta, PartialAccount}; +use miden_objects::asset::Asset; use miden_objects::block::BlockNumber; use miden_objects::transaction::{ InputNote, @@ -96,17 +85,7 @@ impl LocalTransactionProver { let builder = match account.has_public_state() { true => { - let account_update_details = if account.is_new() { - let mut account = partial_account_to_full(account); - account - .apply_delta(&post_fee_account_delta) - .map_err(TransactionProverError::AccountDeltaApplyFailed)?; - - AccountUpdateDetails::New(account) - } else { - AccountUpdateDetails::Delta(post_fee_account_delta) - }; - + let account_update_details = AccountUpdateDetails::Delta(post_fee_account_delta); builder.account_update_details(account_update_details) }, false => builder, @@ -187,55 +166,6 @@ impl Default for LocalTransactionProver { } } -fn partial_account_to_full(partial_account: PartialAccount) -> Account { - let (id, partial_vault, partial_storage, code, nonce, seed) = partial_account.into_parts(); - - // For new accounts, the partial storage must represent the full initial account - // storage. - let storage = partial_storage_to_full(partial_storage) - .expect("partial account should ensure internal consistency for seed"); - - // The vault of a new account should be empty. - debug_assert_eq!(partial_vault.leaves().count(), 0); - let vault = AssetVault::default(); - - Account::new(id, vault, storage, code, nonce, seed) - .expect("partial account should ensure internal consistency for seed") -} - -fn partial_storage_to_full( - partial_storage: PartialStorage, -) -> Result { - let (_, header, mut maps) = partial_storage.into_parts(); - let mut storage_slots = Vec::new(); - for (slot_type, slot_value) in header.slots() { - match slot_type { - StorageSlotType::Value => { - storage_slots.push(StorageSlot::Value(*slot_value)); - }, - StorageSlotType::Map => { - let storage_map = - maps.remove(slot_value) - .map(partial_storage_map_to_storage_map) - .expect("partial storage map should be present in partial storage")?; - storage_slots.push(StorageSlot::Map(storage_map)); - }, - } - } - - AccountStorage::new(storage_slots) -} - -fn partial_storage_map_to_storage_map( - partial_storage_map: PartialStorageMap, -) -> Result { - let mut storage_map = StorageMap::new(); - for (key, value) in partial_storage_map.entries() { - storage_map.insert(*key, *value)?; - } - Ok(storage_map) -} - #[cfg(any(feature = "testing", test))] impl LocalTransactionProver { pub fn prove_dummy( From 233aa9e5b65a463840f8fe14e39e7b64e8fbe91a Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Fri, 31 Oct 2025 20:35:29 +0300 Subject: [PATCH 113/133] refactor: split `account` modules into `active` and `native` [part 1] (#2026) --- CHANGELOG.md | 2 + .../multisig_rpo_falcon_512.masm | 23 +- .../asm/account_components/no_auth.masm | 9 +- .../account_components/rpo_falcon_512.masm | 4 +- .../rpo_falcon_512_acl.masm | 17 +- .../asm/kernels/transaction/api.masm | 25 +- .../asm/kernels/transaction/lib/account.masm | 73 ++- .../asm/kernels/transaction/lib/memory.masm | 2 +- .../{account.masm => active_account.masm} | 542 +++++------------- crates/miden-lib/asm/miden/auth/mod.masm | 4 +- .../asm/miden/auth/rpo_falcon512.masm | 15 +- .../contracts/faucets/basic_fungible.masm | 4 - .../asm/miden/contracts/faucets/mod.masm | 4 +- .../contracts/faucets/network_fungible.masm | 4 +- .../asm/miden/contracts/wallets/basic.masm | 6 +- crates/miden-lib/asm/miden/faucet.masm | 6 +- .../asm/miden/kernel_proc_offsets.masm | 107 ++-- .../miden-lib/asm/miden/native_account.masm | 274 +++++++++ crates/miden-lib/asm/note_scripts/P2ID.masm | 4 +- crates/miden-lib/asm/note_scripts/P2IDE.masm | 4 +- crates/miden-lib/src/lib.rs | 2 +- .../account_component/conditional_auth.rs | 4 +- .../testing/account_component/incr_nonce.rs | 4 +- .../src/testing/mock_account_code.rs | 23 +- .../src/transaction/kernel_procedures.rs | 12 +- crates/miden-lib/src/transaction/memory.rs | 30 +- crates/miden-lib/src/utils/script_builder.rs | 43 +- .../src/account/storage/map/mod.rs | 2 +- .../src/kernel_tests/tx/test_account.rs | 89 +-- .../src/kernel_tests/tx/test_asset_vault.rs | 12 +- .../src/kernel_tests/tx/test_fpi.rs | 174 +----- .../src/kernel_tests/tx/test_tx.rs | 2 +- crates/miden-testing/src/utils.rs | 2 +- docs/src/account/storage.md | 2 +- docs/src/protocol_library.md | 56 +- docs/src/transaction.md | 6 +- 36 files changed, 767 insertions(+), 825 deletions(-) rename crates/miden-lib/asm/miden/{account.masm => active_account.masm} (63%) create mode 100644 crates/miden-lib/asm/miden/native_account.masm diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aa7994a6c..242bc92be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ - Re-add bech32 encoding for `AccountId` ([#2018](https://github.com/0xMiden/miden-base/pull/2018)). - [BREAKING] Change `AccountTree` to be generic over `Smt` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). - [BREAKING] Change `AccountTree` to be generic over `trait AccountTreeBackend` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). +- [BREAKING] Separate account APIs in `miden::account` into `active_account` and `native_account` ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). +- [BREAKING] Remove `miden::account::get_native_nonce` procedure ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). ### Changes diff --git a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm index 0311bd473e..92a0cf0a21 100644 --- a/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/multisig_rpo_falcon_512.masm @@ -2,7 +2,8 @@ # # See the `AuthRpoFalcon512Multisig` Rust type's documentation for more details. -use miden::account +use miden::active_account +use miden::native_account use miden::auth type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt } @@ -72,7 +73,7 @@ proc assert_new_tx(msg: BeWord) # => [index, MSG, IS_EXECUTED_FLAG] # Set the key value pair in the map to mark transaction as executed - exec.account::set_map_item + exec.native_account::set_map_item # => [OLD_MAP_ROOT, [0, 0, 0, is_executed]] dropw drop drop drop @@ -117,7 +118,7 @@ proc cleanup_pubkey_mapping(init_num_of_approvers: u32, new_num_of_approvers: u3 push.PUBLIC_KEYS_MAP_SLOT # => [pub_key_slot_idx, [0, 0, 0, i-1], EMPTY_WORD, i-1, new_num_of_approvers] - exec.account::set_map_item + exec.native_account::set_map_item # => [OLD_MAP_ROOT, OLD_MAP_VALUE, i-1, new_num_of_approvers] dropw dropw @@ -180,7 +181,7 @@ pub proc update_signers_and_threshold.2(multisig_config_hash: BeWord) push.THRESHOLD_CONFIG_SLOT # => [slot, MULTISIG_CONFIG, pad(12)] - exec.account::set_item + exec.native_account::set_item # => [OLD_THRESHOLD_CONFIG, pad(12)] # store init_num_of_approvers for later @@ -207,7 +208,7 @@ pub proc update_signers_and_threshold.2(multisig_config_hash: BeWord) push.PUBLIC_KEYS_MAP_SLOT # => [pub_key_slot_idx, [0, 0, 0, i-1], PUB_KEY, i-1, pad(12)] - exec.account::set_map_item + exec.native_account::set_map_item # => [OLD_MAP_ROOT, OLD_MAP_VALUE, i-1, pad(12)] dropw dropw @@ -254,7 +255,7 @@ proc compute_transaction_threshold.1(default_threshold: u32) -> u32 # => [transaction_threshold] # get the number of account procedures - exec.account::get_num_procedures + exec.active_account::get_num_procedures # => [num_procedures, transaction_threshold] # 2. iterate through all account procedures @@ -265,11 +266,11 @@ proc compute_transaction_threshold.1(default_threshold: u32) -> u32 # => [num_procedures-1, num_procedures-1, transaction_threshold] # get procedure root of the procedure with index i - exec.account::get_procedure_root dupw + exec.active_account::get_procedure_root dupw # => [PROC_ROOT, PROC_ROOT, num_procedures-1, transaction_threshold] # 2a. check if this procedure has been called in the transaction - exec.account::was_procedure_called + exec.native_account::was_procedure_called # => [was_called, PROC_ROOT, num_procedures-1, transaction_threshold] # if it has been called, get the override threshold of that procedure @@ -281,7 +282,7 @@ proc compute_transaction_threshold.1(default_threshold: u32) -> u32 # 2b. get the override proc_threshold of that procedure # if the procedure has no override threshold, the returned map item will be [0, 0, 0, 0] - exec.account::get_initial_map_item + exec.active_account::get_initial_map_item # => [[0, 0, 0, proc_threshold], num_procedures-1, transaction_threshold] drop drop drop dup dup.3 @@ -353,7 +354,7 @@ end #! #! Invocation: call pub proc auth_tx_rpo_falcon512_multisig.1(salt: BeWord) - exec.account::incr_nonce drop + exec.native_account::incr_nonce drop # => [SALT] # ------ Computing transaction summary ------ @@ -374,7 +375,7 @@ pub proc auth_tx_rpo_falcon512_multisig.1(salt: BeWord) push.THRESHOLD_CONFIG_SLOT # => [index, TX_SUMMARY_COMMITMENT] - exec.account::get_initial_item + exec.active_account::get_initial_item # => [0, 0, num_of_approvers, default_threshold, TX_SUMMARY_COMMITMENT] drop drop diff --git a/crates/miden-lib/asm/account_components/no_auth.masm b/crates/miden-lib/asm/account_components/no_auth.masm index 1e9deba999..d651f68179 100644 --- a/crates/miden-lib/asm/account_components/no_auth.masm +++ b/crates/miden-lib/asm/account_components/no_auth.masm @@ -1,4 +1,5 @@ -use miden::account +use miden::active_account +use miden::native_account use std::word #! Increment the nonce only if the account commitment has changed @@ -14,10 +15,10 @@ use std::word pub proc auth_no_auth # check if the account state has changed by comparing initial and final commitments - exec.account::get_initial_commitment + exec.active_account::get_initial_commitment # => [INITIAL_COMMITMENT, pad(16)] - exec.account::compute_current_commitment + exec.active_account::compute_commitment # => [CURRENT_COMMITMENT, INITIAL_COMMITMENT, pad(16)] exec.word::eq not @@ -25,6 +26,6 @@ pub proc auth_no_auth # if the account has been updated, increment the nonce if.true - exec.account::incr_nonce drop + exec.native_account::incr_nonce drop end end diff --git a/crates/miden-lib/asm/account_components/rpo_falcon_512.masm b/crates/miden-lib/asm/account_components/rpo_falcon_512.masm index 58fcc84f89..7e68c230ee 100644 --- a/crates/miden-lib/asm/account_components/rpo_falcon_512.masm +++ b/crates/miden-lib/asm/account_components/rpo_falcon_512.masm @@ -3,7 +3,7 @@ # See the `AuthRpoFalcon512` Rust type's documentation for more details. use miden::auth::rpo_falcon512 -use miden::account +use miden::active_account type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt } @@ -34,7 +34,7 @@ pub proc auth_tx_rpo_falcon512(auth_args: BeWord) # Fetch public key from storage. # --------------------------------------------------------------------------------------------- - push.PUBLIC_KEY_SLOT exec.account::get_item + push.PUBLIC_KEY_SLOT exec.active_account::get_item # => [PUB_KEY, pad(16)] exec.rpo_falcon512::authenticate_transaction diff --git a/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm b/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm index c0b17b49b6..f349738d84 100644 --- a/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm +++ b/crates/miden-lib/asm/account_components/rpo_falcon_512_acl.masm @@ -2,7 +2,8 @@ # # See the `AuthRpoFalcon512Acl` Rust type's documentation for more details. -use miden::account +use miden::active_account +use miden::native_account use miden::tx use std::word @@ -39,7 +40,7 @@ pub proc auth_tx_rpo_falcon512_acl.2(auth_args: BeWord) # => [pad(16)] # Get the authentication configuration - push.AUTH_CONFIG_SLOT exec.account::get_item + push.AUTH_CONFIG_SLOT exec.active_account::get_item # => [0, allow_unauthorized_input_notes, allow_unauthorized_output_notes, num_auth_trigger_procs, pad(16)] drop @@ -64,10 +65,10 @@ pub proc auth_tx_rpo_falcon512_acl.2(auth_args: BeWord) dup.1 sub.1 push.0.0.0 push.AUTH_TRIGGER_PROCS_MAP_SLOT # => [AUTH_TRIGGER_PROCS_MAP_SLOT, [0, 0, 0, i-1], require_acl_auth, i, pad(16)] - exec.account::get_map_item + exec.active_account::get_map_item # => [AUTH_TRIGGER_PROC_ROOT, require_acl_auth, i, pad(16)] - exec.account::was_procedure_called + exec.native_account::was_procedure_called # => [was_called, require_acl_auth, i, pad(16)] # Update require_acl_auth @@ -123,24 +124,24 @@ pub proc auth_tx_rpo_falcon512_acl.2(auth_args: BeWord) # If authentication is required, perform signature verification if.true # Fetch public key from storage. - push.PUBLIC_KEY_SLOT exec.account::get_item + push.PUBLIC_KEY_SLOT exec.active_account::get_item # => [PUB_KEY, pad(16)] exec.::miden::auth::rpo_falcon512::authenticate_transaction else # ------ Check if initial account commitment differs from current commitment ------ - exec.account::get_initial_commitment + exec.active_account::get_initial_commitment # => [INITIAL_COMMITMENT, pad(16)] - exec.account::compute_current_commitment + exec.active_account::compute_commitment # => [CURRENT_COMMITMENT, INITIAL_COMMITMENT, pad(16)] exec.word::eq not # => [has_account_state_changed, pad(16)] if.true - exec.account::incr_nonce drop + exec.native_account::incr_nonce drop end end # => [pad(16)] diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index c1cb411809..728b06c800 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -216,25 +216,6 @@ export.account_get_nonce # => [nonce, pad(15)] end -#! Returns the native account nonce. -#! -#! Inputs: [pad(16)] -#! Outputs: [native_nonce, pad(15)] -#! -#! Where: -#! - native_nonce is the nonce of the native account. -#! -#! Invocation: dynexec -export.account_get_native_nonce - # get the native account nonce - exec.memory::get_native_account_nonce - # => [native_nonce, pad(16)] - - # truncate the stack - swap drop - # => [native_nonce, pad(15)] -end - #! Increments the account nonce by one and returns the new nonce. #! #! Inputs: [pad(16)] @@ -551,7 +532,7 @@ export.account_get_initial_vault_root # => [INIT_VAULT_ROOT, pad(12)] end -#! Returns the vault root of the current account. +#! Returns the vault root of the active account. #! #! Inputs: [pad(16)] #! Outputs: [VAULT_ROOT, pad(12)] @@ -705,6 +686,10 @@ end #! #! Invocation: dynexec export.account_was_procedure_called + # check that this procedure was executed against the native account + exec.memory::assert_native_account + # => [PROC_ROOT, pad(12)] + # check if the procedure was called exec.account::was_procedure_called # => [was_called, pad(15)] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 848df57b27..b7645c6164 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -205,7 +205,7 @@ end # PROCEDURES # ================================================================================================= -#! Computes the account commitment of the current account. +#! Computes the commitment of the active account. #! #! Notice that there is no caching (and, hence, dirty flag) for the commitment of the entire #! account. Assuming that the storage commitment is current, computing account commitment is @@ -298,32 +298,80 @@ export.memory::get_account_id->get_id #! - nonce is the account nonce. export.memory::get_account_nonce->get_nonce -#! Returns the native account commitment at the beginning of the transaction. +#! Returns the commitment of the active account at the beginning of the transaction. #! #! Inputs: [] #! Outputs: [INIT_COMMITMENT] #! #! Where: #! - INIT_COMMITMENT is the initial account commitment. -export.memory::get_init_account_commitment->get_initial_commitment +export.get_initial_commitment + # determine whether the active account is native + exec.memory::is_native_account + # => [is_native_account] -#! Returns the vault root of the native account at the beginning of the transaction. + if.true + exec.memory::get_init_account_commitment + else + # for the foreign account current commitment equals to initial + exec.compute_current_commitment + end + # => [INIT_COMMITMENT] +end + +#! Returns the vault root of the active account at the beginning of the transaction. #! #! Inputs: [] #! Outputs: [INIT_ACCOUNT_VAULT_ROOT] #! #! Where: #! - INIT_ACCOUNT_VAULT_ROOT is the initial account vault root. -export.memory::get_init_native_account_vault_root->get_initial_vault_root +export.get_initial_vault_root + # Get the vault root of the active account. For the foreign account this root will be equal to + # the initial one. + exec.memory::get_account_vault_root + # => [ACTIVE_ACCOUNT_VAULT_ROOT] + + # get the initial vault root of the native account + exec.memory::get_init_native_account_vault_root + # => [INIT_NATIVE_ACCOUNT_VAULT_ROOT, ACTIVE_ACCOUNT_VAULT_ROOT] + + # check whether the active account is native + exec.memory::is_native_account + # => [is_native_account, INIT_NATIVE_ACCOUNT_VAULT_ROOT, ACTIVE_ACCOUNT_VAULT_ROOT] + + # if the active account is native, keep the INIT_NATIVE_ACCOUNT_VAULT_ROOT, or + # ACTIVE_ACCOUNT_VAULT_ROOT otherwise + cdropw + # => [INIT_ACCOUNT_VAULT_ROOT] +end -#! Returns the storage commitment of the native account at the beginning of the transaction. +#! Returns the storage commitment of the active account at the beginning of the transaction. #! #! Inputs: [] #! Outputs: [INIT_ACCOUNT_STORAGE_COMMITMENT] #! #! Where: #! - INIT_ACCOUNT_STORAGE_COMMITMENT is the initial account storage commitment. -export.memory::get_init_account_storage_commitment->get_initial_storage_commitment +export.get_initial_storage_commitment + # Get the storage commitment of the active account. For the foreign account this commitment will + # be initial. + exec.memory::get_account_storage_commitment + # => [ACTIVE_ACCOUNT_STORAGE_COMMITMENT] + + # get the initial storage commitment of the native account + exec.memory::get_init_account_storage_commitment + # => [INIT_NATIVE_ACCOUNT_STORAGE_COMMITMENT, ACTIVE_ACCOUNT_STORAGE_COMMITMENT] + + # check whether the active account is native + exec.memory::is_native_account + # => [is_native_account, INIT_NATIVE_ACCOUNT_STORAGE_COMMITMENT, ACTIVE_ACCOUNT_STORAGE_COMMITMENT] + + # if the active account is native, keep the INIT_NATIVE_ACCOUNT_STORAGE_COMMITMENT, or + # ACTIVE_ACCOUNT_STORAGE_COMMITMENT otherwise + cdropw + # => [INIT_ACCOUNT_STORAGE_COMMITMENT] +end #! Gets the code commitment of the current account. #! @@ -957,7 +1005,7 @@ export.remove_asset_from_vault # => [ASSET] end -#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! Returns the balance of the fungible asset associated with the provided faucet_id in the active #! account's vault. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix] @@ -984,7 +1032,7 @@ export.get_balance # => [balance] end -#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! Returns the balance of the fungible asset associated with the provided faucet_id in the active #! account's vault at the beginning of the transaction. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix] @@ -1011,7 +1059,8 @@ export.get_initial_balance # => [init_balance] end -#! Returns a boolean indicating whether the non-fungible asset is present in the current account's vault. +#! Returns a boolean indicating whether the non-fungible asset is present in the active account's +#! vault. #! #! Inputs: [ASSET] #! Outputs: [has_asset] @@ -1045,7 +1094,7 @@ end #! Inputs: #! Operand stack: [account_id_prefix, account_id_suffix] #! Advice map: { -#! ACCOUNT_ID: [[account_id_suffix, account_id_prefix, 0, account_nonce]], +#! ACCOUNT_ID: [[account_id_suffix, account_id_prefix, 0, account_nonce], #! VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT], #! STORAGE_COMMITMENT: [[STORAGE_SLOT_DATA]], #! CODE_COMMITMENT: [[ACCOUNT_PROCEDURE_DATA]], @@ -1711,7 +1760,7 @@ proc.refresh_storage_commitment end end -#! Checks if a procedure has been called during transaction execution. +#! Checks if a native account procedure has been called during transaction execution. #! #! Note: This returns 1 only if the procedure invoked account-restricted kernel APIs (e.g., #! `exec.faucet::mint`) which trigger `authenticate_and_track_procedure`. Procedures that execute diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index 8ad5007566..aa94e8129d 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -1122,7 +1122,7 @@ export.set_account_vault_root mem_storew end -#! Returns the memory pointer to the initial vault root of the current account. +#! Returns the memory pointer to the initial vault root of the active account. #! #! For the native account, this returns the pointer to the initial vault root. #! For foreign accounts, this returns the regular vault root pointer since foreign accounts diff --git a/crates/miden-lib/asm/miden/account.masm b/crates/miden-lib/asm/miden/active_account.masm similarity index 63% rename from crates/miden-lib/asm/miden/account.masm rename to crates/miden-lib/asm/miden/active_account.masm index 3b4a74c08e..3da76aec24 100644 --- a/crates/miden-lib/asm/miden/account.masm +++ b/crates/miden-lib/asm/miden/active_account.masm @@ -1,9 +1,12 @@ use.miden::kernel_proc_offsets -# NATIVE ACCOUNT PROCEDURES +# ACTIVE ACCOUNT PROCEDURES # ================================================================================================= -#! Returns the ID of the current account. +# ID AND NONCE +# ------------------------------------------------------------------------------------------------- + +#! Returns the ID of the active account. #! #! Inputs: [] #! Outputs: [account_id_prefix, account_id_suffix] @@ -32,21 +35,24 @@ export.get_id # => [account_id_prefix, account_id_suffix] end -#! Returns the ID of the native account of the transaction. +#! Returns the nonce of the active account. +#! +#! This procedure always returns the initial account nonce, as the nonce can only be incremented +#! in the authentication procedure when signing the transaction after all user code has been +#! executed. #! #! Inputs: [] -#! Outputs: [account_id_prefix, account_id_suffix] +#! Outputs: [nonce] #! #! Where: -#! - account_id_{prefix,suffix} are the prefix and suffix felts of the native account ID of the -#! transaction. +#! - nonce is the active account's nonce. #! #! Invocation: exec -export.get_native_id +export.get_nonce # start padding the stack push.0.0.0 - exec.kernel_proc_offsets::account_get_native_id_offset + exec.kernel_proc_offsets::account_get_nonce_offset # => [offset, 0, 0, 0] # pad the stack @@ -54,194 +60,222 @@ export.get_native_id # => [offset, pad(15)] syscall.exec_kernel_proc - # => [account_id_prefix, account_id_suffix, pad(14)] + # => [nonce, pad(15)] # clean the stack - swapdw dropw dropw swapw dropw movdn.3 movdn.3 drop drop - # => [account_id_prefix, account_id_suffix] + swapdw dropw dropw swapw dropw movdn.3 drop drop drop + # => [nonce] end -#! Returns the nonce of the current account. -#! -#! This procedure always returns the initial account nonce, as the nonce can only be incremented -#! in the authentication procedure when signing the transaction after all user code has been -#! executed. +# COMMITMENTS +# ------------------------------------------------------------------------------------------------- + +#! Returns the commitment of the active account at the beginning of the transaction. #! #! Inputs: [] -#! Outputs: [nonce] +#! Outputs: [INIT_COMMITMENT] #! #! Where: -#! - nonce is the current account's nonce. +#! - INIT_COMMITMENT is the initial account commitment. #! #! Invocation: exec -export.get_nonce - # start padding the stack - push.0.0.0 +export.get_initial_commitment + # pad the stack + padw padw padw push.0.0.0 + # => [pad(15)] - exec.kernel_proc_offsets::account_get_nonce_offset - # => [offset, 0, 0, 0] + exec.kernel_proc_offsets::account_get_initial_commitment_offset + # => [offset, pad(15)] + + syscall.exec_kernel_proc + # => [INIT_COMMITMENT, pad(12)] + # clean the stack + swapdw dropw dropw swapw dropw + # => [INIT_COMMITMENT] +end + +#! Computes and returns the active account commitment from account data stored in memory. +#! +#! Inputs: [] +#! Outputs: [ACCOUNT_COMMITMENT] +#! +#! Where: +#! - ACCOUNT_COMMITMENT is the commitment of the account data. +#! +#! Invocation: exec +export.compute_commitment # pad the stack - padw swapw padw padw swapdw + padw padw padw push.0.0.0 + # => [pad(15)] + + exec.kernel_proc_offsets::account_compute_current_commitment_offset # => [offset, pad(15)] syscall.exec_kernel_proc - # => [nonce, pad(15)] + # => [ACCOUNT_COMMITMENT, pad(12)] # clean the stack - swapdw dropw dropw swapw dropw movdn.3 drop drop drop - # => [nonce] + swapdw dropw dropw swapw dropw + # => [ACCOUNT_COMMITMENT] end -#! Returns the nonce of the native account of the transaction. +#! Gets the code commitment of the active account. +#! +#! Notice that the account code cannot be changed during the user code execution, so the code +#! commitment doesn't change during transaction execution: commitment returned by this procedure +#! could be used as both the initial and the current. +#! +#! The commitment to an empty delta is defined as the empty word. +#! +#! During an account-creating transaction (when the initial nonce is 0), this procedure will not +#! return the empty word even if the initial storage commitment and the current storage commitment +#! are identical (storage hasn't changed). This is because the delta for a new account must +#! represent its entire newly created state, and the initial storage in a transaction is +#! initialized to the the storage that the account ID commits to, which may be non-empty. This +#! does not have any consequences other than being inconsistent in this edge case. #! #! Inputs: [] -#! Outputs: [nonce] +#! Outputs: [CODE_COMMITMENT] #! #! Where: -#! - nonce is the nonce of the native account of the transaction. +#! - CODE_COMMITMENT is the commitment of the account code. #! #! Invocation: exec -export.get_native_nonce - # start padding the stack - push.0.0.0 - - exec.kernel_proc_offsets::account_get_native_nonce_offset - # => [offset, 0, 0, 0] +export.get_code_commitment + exec.kernel_proc_offsets::account_get_code_commitment_offset + # => [offset] # pad the stack - padw swapw padw padw swapdw + push.0.0.0 movup.3 padw swapw padw padw swapdw # => [offset, pad(15)] syscall.exec_kernel_proc - # => [nonce, pad(15)] + # => [CODE_COMMITMENT, pad(12)] # clean the stack - swapdw dropw dropw swapw dropw movdn.3 drop drop drop - # => [nonce] + swapdw dropw dropw swapw dropw + # => [CODE_COMMITMENT] end -#! Increments the account nonce by one and returns the new nonce. +#! Returns the storage commitment of the active account at the beginning of the transaction. +#! +#! During an account-creating transaction (when the initial nonce is 0), this procedure and +#! compute_storage_commitment will return the same value at the beginning of the transaction +#! (before any note or transaction scripts were executed). Despite that, the account delta may +#! not be empty. See account::compute_delta_commitment for more. #! #! Inputs: [] -#! Outputs: [final_nonce] +#! Outputs: [INIT_STORAGE_COMMITMENT] #! #! Where: -#! - final_nonce is the new nonce of the account. Since it cannot be incremented again, this will -#! also be the final nonce of the account after transaction execution. -#! -#! Panics if: -#! - the invocation of this procedure does not originate from the native account. -#! - the invocation of this procedure does not originate from the authentication procedure -#! of the account. -#! - the nonce has already been incremented. +#! - INIT_STORAGE_COMMITMENT is the initial account storage commitment. #! #! Invocation: exec -export.incr_nonce +export.get_initial_storage_commitment # pad the stack padw padw padw push.0.0.0 # => [pad(15)] - exec.kernel_proc_offsets::account_incr_nonce_offset + exec.kernel_proc_offsets::account_get_initial_storage_commitment_offset # => [offset, pad(15)] syscall.exec_kernel_proc - # => [final_nonce, pad(15)] + # => [INIT_STORAGE_COMMITMENT, pad(12)] - swap.15 dropw dropw dropw drop drop drop - # => [final_nonce] + # clean the stack + swapdw dropw dropw swapw dropw + # => [INIT_STORAGE_COMMITMENT] end -#! Returns the native account commitment at the beginning of the transaction. +#! Computes the latest account storage commitment of the current account. +#! +#! Notice that this procedure always returns the latest commitment, but it doesn't actually always +#! recompute it: recomputation is performed only if the account's storage has been changed, +#! otherwise the cached value is returned. +#! +#! During an account-creating transaction (when the initial nonce is 0), this procedure and +#! get_initial_storage_commitment will return the same value at the beginning of the transaction +#! (before any note or transaction scripts were executed). Despite that, the account delta may +#! not be empty. See account::compute_delta_commitment for more. #! #! Inputs: [] -#! Outputs: [INIT_COMMITMENT] +#! Outputs: [STORAGE_COMMITMENT] #! #! Where: -#! - INIT_COMMITMENT is the initial account commitment. +#! - STORAGE_COMMITMENT is the commitment of the account storage. #! #! Invocation: exec -export.get_initial_commitment - # pad the stack - padw padw padw push.0.0.0 - # => [pad(15)] +export.compute_storage_commitment + exec.kernel_proc_offsets::account_compute_storage_commitment_offset + # => [offset] - exec.kernel_proc_offsets::account_get_initial_commitment_offset + # pad the stack + push.0.0.0 movup.3 padw swapw padw padw swapdw # => [offset, pad(15)] syscall.exec_kernel_proc - # => [INIT_COMMITMENT, pad(12)] + # => [STORAGE_COMMITMENT, pad(12)] # clean the stack swapdw dropw dropw swapw dropw - # => [INIT_COMMITMENT] + # => [STORAGE_COMMITMENT] end -#! Computes and returns the account commitment from account data stored in memory. +#! Returns the vault root of the active account at the beginning of the transaction. #! #! Inputs: [] -#! Outputs: [ACCOUNT_COMMITMENT] +#! Outputs: [INIT_VAULT_ROOT] #! #! Where: -#! - ACCOUNT_COMMITMENT is the commitment of the account data. +#! - INIT_VAULT_ROOT is the initial account vault root. #! #! Invocation: exec -export.compute_current_commitment +export.get_initial_vault_root # pad the stack padw padw padw push.0.0.0 # => [pad(15)] - exec.kernel_proc_offsets::account_compute_current_commitment_offset + exec.kernel_proc_offsets::account_get_initial_vault_root_offset # => [offset, pad(15)] syscall.exec_kernel_proc - # => [ACCOUNT_COMMITMENT, pad(12)] + # => [INIT_VAULT_ROOT, pad(12)] # clean the stack swapdw dropw dropw swapw dropw - # => [ACCOUNT_COMMITMENT] + # => [INIT_VAULT_ROOT] end -#! Computes the commitment to the native account's delta. -#! -#! Note that if the account state has changed, the nonce must be incremented before this procedure -#! is called, otherwise it will panic. This means it can only be called from an auth procedure, -#! since only auth procedures are allowed to increment the nonce. -#! -#! The commitment to an empty delta is defined as the empty word. -#! -#! During an account-creating transaction (when the initial nonce is 0), this procedure will not -#! return the empty word even if the initial storage commitment and the current storage commitment -#! are identical (storage hasn't changed). This is because the delta for a new account must -#! represent its entire newly created state, and the initial storage in a transaction is -#! initialized to the the storage that the account ID commits to, which may be non-empty. This -#! does not have any consequences other than being inconsistent in this edge case. +#! Returns the vault root of the active account. #! #! Inputs: [] -#! Outputs: [DELTA_COMMITMENT] +#! Outputs: [VAULT_ROOT] #! #! Where: -#! - DELTA_COMMITMENT is the commitment to the account delta. +#! - VAULT_ROOT is the root of the account vault. #! -#! Panics if: -#! - the vault or storage delta is not empty but the nonce increment is zero. -export.compute_delta_commitment - # pad the stack +#! Invocation: exec +export.get_vault_root + # pad the stack for syscall invocation padw padw padw push.0.0.0 # => [pad(15)] - exec.kernel_proc_offsets::account_compute_delta_commitment_offset + exec.kernel_proc_offsets::account_get_vault_root_offset # => [offset, pad(15)] syscall.exec_kernel_proc - # => [DELTA_COMMITMENT, pad(12)] + # => [VAULT_ROOT, pad(12)] # clean the stack swapdw dropw dropw swapw dropw - # => [DELTA_COMMITMENT] + # => [VAULT_ROOT] end -#! Gets an item from the account storage. Panics if the index is out of bounds. +# STORAGE +# ------------------------------------------------------------------------------------------------- + +#! Gets an item from the active account storage. Panics if the index is out of bounds. #! #! Inputs: [index] #! Outputs: [VALUE] @@ -273,7 +307,7 @@ export.get_item # => [VALUE] end -#! Gets the initial item from the account storage slot as it was at the beginning of the +#! Gets the initial item from the active account storage slot as it was at the beginning of the #! transaction. #! #! Inputs: [index] @@ -306,37 +340,7 @@ export.get_initial_item # => [INIT_VALUE] end -#! Sets an item in the account storage. Panics if the index is out of bounds. -#! -#! Inputs: [index, VALUE] -#! Outputs: [OLD_VALUE] -#! -#! Where: -#! - index is the index of the item to set. -#! - VALUE is the value to set. -#! - OLD_VALUE is the previous value of the item. -#! -#! Panics if: -#! - the index of the item is out of bounds. -#! -#! Invocation: exec -export.set_item - exec.kernel_proc_offsets::account_set_item_offset - # => [offset, index, VALUE] - - # pad the stack - push.0.0 movdn.7 movdn.7 padw padw swapdw - # => [offset, index, VALUE, pad(10)] - - syscall.exec_kernel_proc - # => [OLD_VALUE, pad(12)] - - # clean the stack - swapw.3 dropw dropw dropw - # => [OLD_VALUE] -end - -#! Gets a map item from the account storage. +#! Gets a map item from the active account storage. #! #! Inputs: [index, KEY] #! Outputs: [VALUE] @@ -367,40 +371,7 @@ export.get_map_item # => [VALUE] end -#! Sets a map item in the account storage. -#! -#! Inputs: [index, KEY, VALUE] -#! Outputs: [OLD_MAP_ROOT, OLD_MAP_VALUE] -#! -#! Where: -#! - index is the index of the map where the KEY VALUE should be set. -#! - KEY is the key to set at VALUE. -#! - VALUE is the value to set at KEY. -#! - OLD_MAP_ROOT is the old map root. -#! - OLD_MAP_VALUE is the old value at KEY. -#! -#! Panics if: -#! - the index for the map is out of bounds, meaning > 255. -#! - the slot item at index is not a map. -#! -#! Invocation: exec -export.set_map_item - exec.kernel_proc_offsets::account_set_map_item_offset - # => [offset, index, KEY, VALUE] - - # pad the stack - push.0.0 movdn.11 movdn.11 padw movdnw.3 - # => [offset, index, KEY, VALUE, pad(6)] - - syscall.exec_kernel_proc - # => [OLD_MAP_ROOT, OLD_MAP_VALUE, pad(8)] - - # clean the stack - swapdw dropw dropw - # => [OLD_MAP_ROOT, OLD_MAP_VALUE] -end - -#! Gets the initial VALUE from the account storage map as it was at the beginning of the +#! Gets the initial VALUE from the active account storage map as it was at the beginning of the #! transaction. #! #! Inputs: [index, KEY] @@ -432,100 +403,10 @@ export.get_initial_map_item # => [INIT_VALUE] end -#! Gets the account code commitment of the current account. -#! -#! Notice that the account code cannot be changed during the user code execution, so the code -#! commitment doesn't change during transaction execution: commitment returned by this procedure -#! could be used as both the initial and the current. -#! -#! Inputs: [] -#! Outputs: [CODE_COMMITMENT] -#! -#! Where: -#! - CODE_COMMITMENT is the commitment of the account code. -#! -#! Invocation: exec -export.get_code_commitment - exec.kernel_proc_offsets::account_get_code_commitment_offset - # => [offset] - - # pad the stack - push.0.0.0 movup.3 padw swapw padw padw swapdw - # => [offset, pad(15)] - - syscall.exec_kernel_proc - # => [CODE_COMMITMENT, pad(12)] - - # clean the stack - swapdw dropw dropw swapw dropw - # => [CODE_COMMITMENT] -end - -#! Returns the storage commitment of the native account at the beginning of the transaction. -#! -#! During an account-creating transaction (when the initial nonce is 0), this procedure and -#! compute_storage_commitment will return the same value at the beginning of the transaction -#! (before any note or transaction scripts were executed). Despite that, the account delta may -#! not be empty. See account::compute_delta_commitment for more. -#! -#! Inputs: [] -#! Outputs: [INIT_STORAGE_COMMITMENT] -#! -#! Where: -#! - INIT_STORAGE_COMMITMENT is the initial account storage commitment. -#! -#! Invocation: exec -export.get_initial_storage_commitment - # pad the stack - padw padw padw push.0.0.0 - # => [pad(15)] - - exec.kernel_proc_offsets::account_get_initial_storage_commitment_offset - # => [offset, pad(15)] - - syscall.exec_kernel_proc - # => [INIT_STORAGE_COMMITMENT, pad(12)] - - # clean the stack - swapdw dropw dropw swapw dropw - # => [INIT_STORAGE_COMMITMENT] -end - -#! Computes the latest account storage commitment of the current account. -#! -#! Notice that this procedure always returns the latest commitment, but it doesn't actually always -#! recompute it: recomputation is performed only if the account's storage has been changed, -#! otherwise the cached value is returned. -#! -#! During an account-creating transaction (when the initial nonce is 0), this procedure and -#! get_initial_storage_commitment will return the same value at the beginning of the transaction -#! (before any note or transaction scripts were executed). Despite that, the account delta may -#! not be empty. See account::compute_delta_commitment for more. -#! -#! Inputs: [] -#! Outputs: [STORAGE_COMMITMENT] -#! -#! Where: -#! - STORAGE_COMMITMENT is the commitment of the account storage. -#! -#! Invocation: exec -export.compute_storage_commitment - exec.kernel_proc_offsets::account_compute_storage_commitment_offset - # => [offset] - - # pad the stack - push.0.0.0 movup.3 padw swapw padw padw swapdw - # => [offset, pad(15)] - - syscall.exec_kernel_proc - # => [STORAGE_COMMITMENT, pad(12)] +# VAULT +# ------------------------------------------------------------------------------------------------- - # clean the stack - swapdw dropw dropw swapw dropw - # => [STORAGE_COMMITMENT] -end - -#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! Returns the balance of the fungible asset associated with the provided faucet_id in the active #! account's vault. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix] @@ -556,7 +437,7 @@ export.get_balance # => [balance] end -#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! Returns the balance of the fungible asset associated with the provided faucet_id in the active #! account's vault at the beginning of the transaction. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix] @@ -587,7 +468,7 @@ export.get_initial_balance # => [init_balance] end -#! Returns a boolean indicating whether the non-fungible asset is present in the current account's +#! Returns a boolean indicating whether the non-fungible asset is present in the active account's #! vault. #! #! Inputs: [ASSET] @@ -617,150 +498,7 @@ export.has_non_fungible_asset # => [has_asset] end -#! Add the specified asset to the vault. -#! -#! Inputs: [ASSET] -#! Outputs: [ASSET'] -#! -#! Where: -#! - ASSET' is a final asset in the account vault defined as follows: -#! - If ASSET is a non-fungible asset, then ASSET' is the same as ASSET. -#! - If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault -#! after ASSET was added to it. -#! -#! Panics if: -#! - the asset is not valid. -#! - the total value of two fungible assets is greater than or equal to 2^63. -#! - the vault already contains the same non-fungible asset. -#! -#! Invocation: exec -export.add_asset - exec.kernel_proc_offsets::account_add_asset_offset - # => [offset, ASSET] - - # pad the stack - push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw - # => [offset, ASSET, pad(11)] - - syscall.exec_kernel_proc - # => [ASSET', pad(12)] - - # clean the stack - swapdw dropw dropw swapw dropw - # => [ASSET'] -end - -#! Remove the specified asset from the vault. -#! -#! Inputs: [ASSET] -#! Outputs: [ASSET] -#! -#! Where: -#! - ASSET is the asset to remove from the vault. -#! -#! Panics if: -#! - the fungible asset is not found in the vault. -#! - the amount of the fungible asset in the vault is less than the amount to be removed. -#! - the non-fungible asset is not found in the vault. -#! -#! Invocation: exec -export.remove_asset - exec.kernel_proc_offsets::account_remove_asset_offset - # => [offset, ASSET] - - # pad the stack - push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw - # => [offset, ASSET, pad(11)] - - syscall.exec_kernel_proc - # => [ASSET, pad(12)] - - # clean the stack - swapdw dropw dropw swapw dropw - # => [ASSET] -end - -#! Returns the vault root of the native account at the beginning of the transaction. -#! -#! Inputs: [] -#! Outputs: [INIT_VAULT_ROOT] -#! -#! Where: -#! - INIT_VAULT_ROOT is the initial account vault root. -#! -#! Invocation: exec -export.get_initial_vault_root - # pad the stack - padw padw padw push.0.0.0 - # => [pad(15)] - - exec.kernel_proc_offsets::account_get_initial_vault_root_offset - # => [offset, pad(15)] - - syscall.exec_kernel_proc - # => [INIT_VAULT_ROOT, pad(12)] - - # clean the stack - swapdw dropw dropw swapw dropw - # => [INIT_VAULT_ROOT] -end - -#! Returns the vault root of the current account. -#! -#! Inputs: [] -#! Outputs: [VAULT_ROOT] -#! -#! Where: -#! - VAULT_ROOT is the root of the account vault. -#! -#! Invocation: exec -export.get_vault_root - # pad the stack for syscall invocation - padw padw padw push.0.0.0 - # => [pad(15)] - - exec.kernel_proc_offsets::account_get_vault_root_offset - # => [offset, pad(15)] - - syscall.exec_kernel_proc - # => [VAULT_ROOT, pad(12)] - - # clean the stack - swapdw dropw dropw swapw dropw - # => [VAULT_ROOT] -end - -#! Returns 1 if a procedure was called during transaction execution, and 0 otherwise. -#! -#! Note: This returns 1 only if the procedure invoked account-restricted kernel APIs (e.g., -#! `exec.faucet::mint`) which trigger `authenticate_and_track_procedure`. Procedures that execute -#! only local MASM instructions will return 0 even if they were executed. -#! -#! Inputs: [PROC_ROOT] -#! Outputs: [was_called] -#! -#! Where: -#! - PROC_ROOT is the hash of the procedure to check. -#! - was_called is 1 if the procedure was called, 0 otherwise. -#! -#! Invocation: exec -export.was_procedure_called - exec.kernel_proc_offsets::account_was_procedure_called_offset - # => [offset, PROC_ROOT] - - # pad the stack - push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw - # => [offset, PROC_ROOT, pad(11)] - - syscall.exec_kernel_proc - # => [was_called, pad(15)] - - # clean the stack - swapdw dropw dropw swapw dropw movdn.3 drop drop drop - # => [was_called] -end - -#! Returns the number of procedures in the current account. +#! Returns the number of procedures in the active account. #! #! Inputs: [] #! Outputs: [num_procedures] @@ -785,7 +523,7 @@ export.get_num_procedures # => [num_procedures] end -#! Returns the procedure root for the procedure at the specified index. +#! Returns the procedure root for the procedure at the specified index in the active account. #! #! Inputs: [index] #! Outputs: [PROC_ROOT] diff --git a/crates/miden-lib/asm/miden/auth/mod.masm b/crates/miden-lib/asm/miden/auth/mod.masm index ca3cfa7923..187ac3cf5f 100644 --- a/crates/miden-lib/asm/miden/auth/mod.masm +++ b/crates/miden-lib/asm/miden/auth/mod.masm @@ -1,4 +1,4 @@ -use.miden::account +use.miden::native_account use.miden::tx use.std::crypto::hashes::rpo @@ -47,7 +47,7 @@ end #! - INPUT_NOTES_COMMITMENT is the commitment to the transaction's inputs notes. #! - ACCOUNT_DELTA_COMMITMENT is the commitment to the transaction's account delta. export.create_tx_summary - exec.account::compute_delta_commitment + exec.native_account::compute_delta_commitment # => [ACCOUNT_DELTA_COMMITMENT, SALT] exec.tx::get_input_notes_commitment diff --git a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm index b0ff5ecd29..131780b538 100644 --- a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm +++ b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm @@ -1,4 +1,5 @@ -use.miden::account +use.miden::active_account +use.miden::native_account use.miden::auth use.miden::tx use.std::crypto::dsa::rpo_falcon512 @@ -38,7 +39,7 @@ export.authenticate_transaction # --------------------------------------------------------------------------------------------- # This has to happen before computing the delta commitment, otherwise that procedure will abort push.0.0 exec.tx::get_block_number - exec.account::incr_nonce + exec.native_account::incr_nonce # => [[final_nonce, ref_block_num, 0, 0], PUB_KEY] # Compute the message that is signed. @@ -75,10 +76,10 @@ end #! the provided account storage map slot, verifies their signatures against the transaction message, #! and returns the number of successfully verified signatures. #! -#! Note: Calls `account::get_initial_map_item` to access the transaction's initial storage state -#! rather than the current state. This is crucial when validating transactions that update -#! the owner public key mapping - the previous signers must authorize the change to -#! the new signers, not the new signers authorizing themselves. +#! Note: Calls `active_account::get_initial_map_item` to access the transaction's initial storage +#! state rather than the current state. This is crucial when validating transactions that update +#! the owner public key mapping - the previous signers must authorize the change to the new signers, +#! not the new signers authorizing themselves. #! #! Inputs: [pub_key_slot_idx, num_of_approvers, MSG] #! Outputs: [num_verified_signatures, MSG] @@ -107,7 +108,7 @@ export.verify_signatures.16 # => [owner_key_slot, [0, 0, 0, i-1], i-1, MSG] # Get public key from initial storage state - exec.account::get_initial_map_item + exec.active_account::get_initial_map_item # => [OWNER_PUB_KEY, i-1, MSG] loc_storew.CURRENT_PK_LOC diff --git a/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm b/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm index 2cebea9008..130df4d663 100644 --- a/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm +++ b/crates/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm @@ -8,11 +8,7 @@ # - decimals are the decimals of the token. # - token_symbol as three chars encoded in a Felt. -use.miden::account -use.miden::active_note use.miden::contracts::faucets -use.miden::faucet -use.miden::output_note # CONSTANTS # ================================================================================================= diff --git a/crates/miden-lib/asm/miden/contracts/faucets/mod.masm b/crates/miden-lib/asm/miden/contracts/faucets/mod.masm index d0e4d04694..202db163da 100644 --- a/crates/miden-lib/asm/miden/contracts/faucets/mod.masm +++ b/crates/miden-lib/asm/miden/contracts/faucets/mod.masm @@ -1,4 +1,4 @@ -use.miden::account +use.miden::active_account use.miden::active_note use.miden::faucet use.miden::output_note @@ -41,7 +41,7 @@ const.METADATA_SLOT=0 #! Invocation: exec export.distribute # get max supply of this faucet. We assume it is stored at pos 3 of slot 0 - push.METADATA_SLOT exec.account::get_item drop drop drop + push.METADATA_SLOT exec.active_account::get_item drop drop drop # => [max_supply, amount, tag, aux, note_type, execution_hint, RECIPIENT] # get total issuance of this faucet so far and add amount to be minted diff --git a/crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm b/crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm index c660573e00..a983169cfa 100644 --- a/crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm +++ b/crates/miden-lib/asm/miden/contracts/faucets/network_fungible.masm @@ -1,4 +1,4 @@ -use.miden::account +use.miden::active_account use.miden::account_id use.miden::active_note use.miden::contracts::faucets @@ -24,7 +24,7 @@ proc.is_owner push.OWNER_CONFIG_SLOT # => [owner_config_slot] - exec.account::get_item + exec.active_account::get_item # => [owner_prefix, owner_suffix, 0, 0] exec.active_note::get_sender diff --git a/crates/miden-lib/asm/miden/contracts/wallets/basic.masm b/crates/miden-lib/asm/miden/contracts/wallets/basic.masm index ae21b2d8e6..bfa7ea3d59 100644 --- a/crates/miden-lib/asm/miden/contracts/wallets/basic.masm +++ b/crates/miden-lib/asm/miden/contracts/wallets/basic.masm @@ -1,4 +1,4 @@ -use.miden::account +use.miden::native_account use.miden::output_note # CONSTANTS @@ -20,7 +20,7 @@ const.PUBLIC_NOTE=1 #! #! Invocation: call export.receive_asset - exec.account::add_asset + exec.native_account::add_asset # => [ASSET', pad(12)] # drop the final asset @@ -50,7 +50,7 @@ end #! Invocation: call export.move_asset_to_note # remove the asset from the account - exec.account::remove_asset + exec.native_account::remove_asset # => [ASSET, note_idx, pad(11)] exec.output_note::add_asset diff --git a/crates/miden-lib/asm/miden/faucet.masm b/crates/miden-lib/asm/miden/faucet.masm index f18f26b8a4..eca12de7d4 100644 --- a/crates/miden-lib/asm/miden/faucet.masm +++ b/crates/miden-lib/asm/miden/faucet.masm @@ -1,5 +1,5 @@ use.miden::asset -use.miden::account +use.miden::active_account use.miden::kernel_proc_offsets #! Creates a fungible asset for the faucet the transaction is being executed against. @@ -17,7 +17,7 @@ use.miden::kernel_proc_offsets #! Invocation: exec export.create_fungible_asset # fetch the id of the faucet the transaction is being executed against. - exec.account::get_id + exec.active_account::get_id # => [id_prefix, id_suffix, amount] # build the fungible asset @@ -40,7 +40,7 @@ end #! Invocation: exec export.create_non_fungible_asset # get the id of the faucet the transaction is being executed against - exec.account::get_id swap drop + exec.active_account::get_id swap drop # => [faucet_id_prefix, DATA_HASH] # build the non-fungible asset diff --git a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm index c791963f31..36d495edce 100644 --- a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm +++ b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm @@ -14,84 +14,83 @@ const.ACCOUNT_GET_NATIVE_ID_OFFSET=3 # Nonce const.ACCOUNT_GET_NONCE_OFFSET=4 # accessor const.ACCOUNT_INCR_NONCE_OFFSET=5 # mutator -const.ACCOUNT_GET_NATIVE_NONCE_OFFSET=6 # Code -const.ACCOUNT_GET_CODE_COMMITMENT_OFFSET=7 +const.ACCOUNT_GET_CODE_COMMITMENT_OFFSET=6 # Storage -const.ACCOUNT_GET_INITIAL_STORAGE_COMMITMENT_OFFSET=8 -const.ACCOUNT_COMPUTE_STORAGE_COMMITMENT_OFFSET=9 -const.ACCOUNT_GET_ITEM_OFFSET=10 -const.ACCOUNT_GET_INITIAL_ITEM_OFFSET=11 -const.ACCOUNT_SET_ITEM_OFFSET=12 -const.ACCOUNT_GET_MAP_ITEM_OFFSET=13 -const.ACCOUNT_GET_INITIAL_MAP_ITEM_OFFSET=14 -const.ACCOUNT_SET_MAP_ITEM_OFFSET=15 +const.ACCOUNT_GET_INITIAL_STORAGE_COMMITMENT_OFFSET=7 +const.ACCOUNT_COMPUTE_STORAGE_COMMITMENT_OFFSET=8 +const.ACCOUNT_GET_ITEM_OFFSET=9 +const.ACCOUNT_GET_INITIAL_ITEM_OFFSET=10 +const.ACCOUNT_SET_ITEM_OFFSET=11 +const.ACCOUNT_GET_MAP_ITEM_OFFSET=12 +const.ACCOUNT_GET_INITIAL_MAP_ITEM_OFFSET=13 +const.ACCOUNT_SET_MAP_ITEM_OFFSET=14 # Vault -const.ACCOUNT_GET_INITIAL_VAULT_ROOT_OFFSET=16 -const.ACCOUNT_GET_VAULT_ROOT_OFFSET=17 -const.ACCOUNT_ADD_ASSET_OFFSET=18 -const.ACCOUNT_REMOVE_ASSET_OFFSET=19 -const.ACCOUNT_GET_BALANCE_OFFSET=20 -const.ACCOUNT_GET_INITIAL_BALANCE_OFFSET=21 -const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=22 +const.ACCOUNT_GET_INITIAL_VAULT_ROOT_OFFSET=15 +const.ACCOUNT_GET_VAULT_ROOT_OFFSET=16 +const.ACCOUNT_ADD_ASSET_OFFSET=17 +const.ACCOUNT_REMOVE_ASSET_OFFSET=18 +const.ACCOUNT_GET_BALANCE_OFFSET=19 +const.ACCOUNT_GET_INITIAL_BALANCE_OFFSET=20 +const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=21 # Delta -const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=23 +const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=22 # Procedure introspection -const.ACCOUNT_GET_NUM_PROCEDURES_OFFSET=24 -const.ACCOUNT_GET_PROCEDURE_ROOT_OFFSET=25 -const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=26 -const.ACCOUNT_HAS_PROCEDURE_OFFSET=27 +const.ACCOUNT_GET_NUM_PROCEDURES_OFFSET=23 +const.ACCOUNT_GET_PROCEDURE_ROOT_OFFSET=24 +const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=25 +const.ACCOUNT_HAS_PROCEDURE_OFFSET=26 ### Faucet ###################################### -const.FAUCET_MINT_ASSET_OFFSET=28 -const.FAUCET_BURN_ASSET_OFFSET=29 -const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=30 -const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=31 +const.FAUCET_MINT_ASSET_OFFSET=27 +const.FAUCET_BURN_ASSET_OFFSET=28 +const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=29 +const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=30 ### Note ######################################## # input notes -const.INPUT_NOTE_GET_METADATA_OFFSET=32 -const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=33 -const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=34 -const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=35 -const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=36 -const.INPUT_NOTE_GET_RECIPIENT_OFFSET=37 +const.INPUT_NOTE_GET_METADATA_OFFSET=31 +const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=32 +const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=33 +const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=34 +const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=35 +const.INPUT_NOTE_GET_RECIPIENT_OFFSET=36 # output notes -const.OUTPUT_NOTE_CREATE_OFFSET=38 -const.OUTPUT_NOTE_GET_METADATA_OFFSET=39 -const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=40 -const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=41 -const.OUTPUT_NOTE_ADD_ASSET_OFFSET=42 +const.OUTPUT_NOTE_CREATE_OFFSET=37 +const.OUTPUT_NOTE_GET_METADATA_OFFSET=38 +const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=39 +const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=40 +const.OUTPUT_NOTE_ADD_ASSET_OFFSET=41 ### Tx ########################################## # input notes -const.TX_GET_NUM_INPUT_NOTES_OFFSET=43 -const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=44 +const.TX_GET_NUM_INPUT_NOTES_OFFSET=42 +const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=43 # output notes -const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=45 -const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=46 +const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=44 +const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=45 # block info -const.TX_GET_BLOCK_COMMITMENT_OFFSET=47 -const.TX_GET_BLOCK_NUMBER_OFFSET=48 -const.TX_GET_BLOCK_TIMESTAMP_OFFSET=49 +const.TX_GET_BLOCK_COMMITMENT_OFFSET=46 +const.TX_GET_BLOCK_NUMBER_OFFSET=47 +const.TX_GET_BLOCK_TIMESTAMP_OFFSET=48 # foreign context -const.TX_START_FOREIGN_CONTEXT_OFFSET=50 -const.TX_END_FOREIGN_CONTEXT_OFFSET=51 +const.TX_START_FOREIGN_CONTEXT_OFFSET=49 +const.TX_END_FOREIGN_CONTEXT_OFFSET=50 # expiration data -const.TX_GET_EXPIRATION_DELTA_OFFSET=52 # accessor -const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=53 # mutator +const.TX_GET_EXPIRATION_DELTA_OFFSET=51 # accessor +const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=52 # mutator # ACCESSORS # ------------------------------------------------------------------------------------------------- @@ -182,18 +181,6 @@ export.account_incr_nonce_offset push.ACCOUNT_INCR_NONCE_OFFSET end -#! Returns the offset of the `account_get_native_nonce` kernel procedure. -#! -#! Inputs: [] -#! Outputs: [proc_offset] -#! -#! Where: -#! - proc_offset is the offset of the `account_get_native_nonce` kernel procedure required to get -#! the address where this procedure is stored. -export.account_get_native_nonce_offset - push.ACCOUNT_GET_NATIVE_NONCE_OFFSET -end - #! Returns the offset of the `account_get_code_commitment` kernel procedure. #! #! Inputs: [] diff --git a/crates/miden-lib/asm/miden/native_account.masm b/crates/miden-lib/asm/miden/native_account.masm new file mode 100644 index 0000000000..d0bd83b44e --- /dev/null +++ b/crates/miden-lib/asm/miden/native_account.masm @@ -0,0 +1,274 @@ +use.miden::kernel_proc_offsets + +# NATIVE ACCOUNT PROCEDURES +# ================================================================================================= + +# ID AND NONCE +# ------------------------------------------------------------------------------------------------- + +#! Returns the ID of the native account of the transaction. +#! +#! Inputs: [] +#! Outputs: [account_id_prefix, account_id_suffix] +#! +#! Where: +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the native account ID of the +#! transaction. +#! +#! Invocation: exec +export.get_id + # start padding the stack + push.0.0.0 + + exec.kernel_proc_offsets::account_get_native_id_offset + # => [offset, 0, 0, 0] + + # pad the stack + padw swapw padw padw swapdw + # => [offset, pad(15)] + + syscall.exec_kernel_proc + # => [account_id_prefix, account_id_suffix, pad(14)] + + # clean the stack + swapdw dropw dropw swapw dropw movdn.3 movdn.3 drop drop + # => [account_id_prefix, account_id_suffix] +end + +#! Increments the nonce of the native account by one and returns the new nonce. +#! +#! Inputs: [] +#! Outputs: [final_nonce] +#! +#! Where: +#! - final_nonce is the new nonce of the account. Since it cannot be incremented again, this will +#! also be the final nonce of the account after transaction execution. +#! +#! Panics if: +#! - the invocation of this procedure does not originate from the native account. +#! - the invocation of this procedure does not originate from the authentication procedure +#! of the account. +#! - the nonce has already been incremented. +#! +#! Invocation: exec +export.incr_nonce + # pad the stack + padw padw padw push.0.0.0 + # => [pad(15)] + + exec.kernel_proc_offsets::account_incr_nonce_offset + # => [offset, pad(15)] + + syscall.exec_kernel_proc + # => [final_nonce, pad(15)] + + swap.15 dropw dropw dropw drop drop drop + # => [final_nonce] +end + +# COMMITMENTS +# ------------------------------------------------------------------------------------------------- + +#! Computes the commitment to the native account's delta. +#! +#! Note that if the account state has changed, the nonce must be incremented before this procedure +#! is called, otherwise it will panic. This means it can only be called from an auth procedure, +#! since only auth procedures are allowed to increment the nonce. +#! +#! The commitment to an empty delta is defined as the empty word. +#! +#! During an account-creating transaction (when the initial nonce is 0), this procedure will not +#! return the empty word even if the initial storage commitment and the current storage commitment +#! are identical (storage hasn't changed). This is because the delta for a new account must +#! represent its entire newly created state, and the initial storage in a transaction is initialized +#! to the storage that the account ID commits to, which may be non-empty. This does not have any +#! consequences other than being inconsistent in this edge case. +#! +#! Inputs: [] +#! Outputs: [DELTA_COMMITMENT] +#! +#! Where: +#! - DELTA_COMMITMENT is the commitment to the account delta. +#! +#! Panics if: +#! - the vault or storage delta is not empty but the nonce increment is zero. +export.compute_delta_commitment + # pad the stack + padw padw padw push.0.0.0 + # => [pad(15)] + + exec.kernel_proc_offsets::account_compute_delta_commitment_offset + # => [offset, pad(15)] + + syscall.exec_kernel_proc + # => [DELTA_COMMITMENT, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [DELTA_COMMITMENT] +end + +# STORAGE +# ------------------------------------------------------------------------------------------------- + +#! Sets an item in the native account storage. +#! +#! Inputs: [index, VALUE] +#! Outputs: [OLD_VALUE] +#! +#! Where: +#! - index is the index of the item to set. +#! - VALUE is the value to set. +#! - OLD_VALUE is the previous value of the item. +#! +#! Panics if: +#! - the index of the item is out of bounds. +#! +#! Invocation: exec +export.set_item + exec.kernel_proc_offsets::account_set_item_offset + # => [offset, index, VALUE] + + # pad the stack + push.0.0 movdn.7 movdn.7 padw padw swapdw + # => [offset, index, VALUE, pad(10)] + + syscall.exec_kernel_proc + # => [OLD_VALUE, pad(12)] + + # clean the stack + swapw.3 dropw dropw dropw + # => [OLD_VALUE] +end + +#! Sets a map item in the native account storage. +#! +#! Inputs: [index, KEY, VALUE] +#! Outputs: [OLD_MAP_ROOT, OLD_MAP_VALUE] +#! +#! Where: +#! - index is the index of the map where the KEY VALUE should be set. +#! - KEY is the key to set at VALUE. +#! - VALUE is the value to set at KEY. +#! - OLD_MAP_ROOT is the old map root. +#! - OLD_MAP_VALUE is the old value at KEY. +#! +#! Panics if: +#! - the index for the map is out of bounds, meaning > 255. +#! - the slot item at index is not a map. +#! +#! Invocation: exec +export.set_map_item + exec.kernel_proc_offsets::account_set_map_item_offset + # => [offset, index, KEY, VALUE] + + # pad the stack + push.0.0 movdn.11 movdn.11 padw movdnw.3 + # => [offset, index, KEY, VALUE, pad(6)] + + syscall.exec_kernel_proc + # => [OLD_MAP_ROOT, OLD_MAP_VALUE, pad(8)] + + # clean the stack + swapdw dropw dropw + # => [OLD_MAP_ROOT, OLD_MAP_VALUE] +end + +# VAULT +# ------------------------------------------------------------------------------------------------- + +#! Add the specified asset to the vault. +#! +#! Inputs: [ASSET] +#! Outputs: [ASSET'] +#! +#! Where: +#! - ASSET' is a final asset in the account vault defined as follows: +#! - If ASSET is a non-fungible asset, then ASSET' is the same as ASSET. +#! - If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault +#! after ASSET was added to it. +#! +#! Panics if: +#! - the asset is not valid. +#! - the total value of two fungible assets is greater than or equal to 2^63. +#! - the vault already contains the same non-fungible asset. +#! +#! Invocation: exec +export.add_asset + exec.kernel_proc_offsets::account_add_asset_offset + # => [offset, ASSET] + + # pad the stack + push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw + # => [offset, ASSET, pad(11)] + + syscall.exec_kernel_proc + # => [ASSET', pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [ASSET'] +end + +#! Remove the specified asset from the vault. +#! +#! Inputs: [ASSET] +#! Outputs: [ASSET] +#! +#! Where: +#! - ASSET is the asset to remove from the vault. +#! +#! Panics if: +#! - the fungible asset is not found in the vault. +#! - the amount of the fungible asset in the vault is less than the amount to be removed. +#! - the non-fungible asset is not found in the vault. +#! +#! Invocation: exec +export.remove_asset + exec.kernel_proc_offsets::account_remove_asset_offset + # => [offset, ASSET] + + # pad the stack + push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw + # => [offset, ASSET, pad(11)] + + syscall.exec_kernel_proc + # => [ASSET, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [ASSET] +end + +# CODE +# ------------------------------------------------------------------------------------------------- + +#! Returns 1 if a native account procedure was called during transaction execution, and 0 otherwise. +#! +#! Note: This returns 1 only if the procedure invoked account-restricted kernel APIs (e.g., +#! `exec.faucet::mint`) which trigger `authenticate_and_track_procedure`. Procedures that execute +#! only local MASM instructions will return 0 even if they were executed. +#! +#! Inputs: [PROC_ROOT] +#! Outputs: [was_called] +#! +#! Where: +#! - PROC_ROOT is the hash of the procedure to check. +#! - was_called is 1 if the procedure was called, 0 otherwise. +#! +#! Invocation: exec +export.was_procedure_called + exec.kernel_proc_offsets::account_was_procedure_called_offset + # => [offset, PROC_ROOT] + + # pad the stack + push.0.0.0 movdn.7 movdn.7 movdn.7 padw padw swapdw + # => [offset, PROC_ROOT, pad(11)] + + syscall.exec_kernel_proc + # => [was_called, pad(15)] + + # clean the stack + swapdw dropw dropw swapw dropw movdn.3 drop drop drop + # => [was_called] +end diff --git a/crates/miden-lib/asm/note_scripts/P2ID.masm b/crates/miden-lib/asm/note_scripts/P2ID.masm index 3f95e326d3..884d20a1a4 100644 --- a/crates/miden-lib/asm/note_scripts/P2ID.masm +++ b/crates/miden-lib/asm/note_scripts/P2ID.masm @@ -1,4 +1,4 @@ -use.miden::account +use.miden::active_account use.miden::account_id use.miden::active_note @@ -40,7 +40,7 @@ begin mem_loadw drop drop # => [target_account_id_prefix, target_account_id_suffix] - exec.account::get_id + exec.active_account::get_id # => [account_id_prefix, account_id_suffix, target_account_id_prefix, target_account_id_suffix, ...] # ensure account_id = target_account_id, fails otherwise diff --git a/crates/miden-lib/asm/note_scripts/P2IDE.masm b/crates/miden-lib/asm/note_scripts/P2IDE.masm index 34a6095d32..42ccc111c9 100644 --- a/crates/miden-lib/asm/note_scripts/P2IDE.masm +++ b/crates/miden-lib/asm/note_scripts/P2IDE.masm @@ -1,4 +1,4 @@ -use.miden::account +use.miden::active_account use.miden::account_id use.miden::active_note use.miden::tx @@ -121,7 +121,7 @@ begin # => [current_block_height, reclaim_block_height, target_account_id_prefix, target_account_id_suffix] # get current account id - exec.account::get_id dup.1 dup.1 + exec.active_account::get_id dup.1 dup.1 # => [account_id_prefix, account_id_suffix, account_id_prefix, account_id_suffix, current_block_height, reclaim_block_height, target_account_id_prefix, target_account_id_suffix] # determine if the current account is the target account diff --git a/crates/miden-lib/src/lib.rs b/crates/miden-lib/src/lib.rs index 5f377b7281..85e2797b9a 100644 --- a/crates/miden-lib/src/lib.rs +++ b/crates/miden-lib/src/lib.rs @@ -82,7 +82,7 @@ mod tests { #[test] fn test_compile() { - let path = "miden::account::get_id".parse::().unwrap(); + let path = "miden::active_account::get_id".parse::().unwrap(); let miden = MidenLib::default(); let exists = miden.0.module_infos().any(|module| { module diff --git a/crates/miden-lib/src/testing/account_component/conditional_auth.rs b/crates/miden-lib/src/testing/account_component/conditional_auth.rs index 4cbdff0ced..40e847e26f 100644 --- a/crates/miden-lib/src/testing/account_component/conditional_auth.rs +++ b/crates/miden-lib/src/testing/account_component/conditional_auth.rs @@ -11,7 +11,7 @@ pub const ERR_WRONG_ARGS_MSG: &str = "auth procedure args are incorrect"; static CONDITIONAL_AUTH_CODE: LazyLock = LazyLock::new(|| { format!( r#" - use.miden::account + use.miden::native_account const.WRONG_ARGS="{ERR_WRONG_ARGS_MSG}" @@ -26,7 +26,7 @@ static CONDITIONAL_AUTH_CODE: LazyLock = LazyLock::new(|| { # Last element is the incr_nonce_flag. if.true - exec.account::incr_nonce drop + exec.native_account::incr_nonce drop end dropw dropw dropw dropw end diff --git a/crates/miden-lib/src/testing/account_component/incr_nonce.rs b/crates/miden-lib/src/testing/account_component/incr_nonce.rs index c2973f9d63..1af38a1529 100644 --- a/crates/miden-lib/src/testing/account_component/incr_nonce.rs +++ b/crates/miden-lib/src/testing/account_component/incr_nonce.rs @@ -5,10 +5,10 @@ use miden_objects::utils::sync::LazyLock; use crate::transaction::TransactionKernel; const INCR_NONCE_AUTH_CODE: &str = " - use.miden::account + use.miden::native_account export.auth_incr_nonce - exec.account::incr_nonce drop + exec.native_account::incr_nonce drop end "; diff --git a/crates/miden-lib/src/testing/mock_account_code.rs b/crates/miden-lib/src/testing/mock_account_code.rs index 0d82d94f11..50c757ba81 100644 --- a/crates/miden-lib/src/testing/mock_account_code.rs +++ b/crates/miden-lib/src/testing/mock_account_code.rs @@ -24,7 +24,8 @@ const MOCK_FAUCET_CODE: &str = " "; const MOCK_ACCOUNT_CODE: &str = " - use.miden::account + use.miden::active_account + use.miden::native_account use.miden::tx export.::miden::contracts::wallets::basic::receive_asset @@ -37,14 +38,14 @@ const MOCK_ACCOUNT_CODE: &str = " # Stack: [index, VALUE_TO_SET, pad(11)] # Output: [PREVIOUS_STORAGE_VALUE, pad(12)] export.set_item - exec.account::set_item + exec.native_account::set_item # => [V, pad(12)] end # Stack: [index, pad(15)] # Output: [VALUE, pad(12)] export.get_item - exec.account::get_item + exec.active_account::get_item # => [VALUE, pad(15)] # truncate the stack @@ -55,7 +56,7 @@ const MOCK_ACCOUNT_CODE: &str = " # Stack: [index, pad(15)] # Output: [VALUE, pad(12)] export.get_initial_item - exec.account::get_initial_item + exec.active_account::get_initial_item # => [VALUE, pad(15)] # truncate the stack @@ -66,26 +67,26 @@ const MOCK_ACCOUNT_CODE: &str = " # Stack: [index, KEY, VALUE, pad(7)] # Output: [OLD_MAP_ROOT, OLD_MAP_VALUE, pad(8)] export.set_map_item - exec.account::set_map_item + exec.native_account::set_map_item # => [R', V, pad(8)] end # Stack: [index, KEY, pad(11)] # Output: [VALUE, pad(12)] export.get_map_item - exec.account::get_map_item + exec.active_account::get_map_item end # Stack: [index, KEY, pad(11)] # Output: [VALUE, pad(12)] export.get_initial_map_item - exec.account::get_initial_map_item + exec.active_account::get_initial_map_item end # Stack: [pad(16)] # Output: [CODE_COMMITMENT, pad(12)] export.get_code_commitment - exec.account::get_code_commitment + exec.active_account::get_code_commitment # => [CODE_COMMITMENT, pad(16)] # truncate the stack @@ -96,7 +97,7 @@ const MOCK_ACCOUNT_CODE: &str = " # Stack: [pad(16)] # Output: [CODE_COMMITMENT, pad(12)] export.compute_storage_commitment - exec.account::compute_storage_commitment + exec.active_account::compute_storage_commitment # => [STORAGE_COMMITMENT, pad(16)] swapw dropw @@ -106,14 +107,14 @@ const MOCK_ACCOUNT_CODE: &str = " # Stack: [ASSET, pad(12)] # Output: [ASSET', pad(12)] export.add_asset - exec.account::add_asset + exec.native_account::add_asset # => [ASSET', pad(12)] end # Stack: [ASSET, pad(12)] # Output: [ASSET, pad(12)] export.remove_asset - exec.account::remove_asset + exec.native_account::remove_asset # => [ASSET, pad(12)] end diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index fddf09b3d4..91dd86b8d8 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -6,9 +6,9 @@ use miden_objects::{Word, word}; // ================================================================================================ /// Hashes of all dynamically executed kernel procedures. -pub const KERNEL_PROCEDURES: [Word; 54] = [ +pub const KERNEL_PROCEDURES: [Word; 53] = [ // account_get_initial_commitment - word!("0x920898348bacd6d98a399301eb308478fd32b32eab019a5a6ef7a6b44abb61f6"), + word!("0x1c95a0386ebf3645c6271253a4ae49ea4be8610dea7b4436c58951277a75f0c1"), // account_compute_current_commitment word!("0x1aed40e2cc4d3798448f4efdce1a14c9598611da065eebe58432f144c3bca9de"), // account_get_id @@ -19,12 +19,10 @@ pub const KERNEL_PROCEDURES: [Word; 54] = [ word!("0x4a1f11db21ddb1f0ebf7c9fd244f896a95e99bb136008185da3e7d6aa85827a3"), // account_incr_nonce word!("0x99c6b16e86eb9eae02657256b8859e2809acd05bf25810933cf50e72d876d8bf"), - // account_get_native_nonce - word!("0xeae4dcae877e64a1951aa1ca35ac2adda724e359ee9c7689e55c42dde55d70c4"), // account_get_code_commitment word!("0xb2ebd0acc4ef40d37c403190dc07d03d7df9169fb8752e8025e3fe469b5ee192"), // account_get_initial_storage_commitment - word!("0x5932cb0394cc85330e65e4c0325bb869ec1d08b295c310f7375076a98b143d57"), + word!("0x91377c2852feb7a2798e54d7dbaa2d97000270ec4c0d0888b26d720a25ae0e84"), // account_compute_storage_commitment word!("0xa87008550383e1a88dde5d0adefc68ee3bf477aec07e4700f9101241aa1e868f"), // account_get_item @@ -40,7 +38,7 @@ pub const KERNEL_PROCEDURES: [Word; 54] = [ // account_set_map_item word!("0x6ee1674cb94eaf4e23383abbfe918bff742ec13f69150eaf66cc9ea0243f4a7e"), // account_get_initial_vault_root - word!("0x4d4d91079aaacad1bc86b29a0d61d25508ccb705c29d1b1357016f7373bf299e"), + word!("0x46297d9ac95afd60c7ef1a065e024ad49aa4c019f6b3924191905449b244d4ec"), // account_get_vault_root word!("0x42a2bfb8eac4fce9bbf75ea15215b00729faeeaf7fff784692948d3f618a9bb7"), // account_add_asset @@ -60,7 +58,7 @@ pub const KERNEL_PROCEDURES: [Word; 54] = [ // account_get_procedure_root word!("0x4d7b2e6083820088cd1139ed658b631cf391989b16de8af6741d7e17de9245cd"), // account_was_procedure_called - word!("0x34f27a609f2f2b4fec454b17182552b0acc52524e507e134257a1f1ed30a57cd"), + word!("0x37f1f8e67ec9105153721ad59c1f765f58da8f87b0b8eea303701bf4583a5b89"), // account_has_procedure word!("0x667d5ce1b7a54c3b8965666ce90e59085c97775b82eba25dddfe218db5fe137d"), // faucet_mint_asset diff --git a/crates/miden-lib/src/transaction/memory.rs b/crates/miden-lib/src/transaction/memory.rs index 11bff29cbd..db503d2693 100644 --- a/crates/miden-lib/src/transaction/memory.rs +++ b/crates/miden-lib/src/transaction/memory.rs @@ -32,21 +32,21 @@ pub type StorageSlot = u8; // // Here the "end pointer" is the last memory pointer occupied by the current data // -// | Section | Start address, pointer (word pointer) | End address, pointer (word pointer) | Comment | -// | ----------------- | :-----------------------------------: | :---------------------------------: | ----------------------------------- | -// | ID and nonce | 0 (0) | 3 (0) | | -// | Vault root | 4 (1) | 7 (1) | | -// | Storage root | 8 (2) | 11 (2) | | -// | Code root | 12 (3) | 15 (3) | | -// | Padding | 16 (4) | 27 (6) | | -// | Num procedures | 28 (7) | 31 (7) | | -// | Procedures info | 32 (8) | 2_079 (519) | 255 procedures max, 8 elements each | -// | Padding | 2_080 (520) | 2_083 (520) | | -// | Proc tracking | 2_084 (521) | 2_339 (584) | 255 procedures max, 1 element each | -// | Num storage slots | 2_340 (585) | 2_343 (585) | | -// | Storage slot info | 2_344 (586) | 4_383 (1095) | 255 slots max, 8 elements each | -// | Initial slot info | 4_384 (1096) | 6_423 (1545) | Only present on the native account | -// | Padding | 6_424 (1545) | 8_191 (2047) | | +// | Section | Start address, pointer (word pointer) | End address, pointer (word pointer) | Comment | +// | ------------------ | :-----------------------------------: | :---------------------------------: | ----------------------------------- | +// | ID and nonce | 0 (0) | 3 (0) | | +// | Vault root | 4 (1) | 7 (1) | | +// | Storage commitment | 8 (2) | 11 (2) | | +// | Code commitment | 12 (3) | 15 (3) | | +// | Padding | 16 (4) | 27 (6) | | +// | Num procedures | 28 (7) | 31 (7) | | +// | Procedures info | 32 (8) | 2_079 (519) | 255 procedures max, 8 elements each | +// | Padding | 2_080 (520) | 2_083 (520) | | +// | Proc tracking | 2_084 (521) | 2_339 (584) | 255 procedures max, 1 element each | +// | Num storage slots | 2_340 (585) | 2_343 (585) | | +// | Storage slot info | 2_344 (586) | 4_383 (1095) | 255 slots max, 8 elements each | +// | Initial slot info | 4_384 (1096) | 6_423 (1545) | Only present on the native account | +// | Padding | 6_424 (1545) | 8_191 (2047) | | // Relative layout of the native account's delta. // diff --git a/crates/miden-lib/src/utils/script_builder.rs b/crates/miden-lib/src/utils/script_builder.rs index 026f62bbad..e2df108f5e 100644 --- a/crates/miden-lib/src/utils/script_builder.rs +++ b/crates/miden-lib/src/utils/script_builder.rs @@ -352,20 +352,23 @@ mod tests { fn test_create_library_and_create_tx_script() -> anyhow::Result<()> { let script_code = " use.external_contract::counter_contract + begin call.counter_contract::increment end "; let account_code = " - use.miden::account + use.miden::active_account + use.miden::native_account use.std::sys + export.increment push.0 - exec.account::get_item + exec.active_account::get_item push.1 add push.0 - exec.account::set_item + exec.native_account::set_item exec.sys::truncate_stack end "; @@ -387,20 +390,23 @@ mod tests { fn test_compile_library_and_add_to_builder() -> anyhow::Result<()> { let script_code = " use.external_contract::counter_contract + begin call.counter_contract::increment end "; let account_code = " - use.miden::account + use.miden::active_account + use.miden::native_account use.std::sys + export.increment push.0 - exec.account::get_item + exec.active_account::get_item push.1 add push.0 - exec.account::set_item + exec.native_account::set_item exec.sys::truncate_stack end "; @@ -435,20 +441,23 @@ mod tests { fn test_builder_style_chaining() -> anyhow::Result<()> { let script_code = " use.external_contract::counter_contract + begin call.counter_contract::increment end "; let account_code = " - use.miden::account + use.miden::active_account + use.miden::native_account use.std::sys + export.increment push.0 - exec.account::get_item + exec.active_account::get_item push.1 add push.0 - exec.account::set_item + exec.native_account::set_item exec.sys::truncate_stack end "; @@ -493,27 +502,31 @@ mod tests { "; let account_code_1 = " - use.miden::account + use.miden::active_account + use.miden::native_account use.std::sys + export.increment_1 push.0 - exec.account::get_item + exec.active_account::get_item push.1 add push.0 - exec.account::set_item + exec.native_account::set_item exec.sys::truncate_stack end "; let account_code_2 = " - use.miden::account + use.miden::active_account + use.miden::native_account use.std::sys + export.increment_2 push.0 - exec.account::get_item + exec.active_account::get_item push.2 add push.0 - exec.account::set_item + exec.native_account::set_item exec.sys::truncate_stack end "; diff --git a/crates/miden-objects/src/account/storage/map/mod.rs b/crates/miden-objects/src/account/storage/map/mod.rs index a9b79cacc1..4b262f88ed 100644 --- a/crates/miden-objects/src/account/storage/map/mod.rs +++ b/crates/miden-objects/src/account/storage/map/mod.rs @@ -26,7 +26,7 @@ pub const EMPTY_STORAGE_MAP_ROOT: Word = *EmptySubtreeRoots::entry(StorageMap::D /// It can be used to store a large amount of data in an account than would be otherwise possible /// using just the account's storage slots. This works by storing the root of the map's underlying /// SMT in one account storage slot. Each map entry is a leaf in the tree and its inclusion is -/// proven while retrieving it (e.g. via `account::get_map_item`). +/// proven while retrieving it (e.g. via `active_account::get_map_item`). /// /// As a side-effect, this also means that _not all_ entries of the map have to be present at /// transaction execution time in order to access or modify the map. It is sufficient if _just_ the diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 67dff516e7..874564229e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -85,14 +85,14 @@ pub async fn compute_current_commitment() -> miette::Result<()> { use.std::word use.miden::prologue - use.miden::account + use.miden::active_account use.mock::account->mock_account begin - exec.account::get_initial_commitment + exec.active_account::get_initial_commitment # => [INITIAL_COMMITMENT] - exec.account::compute_current_commitment + exec.active_account::compute_commitment # => [CURRENT_COMMITMENT, INITIAL_COMMITMENT] assert_eqw.err="initial and current commitment should be equal when no changes have been made" @@ -114,7 +114,7 @@ pub async fn compute_current_commitment() -> miette::Result<()> { # => [STORAGE_COMMITMENT0] # compute the commitment which will recompute the storage commitment - exec.account::compute_current_commitment + exec.active_account::compute_commitment # => [CURRENT_COMMITMENT, STORAGE_COMMITMENT0] push.{expected_commitment} @@ -563,7 +563,6 @@ async fn test_set_map_item() -> miette::Result<()> { " use.std::sys - use.mock::account use.$kernel::prologue use.mock::account->mock_account @@ -625,18 +624,19 @@ async fn test_account_component_storage_offset() -> miette::Result<()> { // insuring consistent "set" and "get" using offsets. let source_code_component1 = " use.std::word - use.miden::account + use.miden::active_account + use.miden::native_account export.foo_write push.1.2.3.4.0 - exec.account::set_item + exec.native_account::set_item dropw end export.foo_read push.0 - exec.account::get_item + exec.active_account::get_item push.1.2.3.4 exec.word::eq assert @@ -645,18 +645,19 @@ async fn test_account_component_storage_offset() -> miette::Result<()> { let source_code_component2 = " use.std::word - use.miden::account + use.miden::active_account + use.miden::native_account export.bar_write push.5.6.7.8.0 - exec.account::set_item + exec.native_account::set_item dropw end export.bar_read push.0 - exec.account::get_item + exec.active_account::get_item push.5.6.7.8 exec.word::eq assert @@ -893,14 +894,14 @@ async fn test_get_initial_storage_commitment() -> anyhow::Result<()> { let code = format!( r#" - use.miden::account + use.miden::active_account use.$kernel::prologue begin exec.prologue::prepare_transaction # get the initial storage commitment - exec.account::get_initial_storage_commitment + exec.active_account::get_initial_storage_commitment push.{expected_storage_commitment} assert_eqw.err="actual storage commitment is not equal to the expected one" end @@ -1140,14 +1141,14 @@ async fn test_get_vault_root() -> anyhow::Result<()> { // get the initial vault root let code = format!( " - use.miden::account + use.miden::active_account use.$kernel::prologue begin exec.prologue::prepare_transaction # get the initial vault root - exec.account::get_initial_vault_root + exec.active_account::get_initial_vault_root push.{expected_vault_root} assert_eqw end @@ -1161,7 +1162,7 @@ async fn test_get_vault_root() -> anyhow::Result<()> { let code = format!( r#" - use.miden::account + use.miden::active_account use.$kernel::prologue use.mock::account->mock_account @@ -1174,7 +1175,7 @@ async fn test_get_vault_root() -> anyhow::Result<()> { # => [] # get the current vault root - exec.account::get_vault_root + exec.active_account::get_vault_root push.{expected_vault_root} assert_eqw.err="actual vault root is not equal to the expected one" end @@ -1187,13 +1188,13 @@ async fn test_get_vault_root() -> anyhow::Result<()> { Ok(()) } -/// This test checks the correctness of the `miden::account::get_initial_balance` procedure in two -/// cases: +/// This test checks the correctness of the `miden::active_account::get_initial_balance` procedure +/// in two cases: /// - when a note adds the asset which already exists in the account vault. /// - when a note adds the asset which doesn't exist in the account vault. /// /// As part of the test pipeline it also checks the correctness of the -/// `miden::account::get_balance` procedure. +/// `miden::active_account::get_balance` procedure. #[tokio::test] async fn test_get_init_balance_addition() -> anyhow::Result<()> { // prepare the testing data @@ -1245,7 +1246,7 @@ async fn test_get_init_balance_addition() -> anyhow::Result<()> { let add_existing_source = format!( r#" - use.miden::account + use.miden::active_account begin # push faucet ID prefix and suffix @@ -1253,7 +1254,7 @@ async fn test_get_init_balance_addition() -> anyhow::Result<()> { # => [faucet_id_prefix, faucet_id_suffix] # get the current asset balance - dup.1 dup.1 exec.account::get_balance + dup.1 dup.1 exec.active_account::get_balance # => [final_balance, faucet_id_prefix, faucet_id_suffix] # assert final balance is correct @@ -1262,7 +1263,7 @@ async fn test_get_init_balance_addition() -> anyhow::Result<()> { # => [faucet_id_prefix, faucet_id_suffix] # get the initial asset balance - exec.account::get_initial_balance + exec.active_account::get_initial_balance # => [init_balance] # assert initial balance is correct @@ -1299,7 +1300,7 @@ async fn test_get_init_balance_addition() -> anyhow::Result<()> { let add_new_source = format!( r#" - use.miden::account + use.miden::active_account begin # push faucet ID prefix and suffix @@ -1307,7 +1308,7 @@ async fn test_get_init_balance_addition() -> anyhow::Result<()> { # => [faucet_id_prefix, faucet_id_suffix] # get the current asset balance - dup.1 dup.1 exec.account::get_balance + dup.1 dup.1 exec.active_account::get_balance # => [final_balance, faucet_id_prefix, faucet_id_suffix] # assert final balance is correct @@ -1316,7 +1317,7 @@ async fn test_get_init_balance_addition() -> anyhow::Result<()> { # => [faucet_id_prefix, faucet_id_suffix] # get the initial asset balance - exec.account::get_initial_balance + exec.active_account::get_initial_balance # => [init_balance] # assert initial balance is correct @@ -1341,11 +1342,11 @@ async fn test_get_init_balance_addition() -> anyhow::Result<()> { Ok(()) } -/// This test checks the correctness of the `miden::account::get_initial_balance` procedure in case -/// when we create a note which removes an asset from the account vault. +/// This test checks the correctness of the `miden::active_account::get_initial_balance` procedure +/// in case when we create a note which removes an asset from the account vault. /// /// As part of the test pipeline it also checks the correctness of the -/// `miden::account::get_balance` procedure. +/// `miden::active_account::get_balance` procedure. #[tokio::test] async fn test_get_init_balance_subtraction() -> anyhow::Result<()> { let mut builder = MockChain::builder(); @@ -1376,7 +1377,7 @@ async fn test_get_init_balance_subtraction() -> anyhow::Result<()> { let remove_existing_source = format!( r#" - use.miden::account + use.miden::active_account use.miden::contracts::wallets::basic->wallet use.mock::util @@ -1409,7 +1410,7 @@ async fn test_get_init_balance_subtraction() -> anyhow::Result<()> { # => [faucet_id_prefix, faucet_id_suffix] # get the current asset balance - dup.1 dup.1 exec.account::get_balance + dup.1 dup.1 exec.active_account::get_balance # => [final_balance, faucet_id_prefix, faucet_id_suffix] # assert final balance is correct @@ -1418,7 +1419,7 @@ async fn test_get_init_balance_subtraction() -> anyhow::Result<()> { # => [faucet_id_prefix, faucet_id_suffix] # get the initial asset balance - exec.account::get_initial_balance + exec.active_account::get_initial_balance # => [init_balance] # assert initial balance is correct @@ -1527,12 +1528,12 @@ async fn test_was_procedure_called() -> miette::Result<()> { // 5. Checks that `was_procedure_called` returns `true` let tx_script_code = r#" use.mock::account->mock_account - use.miden::account + use.miden::native_account begin # First check that get_item procedure hasn't been called yet procref.mock_account::get_item - exec.account::was_procedure_called + exec.native_account::was_procedure_called assertz.err="procedure should not have been called" # Call the procedure first time @@ -1541,7 +1542,7 @@ async fn test_was_procedure_called() -> miette::Result<()> { # => [] procref.mock_account::get_item - exec.account::was_procedure_called + exec.native_account::was_procedure_called assert.err="procedure should have been called" # Call the procedure second time @@ -1549,7 +1550,7 @@ async fn test_was_procedure_called() -> miette::Result<()> { call.mock_account::get_item dropw procref.mock_account::get_item - exec.account::was_procedure_called + exec.native_account::was_procedure_called assert.err="2nd call should not change the was_called flag" end "#; @@ -1580,12 +1581,12 @@ async fn test_was_procedure_called() -> miette::Result<()> { #[tokio::test] async fn transaction_executor_account_code_using_custom_library() -> miette::Result<()> { const EXTERNAL_LIBRARY_CODE: &str = r#" - use.miden::account + use.miden::native_account export.external_setter push.2.3.4.5 push.0 - exec.account::set_item + exec.native_account::set_item dropw dropw end"#; @@ -1657,11 +1658,11 @@ async fn transaction_executor_account_code_using_custom_library() -> miette::Res #[tokio::test] async fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { let source_code = " - use.miden::account + use.miden::native_account export.auth_incr_nonce_twice - exec.account::incr_nonce drop - exec.account::incr_nonce drop + exec.native_account::incr_nonce drop + exec.native_account::incr_nonce drop end "; @@ -1693,14 +1694,14 @@ async fn test_has_procedure() -> miette::Result<()> { let tx_script_code = r#" use.mock::account->mock_account - use.miden::account + use.miden::active_account begin # check that get_item procedure is available on the mock account procref.mock_account::get_item # => [GET_ITEM_ROOT] - exec.account::has_procedure + exec.active_account::has_procedure # => [is_procedure_available] # assert that the get_item is exposed @@ -1709,7 +1710,7 @@ async fn test_has_procedure() -> miette::Result<()> { # get some random word and assert that it is not exposed push.5.3.15.686 - exec.account::has_procedure + exec.active_account::has_procedure # => [is_procedure_available] # assert that the procedure with some random root is not exposed diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 2afd5ea086..83661b5f61 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -29,13 +29,13 @@ async fn get_balance_returns_correct_amount() -> anyhow::Result<()> { let code = format!( r#" use.$kernel::prologue - use.miden::account + use.miden::active_account begin exec.prologue::prepare_transaction push.{suffix} push.{prefix} - exec.account::get_balance + exec.active_account::get_balance # => [balance] # truncate the stack @@ -114,12 +114,12 @@ async fn test_get_balance_non_fungible_fails() -> anyhow::Result<()> { let code = format!( " use.$kernel::prologue - use.miden::account + use.miden::active_account begin exec.prologue::prepare_transaction push.{suffix} push.{prefix} - exec.account::get_balance + exec.active_account::get_balance end ", prefix = faucet_id.prefix().as_felt(), @@ -145,12 +145,12 @@ async fn test_has_non_fungible_asset() -> anyhow::Result<()> { let code = format!( " use.$kernel::prologue - use.miden::account + use.miden::active_account begin exec.prologue::prepare_transaction push.{non_fungible_asset_key} - exec.account::has_non_fungible_asset + exec.active_account::has_non_fungible_asset # truncate the stack swap drop diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index 0aa019ca27..be8921c020 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -62,13 +62,13 @@ async fn test_fpi_memory_single_account() -> anyhow::Result<()> { let storage_slots = vec![AccountStorage::mock_item_0().slot, AccountStorage::mock_item_2().slot]; let foreign_account_code_source = " - use.miden::account + use.miden::active_account export.get_item_foreign # make this foreign procedure unique to make sure that we invoke the procedure of the # foreign account, not the native one push.1 drop - exec.account::get_item + exec.active_account::get_item # truncate the stack movup.6 movup.6 movup.6 drop drop drop @@ -78,7 +78,7 @@ async fn test_fpi_memory_single_account() -> anyhow::Result<()> { # make this foreign procedure unique to make sure that we invoke the procedure of the # foreign account, not the native one push.2 drop - exec.account::get_map_item + exec.active_account::get_map_item end "; @@ -305,26 +305,26 @@ async fn test_fpi_memory_two_accounts() -> anyhow::Result<()> { let storage_slots_2 = vec![AccountStorage::mock_item_1().slot]; let foreign_account_code_source_1 = " - use.miden::account + use.miden::active_account export.get_item_foreign_1 # make this foreign procedure unique to make sure that we invoke the procedure of the # foreign account, not the native one push.1 drop - exec.account::get_item + exec.active_account::get_item # truncate the stack movup.6 movup.6 movup.6 drop drop drop end "; let foreign_account_code_source_2 = " - use.miden::account + use.miden::active_account export.get_item_foreign_2 # make this foreign procedure unique to make sure that we invoke the procedure of the # foreign account, not the native one push.2 drop - exec.account::get_item + exec.active_account::get_item # truncate the stack movup.6 movup.6 movup.6 drop drop drop @@ -517,13 +517,13 @@ async fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { let storage_slots = vec![AccountStorage::mock_item_0().slot, AccountStorage::mock_item_2().slot]; let foreign_account_code_source = " - use.miden::account + use.miden::active_account export.get_item_foreign # make this foreign procedure unique to make sure that we invoke the procedure of the # foreign account, not the native one push.1 drop - exec.account::get_item + exec.active_account::get_item # truncate the stack movup.6 movup.6 movup.6 drop drop drop @@ -533,7 +533,7 @@ async fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { # make this foreign procedure unique to make sure that we invoke the procedure of the # foreign account, not the native one push.2 drop - exec.account::get_map_item + exec.active_account::get_map_item end "; @@ -566,7 +566,6 @@ async fn test_fpi_execute_foreign_procedure() -> anyhow::Result<()> { use.std::sys use.miden::tx - use.miden::account begin # get the storage item at index 0 @@ -661,17 +660,17 @@ async fn foreign_account_can_get_balance_and_presence_of_asset() -> anyhow::Resu let foreign_account_code_source = format!( " - use.miden::account + use.miden::active_account export.get_asset_balance # get balance of first asset push.{fungible_faucet_id_suffix} push.{fungible_faucet_id_prefix} - exec.account::get_balance + exec.active_account::get_balance # => [balance] # check presence of non fungible asset push.{non_fungible_asset_word} - exec.account::has_non_fungible_asset + exec.active_account::has_non_fungible_asset # => [has_asset, balance] # add the balance and the bool @@ -774,14 +773,14 @@ async fn foreign_account_get_initial_balance() -> anyhow::Result<()> { let foreign_account_code_source = format!( " - use.miden::account + use.miden::active_account export.get_initial_balance # push the faucet ID on the stack push.{fungible_faucet_id_suffix} push.{fungible_faucet_id_prefix} # get the initial balance of the asset associated with the provided faucet ID - exec.account::get_balance + exec.active_account::get_balance # => [initial_balance] # truncate the stack @@ -887,7 +886,7 @@ async fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { let storage_slots = vec![AccountStorage::mock_item_0().slot]; let second_foreign_account_code_source = r#" use.miden::tx - use.miden::account + use.miden::active_account use.std::sys @@ -915,7 +914,7 @@ async fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { # get the first element of the 0'th storage slot (it should be 1) and add it to the # obtained foreign value. - push.0 exec.account::get_item drop drop drop + push.0 exec.active_account::get_item drop drop drop add # assert that the resulting value equals 6 @@ -943,7 +942,7 @@ async fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { vec![AccountStorage::mock_item_0().slot, AccountStorage::mock_item_1().slot]; let first_foreign_account_code_source = r#" use.miden::tx - use.miden::account + use.miden::active_account use.std::sys @@ -964,7 +963,7 @@ async fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { # get the second element of the 0'th storage slot (it should be 2) and add it to the # obtained foreign value. - push.0 exec.account::get_item drop drop swap drop + push.0 exec.active_account::get_item drop drop swap drop add # assert that the resulting value equals 8 @@ -977,7 +976,7 @@ async fn test_nested_fpi_cyclic_invocation() -> anyhow::Result<()> { # make this foreign procedure unique to make sure that we invoke the procedure of the # foreign account, not the native one push.1 drop - exec.account::get_item + exec.active_account::get_item # return the first element of the resulting word drop drop drop @@ -1104,7 +1103,7 @@ async fn test_nested_fpi_stack_overflow() -> anyhow::Result<()> { let mut foreign_accounts = Vec::new(); let last_foreign_account_code_source = " - use.miden::account + use.miden::active_account export.get_item_foreign # make this foreign procedure unique to make sure that we invoke the procedure @@ -1114,7 +1113,7 @@ async fn test_nested_fpi_stack_overflow() -> anyhow::Result<()> { # push the index of desired storage item push.0 - exec.account::get_item + exec.active_account::get_item # return the first element of the resulting word drop drop drop @@ -1376,12 +1375,12 @@ async fn test_nested_fpi_native_account_invocation() -> anyhow::Result<()> { async fn test_fpi_stale_account() -> anyhow::Result<()> { // Prepare the test data let foreign_account_code_source = " - use.miden::account + use.miden::native_account # code is not used in this test export.set_some_item_foreign push.34.1 - exec.account::set_item + exec.native_account::set_item end "; @@ -1474,15 +1473,16 @@ async fn test_fpi_stale_account() -> anyhow::Result<()> { #[tokio::test] async fn test_fpi_get_account_id() -> anyhow::Result<()> { let foreign_account_code_source = " - use.miden::account + use.miden::active_account + use.miden::native_account export.get_current_and_native_ids # get the ID of the current (foreign) account - exec.account::get_id + exec.active_account::get_id # => [acct_id_prefix, acct_id_suffix, pad(16)] # get the ID of the native account - exec.account::get_native_id + exec.native_account::get_id # => [native_acct_id_prefix, native_acct_id_suffix, acct_id_prefix, acct_id_suffix, pad(16)] # truncate the stack @@ -1519,7 +1519,6 @@ async fn test_fpi_get_account_id() -> anyhow::Result<()> { use.std::sys use.miden::tx - use.miden::account use.miden::account_id begin @@ -1582,117 +1581,6 @@ async fn test_fpi_get_account_id() -> anyhow::Result<()> { Ok(()) } -/// This test checks that our `miden::get_nonce` and `miden::get_native_nonce` procedures return -/// nonce values of the current and native account respectively while being called from the foreign -/// account. -#[tokio::test] -async fn test_fpi_get_account_nonce() -> anyhow::Result<()> { - let foreign_account_code_source = " - use.miden::account - - export.get_current_and_native_nonce_values - # get the nonce of the current (foreign) account - exec.account::get_nonce - # => [nonce, pad(16)] - - # get the nonce of the native account - exec.account::get_native_nonce - # => [native_nonce, nonce, pad(16)] - - # truncate the stack - movdn.3 movdn.3 drop drop - # => [native_nonce, nonce, pad(14)] - end - "; - - let foreign_account_component = AccountComponent::compile( - foreign_account_code_source, - TransactionKernel::with_kernel_library(Arc::new(DefaultSourceManager::default())), - Vec::new(), - )? - .with_supports_all_types(); - - let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) - .with_auth_component(Auth::IncrNonce) - .with_component(foreign_account_component) - .build_existing()?; - - let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) - .with_auth_component(Auth::IncrNonce) - .with_component(MockAccountComponent::with_empty_slots()) - .storage_mode(AccountStorageMode::Public) - .nonce(Felt::new(2)) - .build_existing()?; - - let mut mock_chain = - MockChainBuilder::with_accounts([native_account.clone(), foreign_account.clone()])? - .build()?; - mock_chain.prove_next_block()?; - - let code = format!( - r#" - use.std::sys - - use.miden::tx - use.miden::account - - begin - # get the nonce values of the foreign and native accounts - # pad the stack for the `execute_foreign_procedure` execution - padw padw padw push.0.0.0 - # => [pad(15)] - - # get the hash of the `get_current_and_native_nonce_values` foreign account procedure - push.{get_current_and_native_nonce_values} - - # push the foreign account ID - push.{foreign_suffix} push.{foreign_prefix} - # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, pad(15)] - - exec.tx::execute_foreign_procedure - # => [native_nonce, foreign_nonce] - - # push the expected native account nonce and check that it is equal to the one returned - # from the FPI - push.{expected_native_nonce} - assert_eq.err="native account nonce returned from the FPI is not equal to the expected one" - # => [foreign_nonce] - - # push the expected foreign account nonce and check that it is equal to the one returned - # from the FPI - push.{expected_foreign_nonce} - assert_eq.err="foreign account nonce returned from the FPI is not equal to the expected one" - # => [] - - # truncate the stack - exec.sys::truncate_stack - end - "#, - get_current_and_native_nonce_values = foreign_account.code().procedures()[1].mast_root(), - foreign_suffix = foreign_account.id().suffix(), - foreign_prefix = foreign_account.id().prefix().as_felt(), - expected_native_nonce = native_account.nonce(), - expected_foreign_nonce = foreign_account.nonce(), - ); - - let tx_script = ScriptBuilder::default().compile_tx_script(code)?; - - let foreign_account_inputs = mock_chain - .get_foreign_account_inputs(foreign_account.id()) - .expect("failed to get foreign account inputs"); - - mock_chain - .build_tx_context(native_account.id(), &[], &[]) - .expect("failed to build tx context") - .foreign_accounts(vec![foreign_account_inputs]) - .tx_script(tx_script) - .build()? - .execute() - .await?; - - Ok(()) -} - // HELPER FUNCTIONS // ================================================================================================ @@ -1781,17 +1669,17 @@ async fn test_get_initial_item_and_get_initial_map_item_with_foreign_account() - // Create foreign procedures that test get_initial_item and get_initial_map_item let foreign_account_code_source = " - use.miden::account + use.miden::active_account use.std::sys export.test_get_initial_item push.0 - exec.account::get_initial_item + exec.active_account::get_initial_item exec.sys::truncate_stack end export.test_get_initial_map_item - exec.account::get_initial_map_item + exec.active_account::get_initial_map_item exec.sys::truncate_stack end "; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index d31111067e..b619550626 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -422,7 +422,7 @@ async fn user_code_can_abort_transaction_with_summary() -> anyhow::Result<()> { # => [pad(16)] push.0.0 exec.tx::get_block_number - exec.::miden::account::incr_nonce + exec.::miden::native_account::incr_nonce # => [[final_nonce, block_num, 0, 0], pad(16)] # => [SALT, pad(16)] diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index bbf9020b7c..bb84084352 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -208,7 +208,7 @@ fn note_script_that_creates_notes<'note>( // Make sure that the transaction's native account matches the note sender. out.push_str(&format!( - r#"exec.::miden::account::get_native_id + r#"exec.::miden::native_account::get_id # => [native_account_id_prefix, native_account_id_suffix] push.{sender_prefix} assert_eq.err="sender ID prefix does not match native account ID's prefix" # => [native_account_id_suffix] diff --git a/docs/src/account/storage.md b/docs/src/account/storage.md index 5c8cad6872..06146ef588 100644 --- a/docs/src/account/storage.md +++ b/docs/src/account/storage.md @@ -22,7 +22,7 @@ A value slot can be used whenever 32 bytes of data is enough, e.g. for storing a ## Map Slots -A map slot contains a `StorageMap` which is a key-value store implemented as a sparse Merkle tree (SMT). This allows an account to store a much larger amount of data than would be possible using only the account's storage slots. The root of the underlying SMT is stored in a single account storage slot, and each map entry is a leaf in the tree. When retrieving an entry (e.g., via `account::get_map_item`), its inclusion is proven using a Merkle proof. +A map slot contains a `StorageMap` which is a key-value store implemented as a sparse Merkle tree (SMT). This allows an account to store a much larger amount of data than would be possible using only the account's storage slots. The root of the underlying SMT is stored in a single account storage slot, and each map entry is a leaf in the tree. When retrieving an entry (e.g., via `active_account::get_map_item`), its inclusion is proven using a Merkle proof. Key properties of `StorageMap`: diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index 467b66cbea..d08c3599f6 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -25,41 +25,47 @@ Most procedures in the Miden protocol library are implemented as wrappers around The procedures maintain the same security and context restrictions as the underlying kernel procedures. When invoking these procedures, ensure that the calling context matches the requirements. -## Account Procedures (`miden::account`) - -Account procedures can be used to read and write to account storage, add or remove assets from the vault and fetch or compute commitments. - -| Procedure | Description | Context | -| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `get_id` | Returns the ID of the current account.

**Inputs:** `[]`
**Outputs:** `[account_id_prefix, account_id_suffix]` | Any | -| `get_native_id` | Returns the ID of the native account of the transaction.

**Inputs:** `[]`
**Outputs:** `[account_id_prefix, account_id_suffix]` | Any | -| `get_nonce` | Returns the nonce of the current account. Always returns the initial nonce as it can only be incremented in auth procedures.

**Inputs:** `[]`
**Outputs:** `[nonce]` | Any | -| `get_native_nonce` | Returns the nonce of the native account of the transaction.

**Inputs:** `[]`
**Outputs:** `[nonce]` | Any | -| `incr_nonce` | Increments the account nonce by one and returns the new nonce. Can only be called from auth procedures.

**Inputs:** `[]`
**Outputs:** `[final_nonce]` | Auth | -| `get_initial_commitment` | Returns the native account commitment at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_COMMITMENT]` | Any | -| `compute_current_commitment` | Computes and returns the account commitment from account data stored in memory.

**Inputs:** `[]`
**Outputs:** `[ACCOUNT_COMMITMENT]` | Any | -| `compute_delta_commitment` | Computes the commitment to the native account's delta. Can only be called from auth procedures.

**Inputs:** `[]`
**Outputs:** `[DELTA_COMMITMENT]` | Auth | +## Active account Procedures (`miden::active_account`) + +Active account procedures can be used to read from storage, fetch or compute commitments or obtain other internal data of the active account. + +| Procedure | Description | Context | +| -------------------------------- | ----------------------------- | ----------------------------- | +| `get_id` | Returns the ID of the active account.

**Inputs:** `[]`
**Outputs:** `[account_id_prefix, account_id_suffix]` | Any | +| `get_nonce` | Returns the nonce of the active account. Always returns the initial nonce as it can only be incremented in auth procedures.

**Inputs:** `[]`
**Outputs:** `[nonce]` | Any | +| `get_initial_commitment` | Returns the active account commitment at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_COMMITMENT]` | Any | +| `compute_commitment` | Computes and returns the account commitment from account data stored in memory.

**Inputs:** `[]`
**Outputs:** `[ACCOUNT_COMMITMENT]` | Any | +| `get_code_commitment` | Gets the account code commitment of the active account.

**Inputs:** `[]`
**Outputs:** `[CODE_COMMITMENT]` | Account | +| `get_initial_storage_commitment` | Returns the storage commitment of the active account at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_STORAGE_COMMITMENT]` | Any | +| `compute_storage_commitment` | Computes the latest account storage commitment of the active account.

**Inputs:** `[]`
**Outputs:** `[STORAGE_COMMITMENT]` | Account | | `get_item` | Gets an item from the account storage.

**Inputs:** `[index]`
**Outputs:** `[VALUE]` | Account | | `get_initial_item` | Gets the initial item from the account storage slot as it was at the beginning of the transaction.

**Inputs:** `[index]`
**Outputs:** `[VALUE]` | Account | -| `set_item` | Sets an item in the account storage.

**Inputs:** `[index, VALUE]`
**Outputs:** `[OLD_VALUE]` | Native & Account | | `get_map_item` | Returns the VALUE located under the specified KEY within the map contained in the given account storage slot.

**Inputs:** `[index, KEY]`
**Outputs:** `[VALUE]` | Account | | `get_initial_map_item` | Gets the initial VALUE from the account storage map as it was at the beginning of the transaction.

**Inputs:** `[index, KEY]`
**Outputs:** `[VALUE]` | Account | -| `set_map_item` | Sets VALUE under the specified KEY within the map contained in the given account storage slot.

**Inputs:** `[index, KEY, VALUE]`
**Outputs:** `[OLD_MAP_ROOT, OLD_MAP_VALUE]` | Native & Account | -| `get_code_commitment` | Gets the account code commitment of the current account.

**Inputs:** `[]`
**Outputs:** `[CODE_COMMITMENT]` | Account | -| `get_initial_storage_commitment` | Returns the storage commitment of the native account at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_STORAGE_COMMITMENT]` | Any | -| `compute_storage_commitment` | Computes the latest account storage commitment of the current account.

**Inputs:** `[]`
**Outputs:** `[STORAGE_COMMITMENT]` | Account | | `get_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[balance]` | Any | | `get_initial_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault at the beginning of the transaction.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[init_balance]` | Any | | `has_non_fungible_asset` | Returns a boolean indicating whether the non-fungible asset is present in the current account's vault.

**Inputs:** `[ASSET]`
**Outputs:** `[has_asset]` | Any | -| `add_asset` | Adds the specified asset to the vault. For fungible assets, returns the total after addition.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET']` | Native & Account | -| `remove_asset` | Removes the specified asset from the vault.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account | -| `get_initial_vault_root` | Returns the vault root of the native account at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_VAULT_ROOT]` | Any | -| `get_vault_root` | Returns the vault root of the current account.

**Inputs:** `[]`
**Outputs:** `[VAULT_ROOT]` | Any | -| `was_procedure_called` | Returns 1 if a procedure was called during transaction execution, and 0 otherwise.

**Inputs:** `[PROC_ROOT]`
**Outputs:** `[was_called]` | Any | -| `get_num_procedures` | Returns the number of procedures in the current account.

**Inputs:** `[]`
**Outputs:** `[num_procedures]` | Any | +| `get_initial_vault_root` | Returns the vault root of the active account at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_VAULT_ROOT]` | Any | +| `get_vault_root` | Returns the vault root of the active account.

**Inputs:** `[]`
**Outputs:** `[VAULT_ROOT]` | Any | +| `get_num_procedures` | Returns the number of procedures in the active account.

**Inputs:** `[]`
**Outputs:** `[num_procedures]` | Any | | `get_procedure_root` | Returns the procedure root for the procedure at the specified index.

**Inputs:** `[index]`
**Outputs:** `[PROC_ROOT]` | Any | | `has_procedure` | Returns the binary flag indicating whether the procedure with the provided root is available on the active account.

**Inputs:** `[PROC_ROOT]`
**Outputs:** `[is_procedure_available]` | Any | +## Native account Procedures (`miden::native_account`) + +Native account procedures can be used to write to storage, add or remove assets from the vault and compute delta commitment of the native account. + +| Procedure | Description | Context | +| ------------------------------ | ------------------------------ | ------------------------------ | +| `get_id` | Returns the ID of the native account of the transaction.

**Inputs:** `[]`
**Outputs:** `[account_id_prefix, account_id_suffix]` | Any | +| `incr_nonce` | Increments the nonce of the native account by one and returns the new nonce. Can only be called from auth procedures.

**Inputs:** `[]`
**Outputs:** `[final_nonce]` | Auth | +| `compute_delta_commitment` | Computes the commitment to the native account's delta. Can only be called from auth procedures.

**Inputs:** `[]`
**Outputs:** `[DELTA_COMMITMENT]` | Auth | +| `set_item` | Sets an item in the native account storage.

**Inputs:** `[index, VALUE]`
**Outputs:** `[OLD_VALUE]` | Native & Account | +| `set_map_item` | Sets VALUE under the specified KEY within the map contained in the given native account storage slot.

**Inputs:** `[index, KEY, VALUE]`
**Outputs:** `[OLD_MAP_ROOT, OLD_MAP_VALUE]` | Native & Account | +| `add_asset` | Adds the specified asset to the vault. For fungible assets, returns the total after addition.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET']` | Native & Account | +| `remove_asset` | Removes the specified asset from the vault.

**Inputs:** `[ASSET]`
**Outputs:** `[ASSET]` | Native & Account | +| `was_procedure_called` | Returns 1 if a native account procedure was called during transaction execution, and 0 otherwise.

**Inputs:** `[PROC_ROOT]`
**Outputs:** `[was_called]` | Any | + ## Active Note Procedures (`miden::active_note`) Active note procedures can be used to fetch data from the note that is currently being processed by the transaction kernel. diff --git a/docs/src/transaction.md b/docs/src/transaction.md index b92b92f230..72fe743cf5 100644 --- a/docs/src/transaction.md +++ b/docs/src/transaction.md @@ -66,7 +66,7 @@ To illustrate the `Transaction` protocol, we provide two examples for a basic `T Let's assume account A wants to create a P2ID note. P2ID notes are pay-to-ID notes that can only be consumed by a specified target account ID. Note creators can provide the target account ID using the [note inputs](note#inputs). -In this example, account A uses the basic wallet and the authentication component provided by `miden-lib`. The basic wallet component defines the methods `wallets::basic::create_note` and `wallets::basic::move_asset_to_note` to create notes with assets, and `wallets::basic::receive_asset` to receive assets. The authentication component exposes `auth::basic::auth_tx_rpo_falcon512` which allows for signing a transaction. Some account methods like `account::get_id` are always exposed. +In this example, account A uses the basic wallet and the authentication component provided by `miden-lib`. The basic wallet component defines the methods `wallets::basic::create_note` and `wallets::basic::move_asset_to_note` to create notes with assets, and `wallets::basic::receive_asset` to receive assets. The authentication component exposes `auth::basic::auth_tx_rpo_falcon512` which allows for signing a transaction. Some account methods like `active_account::get_id` are always exposed. The executor inputs to the Miden VM a `Transaction` script in which he places on the stack the data (tag, aux, note_type, execution_hint, RECIPIENT) of the note(s) that he wants to create using `wallets::basic::create_note` during the said `Transaction`. The [`NoteRecipient`](https://github.com/0xMiden/miden-base/blob/main/crates/miden-objects/src/note/recipient.rs) is a value that describes under which condition a note can be consumed and is built using a `serial_number`, the `note_script` (in this case P2ID script) and the `note_inputs`. The Miden VM will execute the `Transaction` script and create the note(s). After having been created, the executor can use `wallets::basic::move_asset_to_note` to move assets from the account's vault to the notes vault. @@ -80,9 +80,9 @@ To start the transaction process, the executor fetches and prepares all the inpu In the transaction's prologue the data is being authenticated by re-hashing the provided values and comparing them to the blockchain's data (this is how private data can be used and verified during the execution of transaction without actually revealing it to the network). -Then the P2ID note script is being executed. The script starts by reading the note inputs `active_note::get_inputs` — in our case the account ID of the intended target account. It checks if the provided target account ID equals the account ID of the executing account. This is the first time the note invokes a method exposed by the `Transaction` kernel, `account::get_id`. +Then the P2ID note script is being executed. The script starts by reading the note inputs `active_note::get_inputs` — in our case the account ID of the intended target account. It checks if the provided target account ID equals the account ID of the executing account. This is the first time the note invokes a method exposed by the `Transaction` kernel, `active_account::get_id`. -If the check passes, the note script pushes the assets it holds into the account's vault. For every asset the note contains, the script calls the `wallets::basic::receive_asset` method exposed by the account's wallet component. The `wallets::basic::receive_asset` procedure calls `account::add_asset`, which cannot be called from the note itself. This allows accounts to control what functionality to expose, e.g. whether the account supports receiving assets or not, and the note cannot bypass that. +If the check passes, the note script pushes the assets it holds into the account's vault. For every asset the note contains, the script calls the `wallets::basic::receive_asset` method exposed by the account's wallet component. The `wallets::basic::receive_asset` procedure calls `native_account::add_asset`, which cannot be called from the note itself. This allows accounts to control what functionality to expose, e.g. whether the account supports receiving assets or not, and the note cannot bypass that. After the assets are stored in the account's vault, the transaction script is being executed. The script calls `auth::basic::auth_tx_rpo_falcon512` which is explicitly exposed in the account interface. The method is used to verify a provided signature against a public key stored in the account's storage and a commitment to this specific transaction. If the signature can be verified, the method increments the nonce. From 8230f7001c1b37c6164637de8160ae479c8448a7 Mon Sep 17 00:00:00 2001 From: juan518munoz <62400508+juan518munoz@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:12:21 -0300 Subject: [PATCH 114/133] refactor: expose conversion (#2033) --- crates/miden-objects/src/account/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index f600a3f221..bc52c73fdb 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -420,7 +420,6 @@ impl Account { } } -#[cfg(any(test, feature = "testing"))] impl TryFrom for AccountDelta { type Error = AccountError; From d4a45495cf344746449a68796880b2b4c8773393 Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Sun, 2 Nov 2025 00:31:35 +0300 Subject: [PATCH 115/133] feat: enable public output note creation during NTX execution (#1995) --- CHANGELOG.md | 1 + crates/miden-lib/asm/miden/note.masm | 60 ++++-- crates/miden-lib/build.rs | 2 +- crates/miden-lib/src/transaction/inputs.rs | 5 +- crates/miden-objects/src/note/inputs.rs | 5 +- crates/miden-objects/src/note/recipient.rs | 21 -- .../miden-objects/src/transaction/tx_args.rs | 27 ++- .../miden-testing/src/tx_context/builder.rs | 4 +- crates/miden-testing/tests/scripts/faucet.rs | 179 ++++++++++++++++++ crates/miden-tx/src/executor/exec_host.rs | 71 ++++++- crates/miden-tx/src/host/mod.rs | 178 +++++++++++++++-- crates/miden-tx/src/host/note_builder.rs | 115 +++-------- crates/miden-tx/src/prover/prover_host.rs | 3 + 13 files changed, 514 insertions(+), 157 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 242bc92be9..67b7b6d762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ - Re-add bech32 encoding for `AccountId` ([#2018](https://github.com/0xMiden/miden-base/pull/2018)). - [BREAKING] Change `AccountTree` to be generic over `Smt` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). - [BREAKING] Change `AccountTree` to be generic over `trait AccountTreeBackend` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). +- [BREAKING] Added `get_note_script()` method to `DataStore` trait to enable lazy loading of note scripts during transaction execution ([#1995](https://github.com/0xMiden/miden-base/pull/1995)). - [BREAKING] Separate account APIs in `miden::account` into `active_account` and `native_account` ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). - [BREAKING] Remove `miden::account::get_native_nonce` procedure ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). diff --git a/crates/miden-lib/asm/miden/note.masm b/crates/miden-lib/asm/miden/note.masm index 33fec4af60..5ce7214497 100644 --- a/crates/miden-lib/asm/miden/note.masm +++ b/crates/miden-lib/asm/miden/note.masm @@ -1,8 +1,7 @@ +use.miden::account_id use.std::crypto::hashes::rpo use.std::mem -use.miden::account_id - # ERRORS # ================================================================================================= @@ -56,7 +55,7 @@ end #! - max_inputs_per_note is the max inputs per note. export.::miden::util::note::get_max_inputs_per_note -#! Writes the assets data stored in the advice map to the memory specified by the provided +#! Writes the assets data stored in the advice map to the memory specified by the provided #! destination pointer. #! #! Inputs: @@ -91,17 +90,32 @@ end #! Builds the recipient hash from note inputs, script root, and serial number. #! #! This procedure computes the commitment of the note inputs and then uses it to calculate the note -#! recipient by hashing this commit, the provided script root, and the serial number. +#! recipient by hashing this commitment, the provided script root, and the serial number. #! -#! Inputs: [inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] -#! Outputs: [RECIPIENT] +#! Inputs: +#! Operand stack: [inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] +#! Advice map: { +#! INPUTS_COMMITMENT: [INPUTS], +#! } +#! Outputs: +#! Operand stack: [RECIPIENT] +#! Advice map: { +#! INPUTS_COMMITMENT: [INPUTS], +#! RECIPIENT: [SERIAL_SCRIPT_HASH, INPUTS_COMMITMENT], +#! SERIAL_SCRIPT_HASH: [SERIAL_HASH, SCRIPT_ROOT], +#! SERIAL_HASH: [SERIAL_NUM, EMPTY_WORD], +#! } #! #! Where: #! - inputs_ptr is the memory address where the note inputs are stored. #! - num_inputs is the number of input values. #! - SCRIPT_ROOT is the script root of the note. #! - SERIAL_NUM is the serial number of the note. -#! - RECIPIENT is the commitment to the input note's script, inputs, the serial number. +#! - RECIPIENT is the commitment to the input note's script, inputs, and the serial number. +#! +#! Locals: +#! - 0: inputs_ptr +#! - 1: num_inputs #! #! Panics if: #! - inputs_ptr is not word-aligned (i.e., is not a multiple of 4). @@ -109,13 +123,37 @@ end #! #! Invocation: exec export.build_recipient + dup.1 dup.1 + # => [inputs_ptr, num_inputs, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + exec.compute_inputs_commitment - # => [INPUTS_HASH, SERIAL_NUM, SCRIPT_ROOT] + # => [INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + + movup.5 movup.5 dup movdn.2 + # => [inputs_ptr, num_inputs, inputs_ptr, INPUTS_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT] + + add swap + # => [inputs_ptr, end_ptr, INPUTS_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT] + + movdn.5 movdn.5 + # => [INPUTS_COMMITMENT, inputs_ptr, end_ptr, SERIAL_NUM, SCRIPT_ROOT] + + adv.insert_mem + # => [INPUTS_COMMITMENT, inputs_ptr, end_ptr, SERIAL_NUM, SCRIPT_ROOT] + + movup.4 drop movup.4 drop + # => [INPUTS_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT] movdnw.2 - # => [SERIAL_NUM, SCRIPT_ROOT, INPUTS_HASH] + # => [SERIAL_NUM, SCRIPT_ROOT, INPUTS_COMMITMENT] + + padw adv.insert_hdword hmerge + # => [SERIAL_HASH, SCRIPT_ROOT, INPUTS_COMMITMENT] + + swapw adv.insert_hdword hmerge + # => [SERIAL_SCRIPT_HASH, INPUTS_COMMITMENT] - exec.build_recipient_hash + swapw adv.insert_hdword hmerge # => [RECIPIENT] end @@ -149,7 +187,7 @@ end #! #! Where: #! - METADATA is the metadata of some note. -#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender ID of the note which +#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender ID of the note which #! metadata was provided. export.extract_sender_from_metadata # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_id_prefix] diff --git a/crates/miden-lib/build.rs b/crates/miden-lib/build.rs index b87b2f5b9b..10254ae490 100644 --- a/crates/miden-lib/build.rs +++ b/crates/miden-lib/build.rs @@ -161,7 +161,7 @@ fn compile_tx_kernel(source_dir: &Path, target_dir: &Path) -> Result let output_file = target_dir.join("tx_kernel").with_extension(Library::LIBRARY_EXTENSION); kernel_lib.write_to_file(output_file).into_diagnostic()?; - let assembler = build_assembler(Some(kernel_lib))?.with_debug_mode(true); + let assembler = build_assembler(Some(kernel_lib))?; // assemble the kernel program and write it to the "tx_kernel.masb" file let mut main_assembler = assembler.clone(); diff --git a/crates/miden-lib/src/transaction/inputs.rs b/crates/miden-lib/src/transaction/inputs.rs index cb0237dd47..84140838e8 100644 --- a/crates/miden-lib/src/transaction/inputs.rs +++ b/crates/miden-lib/src/transaction/inputs.rs @@ -344,10 +344,7 @@ impl TransactionAdviceInputs { let note_arg = tx_inputs.tx_args().get_note_args(note.id()).unwrap_or(&EMPTY_WORD); // recipient inputs / assets commitments - self.add_map_entry( - recipient.inputs().commitment(), - recipient.inputs().format_for_advice(), - ); + self.add_map_entry(recipient.inputs().commitment(), recipient.inputs().to_elements()); self.add_map_entry(assets.commitment(), assets.to_padded_assets()); // note details / metadata diff --git a/crates/miden-objects/src/note/inputs.rs b/crates/miden-objects/src/note/inputs.rs index 9a9e09203d..5d0366eeaf 100644 --- a/crates/miden-objects/src/note/inputs.rs +++ b/crates/miden-objects/src/note/inputs.rs @@ -68,14 +68,13 @@ impl NoteInputs { &self.values } - /// Returns the note's input formatted to be used with the advice map. + /// Returns the note's input as a vector of field elements. /// /// The format is `INPUTS || PADDING`, where: /// - /// Where: /// - INPUTS is the variable inputs for the note /// - PADDING is the optional padding to align the data with a 2WORD boundary - pub fn format_for_advice(&self) -> Vec { + pub fn to_elements(&self) -> Vec { pad_inputs(&self.values) } } diff --git a/crates/miden-objects/src/note/recipient.rs b/crates/miden-objects/src/note/recipient.rs index 50c90ca6fb..78cc247bf3 100644 --- a/crates/miden-objects/src/note/recipient.rs +++ b/crates/miden-objects/src/note/recipient.rs @@ -1,8 +1,5 @@ -use alloc::vec::Vec; use core::fmt::Debug; -use miden_crypto::Felt; - use super::{ ByteReader, ByteWriter, @@ -63,24 +60,6 @@ impl NoteRecipient { pub fn digest(&self) -> Word { self.digest } - - /// Returns the recipient formatted to be used with the advice map. - /// - /// The format is `inputs_length || INPUTS_COMMITMENT || SCRIPT_ROOT || SERIAL_NUMBER` - /// - /// Where: - /// - inputs_length is the length of the note inputs - /// - INPUTS_COMMITMENT is the commitment of the note inputs - /// - SCRIPT_ROOT is the commitment of the note script (i.e., the script's MAST root) - /// - SERIAL_NUMBER is the recipient's serial number - pub fn format_for_advice(&self) -> Vec { - let mut result = Vec::with_capacity(13); - result.push(self.inputs.num_values().into()); - result.extend(self.inputs.commitment()); - result.extend(self.script.root()); - result.extend(self.serial_num); - result - } } fn compute_recipient_digest(serial_num: Word, script: &NoteScript, inputs: &NoteInputs) -> Word { diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index 6f8d1dc09e..246f03cf80 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -154,9 +154,10 @@ impl TransactionArgs { /// Populates the advice inputs with the expected recipient data for creating output notes. /// - /// The advice inputs' map is extended with the following keys: - /// - /// - recipient_digest |-> recipient details (inputs_hash, script_root, serial_num). + /// The advice inputs' map is extended with the following entries: + /// - RECIPIENT: [SERIAL_SCRIPT_HASH, INPUTS_COMMITMENT] + /// - SERIAL_SCRIPT_HASH: [SERIAL_HASH, SCRIPT_ROOT] + /// - SERIAL_HASH: [SERIAL_NUM, EMPTY_WORD] /// - inputs_commitment |-> inputs. /// - script_root |-> script. pub fn add_output_note_recipient>(&mut self, note_recipient: T) { @@ -165,9 +166,15 @@ impl TransactionArgs { let script = note_recipient.script(); let script_encoded: Vec = script.into(); - let new_elements = [ - (note_recipient.digest(), note_recipient.format_for_advice()), - (inputs.commitment(), inputs.format_for_advice()), + // Build the advice map entries + let sn_hash = Hasher::merge(&[note_recipient.serial_num(), Word::empty()]); + let sn_script_hash = Hasher::merge(&[sn_hash, script.root()]); + + let new_elements = vec![ + (sn_hash, concat_words(note_recipient.serial_num(), Word::empty())), + (sn_script_hash, concat_words(sn_hash, script.root())), + (note_recipient.digest(), concat_words(sn_script_hash, inputs.commitment())), + (inputs.commitment(), inputs.to_elements()), (script.root(), script_encoded), ]; @@ -224,6 +231,14 @@ impl TransactionArgs { } } +/// Concatenates two [`Word`]s into a [`Vec`] containing 8 elements. +fn concat_words(first: Word, second: Word) -> Vec { + let mut result = Vec::with_capacity(8); + result.extend(first); + result.extend(second); + result +} + impl Default for TransactionArgs { fn default() -> Self { Self::new(AdviceMap::default()) diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index fa4ed27024..e0148ca026 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -292,9 +292,9 @@ impl TransactionContextBuilder { }, }; - let tx_args = TransactionArgs::default().with_note_args(self.note_args); + let mut tx_args = TransactionArgs::default().with_note_args(self.note_args); - let mut tx_args = if let Some(tx_script) = self.tx_script { + tx_args = if let Some(tx_script) = self.tx_script { tx_args.with_tx_script_and_args(tx_script, self.tx_script_args) } else { tx_args diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 160f29bc1e..cf657676a6 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use miden_lib::account::faucets::{BasicFungibleFaucet, FungibleFaucetExt, NetworkFungibleFaucet}; use miden_lib::errors::tx_kernel_errors::ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED; use miden_lib::note::well_known_note::WellKnownNote; +use miden_lib::testing::note::NoteBuilder; use miden_lib::utils::ScriptBuilder; use miden_objects::account::{ Account, @@ -20,6 +21,7 @@ use miden_objects::note::{ Note, NoteAssets, NoteExecutionHint, + NoteExecutionMode, NoteId, NoteInputs, NoteMetadata, @@ -27,8 +29,10 @@ use miden_objects::note::{ NoteTag, NoteType, }; +use miden_objects::testing::account_id::ACCOUNT_ID_PRIVATE_SENDER; use miden_objects::transaction::{ExecutedTransaction, OutputNote}; use miden_objects::{Felt, Word}; +use miden_processor::crypto::RpoRandomCoin; use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; use crate::scripts::swap::create_p2id_note_exact; @@ -298,6 +302,181 @@ async fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::R Ok(()) } +// TEST PUBLIC NOTE CREATION DURING NOTE CONSUMPTION +// ================================================================================================ + +/// Tests that a public note can be created during note consumption by fetching the note script +/// from the data store. This test verifies the functionality added in issue #1972. +/// +/// The test creates a note that calls the faucet's `distribute` function to create a PUBLIC +/// P2ID output note. The P2ID script is fetched from the data store during transaction execution. +#[tokio::test] +async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let faucet = builder.add_existing_basic_faucet(Auth::BasicAuth, "TST", 200, None)?; + + // Parameters for the PUBLIC note that will be created by the faucet + let recipient_account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?; + let amount = Felt::new(75); + let tag = NoteTag::for_public_use_case(0, 0, NoteExecutionMode::Local)?; + let aux = Felt::new(27); + let note_execution_hint = NoteExecutionHint::on_block_slot(5, 6, 7); + let note_type = NoteType::Public; + + // Create a simple output note script + let output_note_script_code = "begin push.1 drop end"; + let source_manager = Arc::new(DefaultSourceManager::default()); + let output_note_script = ScriptBuilder::with_source_manager(source_manager.clone()) + .compile_note_script(output_note_script_code)?; + + let serial_num = Word::default(); + let target_account_suffix = recipient_account_id.suffix(); + let target_account_prefix = recipient_account_id.prefix().as_felt(); + + // Adding extra 0 values to inputs to test trial unhashing in extract_note_inputs fn + let note_inputs = NoteInputs::new(vec![ + target_account_suffix, + target_account_prefix, + Felt::new(0), + Felt::new(0), + Felt::new(0), + Felt::new(0), + Felt::new(0), + Felt::new(1), + ])?; + + let note_recipient = + NoteRecipient::new(serial_num, output_note_script.clone(), note_inputs.clone()); + + let output_script_root = note_recipient.script().root(); + + let asset = FungibleAsset::new(faucet.id(), amount.into())?; + let metadata = NoteMetadata::new(faucet.id(), note_type, tag, note_execution_hint, aux)?; + let expected_note = Note::new(NoteAssets::new(vec![asset.into()])?, metadata, note_recipient); + + let trigger_note_script_code = format!( + " + use.miden::note + + begin + # Build recipient hash from SERIAL_NUM, SCRIPT_ROOT, and INPUTS_COMMITMENT + push.{script_root} + # => [SCRIPT_ROOT] + + push.{serial_num} + # => [SERIAL_NUM, SCRIPT_ROOT] + + # Store note inputs in memory at address 0 + # First word: inputs[0..4] + push.{input0}.{input1}.{input2}.{input3} + mem_storew.0 dropw + # Memory[0] = [input0, input1, input2, input3] + + # Second word: inputs[4..8] + push.{input4}.{input5}.{input6}.{input7} + mem_storew.4 dropw + # Memory[1] = [input4, input5, input6, input7] + + push.8 push.0 + # => [inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + + exec.note::build_recipient + # => [RECIPIENT] + + # Now call distribute with the computed recipient + push.{note_execution_hint} + push.{note_type} + push.{aux} + push.{tag} + push.{amount} + # => [amount, tag, aux, note_type, execution_hint, RECIPIENT] + + call.::miden::contracts::faucets::basic_fungible::distribute + # => [note_idx, pad(15)] + + # Truncate the stack + dropw dropw dropw dropw + end + ", + note_type = note_type as u8, + input0 = note_inputs.values()[0], + input1 = note_inputs.values()[1], + input2 = note_inputs.values()[2], + input3 = note_inputs.values()[3], + input4 = note_inputs.values()[4], + input5 = note_inputs.values()[5], + input6 = note_inputs.values()[6], + input7 = note_inputs.values()[7], + script_root = output_script_root, + serial_num = serial_num, + aux = aux, + tag = u32::from(tag), + note_execution_hint = Felt::from(note_execution_hint), + amount = amount, + ); + + // Create the trigger note that will call distribute + let mut rng = RpoRandomCoin::new([Felt::from(1u32); 4].into()); + let trigger_note = NoteBuilder::new(faucet.id(), &mut rng) + .note_type(NoteType::Private) + .tag(NoteTag::for_local_use_case(0, 0)?.into()) + .note_execution_hint(NoteExecutionHint::always()) + .aux(Felt::new(0)) + .serial_number(Word::from([1, 2, 3, 4u32])) + .code(trigger_note_script_code) + .build()?; + + builder.add_output_note(OutputNote::Full(trigger_note.clone())); + let mock_chain = builder.build()?; + + // Execute the transaction - this should fetch the output note script from the data store. + // Note: There is intentionally no call to extend_expected_output_notes here, so the + // transaction host is forced to request the script from the data store during execution. + let executed_transaction = mock_chain + .build_tx_context(faucet.id(), &[trigger_note.id()], &[])? + .add_note_script(output_note_script) + .with_source_manager(source_manager) + .build()? + .execute() + .await?; + + // Verify that a PUBLIC note was created + assert_eq!(executed_transaction.output_notes().num_notes(), 1); + let output_note = executed_transaction.output_notes().get_note(0); + + // Extract the full note from the OutputNote enum + let full_note = match output_note { + OutputNote::Full(note) => note, + _ => panic!("Expected OutputNote::Full variant"), + }; + + // Verify the output note is public + assert_eq!(full_note.metadata().note_type(), NoteType::Public); + + // Verify the output note contains the minted fungible asset + let expected_asset = FungibleAsset::new(faucet.id(), amount.into())?; + let expected_asset_obj = Asset::from(expected_asset); + assert!(full_note.assets().iter().any(|asset| asset == &expected_asset_obj)); + + // Verify the note was created by the faucet + assert_eq!(full_note.metadata().sender(), faucet.id()); + + // Verify the note inputs commitment matches the expected commitment + assert_eq!( + full_note.recipient().inputs().commitment(), + note_inputs.commitment(), + "Output note inputs commitment should match expected inputs commitment" + ); + + // Verify the output note ID matches the expected note ID + assert_eq!(full_note.id(), expected_note.id()); + + // Verify nonce was incremented + assert_eq!(executed_transaction.account_delta().nonce_delta(), Felt::new(1)); + + Ok(()) +} + // TESTS NETWORK FAUCET // ================================================================================================ diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 71245f6cf0..113d9ea9ea 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -15,6 +15,7 @@ use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; use miden_objects::asset::{Asset, AssetWitness, FungibleAsset, VaultKey}; use miden_objects::block::BlockNumber; use miden_objects::crypto::merkle::SmtProof; +use miden_objects::note::{NoteInputs, NoteMetadata, NoteRecipient}; use miden_objects::transaction::{InputNote, InputNotes, OutputNote}; use miden_objects::vm::AdviceMap; use miden_objects::{Felt, Hasher, Word}; @@ -30,6 +31,7 @@ use miden_processor::{ use crate::auth::{SigningInputs, TransactionAuthenticator}; use crate::errors::TransactionKernelError; +use crate::host::note_builder::OutputNoteBuilder; use crate::host::{ ScriptMastForestStore, TransactionBaseHost, @@ -37,7 +39,7 @@ use crate::host::{ TransactionEventHandling, TransactionProgress, }; -use crate::{AccountProcedureIndexMap, DataStore}; +use crate::{AccountProcedureIndexMap, DataStore, DataStoreError}; // TRANSACTION EXECUTOR HOST // ================================================================================================ @@ -362,6 +364,55 @@ where Ok(asset_witness_to_advice_mutation(asset_witness)) } + /// Handles a request for a [`NoteScript`] by querying the [`DataStore`]. + /// + /// The script is fetched from the data store and used to build a [`NoteRecipient`], which is + /// then used to create an [`OutputNoteBuilder`]. This function is only called for public notes + /// where the script is not already available in the advice provider. + async fn on_note_script_requested( + &mut self, + script_root: Word, + metadata: NoteMetadata, + recipient_digest: Word, + note_idx: usize, + note_inputs: NoteInputs, + serial_num: Word, + ) -> Result, TransactionKernelError> { + let note_script_result = self.base_host.store().get_note_script(script_root).await; + + let (recipient, mutations) = match note_script_result { + Ok(note_script) => { + let script_felts: Vec = (¬e_script).into(); + let recipient = NoteRecipient::new(serial_num, note_script, note_inputs); + let mutations = vec![AdviceMutation::extend_map(AdviceMap::from_iter([( + script_root, + script_felts, + )]))]; + + (Some(recipient), mutations) + }, + Err(DataStoreError::NoteScriptNotFound(_)) if metadata.is_private() => { + (None, Vec::new()) + }, + Err(DataStoreError::NoteScriptNotFound(_)) => { + return Err(TransactionKernelError::other(format!( + "note script with root {script_root} not found in data store for public note" + ))); + }, + Err(err) => { + return Err(TransactionKernelError::other_with_source( + "failed to retrieve note script from data store", + err, + )); + }, + }; + + let note_builder = OutputNoteBuilder::new(metadata, recipient_digest, recipient)?; + self.base_host.insert_output_note_builder(note_idx, note_builder)?; + + Ok(mutations) + } + /// Consumes `self` and returns the account delta, output notes, generated signatures and /// transaction progress. #[allow(clippy::type_complexity)] @@ -468,6 +519,24 @@ where .on_account_storage_map_witness_requested(current_account_id, map_root, map_key) .await .map_err(EventError::from), + TransactionEventData::NoteData { + note_idx, + metadata, + script_root, + recipient_digest, + note_inputs, + serial_num, + } => self + .on_note_script_requested( + script_root, + metadata, + recipient_digest, + note_idx, + note_inputs, + serial_num, + ) + .await + .map_err(EventError::from), } } } diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 9613872edd..0ce849b4ed 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -1,7 +1,6 @@ mod account_delta_tracker; use account_delta_tracker::AccountDeltaTracker; - mod storage_delta_tracker; mod link_map; @@ -10,7 +9,7 @@ pub use link_map::{LinkMap, MemoryViewer}; mod account_procedures; pub use account_procedures::AccountProcedureIndexMap; -mod note_builder; +pub(crate) mod note_builder; use miden_lib::StdLibrary; use note_builder::OutputNoteBuilder; @@ -40,7 +39,7 @@ use miden_objects::account::{ StorageSlotType, }; use miden_objects::asset::{Asset, AssetVault, FungibleAsset, VaultKey}; -use miden_objects::note::NoteId; +use miden_objects::note::{NoteId, NoteInputs, NoteMetadata, NoteRecipient, NoteScript}; use miden_objects::transaction::{ InputNote, InputNotes, @@ -50,7 +49,7 @@ use miden_objects::transaction::{ TransactionSummary, }; use miden_objects::vm::RowIndex; -use miden_objects::{Hasher, Word}; +use miden_objects::{Hasher, Word, ZERO}; use miden_processor::{ AdviceError, AdviceMutation, @@ -219,6 +218,25 @@ where // MUTATORS // -------------------------------------------------------------------------------------------- + /// Inserts an output note builder at the specified index. + /// + /// # Errors + /// Returns an error if a note builder already exists at the given index. + pub(super) fn insert_output_note_builder( + &mut self, + note_idx: usize, + note_builder: OutputNoteBuilder, + ) -> Result<(), TransactionKernelError> { + if self.output_notes.contains_key(¬e_idx) { + return Err(TransactionKernelError::other(format!( + "Attempted to create note builder for note index {} twice", + note_idx + ))); + } + self.output_notes.insert(note_idx, note_builder); + Ok(()) + } + /// Returns a mutable reference to the [`AccountProcedureIndexMap`]. pub fn load_foreign_account_code( &mut self, @@ -303,7 +321,7 @@ where }, TransactionEvent::NoteBeforeCreated => Ok(TransactionEventHandling::Handled(Vec::new())), - TransactionEvent::NoteAfterCreated => self.on_note_after_created(process).map(|_| TransactionEventHandling::Handled(Vec::new())), + TransactionEvent::NoteAfterCreated => self.on_note_after_created(process), TransactionEvent::NoteBeforeAddAsset => self.on_note_before_add_asset(process).map(|_| TransactionEventHandling::Handled(Vec::new())), TransactionEvent::NoteAfterAddAsset => Ok(TransactionEventHandling::Handled(Vec::new())), @@ -503,26 +521,65 @@ where )) } - /// Creates a new [OutputNoteBuilder] from the data on the operand stack and stores it into the - /// `output_notes` field of this [`TransactionBaseHost`]. + /// Handles the note creation event by extracting note data from the stack and advice provider. + /// + /// If the recipient data and note script are present in the advice provider, creates a new + /// [`OutputNoteBuilder`] and stores it in the `output_notes` field of this + /// [`TransactionBaseHost`]. Otherwise, returns [`TransactionEventHandling::Unhandled`] to + /// request the missing note script from the data store. /// - /// Expected stack state: `[event, NOTE_METADATA, RECIPIENT, ...]` + /// Expected stack state: `[event, NOTE_METADATA, note_ptr, RECIPIENT, note_idx]` fn on_note_after_created( &mut self, process: &ProcessState, - ) -> Result<(), TransactionKernelError> { - let stack = process.get_stack_state().split_off(1); - // # => [NOTE_METADATA] + ) -> Result { + let metadata_word = process.get_stack_word(1); + let metadata = NoteMetadata::try_from(metadata_word) + .map_err(TransactionKernelError::MalformedNoteMetadata)?; + + let recipient_digest = process.get_stack_word(6); + let note_idx = process.get_stack_item(10).as_int() as usize; - let note_idx: usize = stack[9].as_int() as usize; + let recipient = if process.advice_provider().get_mapped_values(&recipient_digest).is_some() + { + let (sn_script_hash, inputs_commitment) = + read_double_word_from_adv_map(process, recipient_digest)?; - assert_eq!(note_idx, self.output_notes.len(), "note index mismatch"); + let (sn_hash, script_root) = read_double_word_from_adv_map(process, sn_script_hash)?; - let note_builder = OutputNoteBuilder::new(stack, process.advice_provider())?; + let (serial_num, _) = read_double_word_from_adv_map(process, sn_hash)?; - self.output_notes.insert(note_idx, note_builder); + let inputs = extract_note_inputs(process, &inputs_commitment)?; - Ok(()) + let script_data = process.advice_provider().get_mapped_values(&script_root); + + if script_data.is_none() { + return Ok(TransactionEventHandling::Unhandled(TransactionEventData::NoteData { + note_idx, + metadata, + script_root, + recipient_digest, + note_inputs: inputs, + serial_num, + })); + } + + let script = NoteScript::try_from(script_data.unwrap()).map_err(|source| { + TransactionKernelError::MalformedNoteScript { + data: script_data.unwrap().to_vec(), + source, + } + })?; + + Some(NoteRecipient::new(serial_num, script, inputs)) + } else { + None + }; + + let note_builder = OutputNoteBuilder::new(metadata, recipient_digest, recipient)?; + self.insert_output_note_builder(note_idx, note_builder)?; + + Ok(TransactionEventHandling::Handled(Vec::new())) } /// Adds an asset at the top of the [OutputNoteBuilder] identified by the note pointer. @@ -1122,6 +1179,30 @@ where } } +/// Reads a double word (two [`Word`]s, 8 [`Felt`]s total) from the advice map. +/// +/// # Errors +/// Returns an error if the key is not present in the advice map or if the data is malformed +/// (not exactly 8 elements). +fn read_double_word_from_adv_map( + process: &ProcessState, + key: Word, +) -> Result<(Word, Word), TransactionKernelError> { + let data = process + .advice_provider() + .get_mapped_values(&key) + .ok_or_else(|| TransactionKernelError::MalformedRecipientData(vec![]))?; + + if data.len() != 8 { + return Err(TransactionKernelError::MalformedRecipientData(data.to_vec())); + } + + let first_word = Word::new([data[0], data[1], data[2], data[3]]); + let second_word = Word::new([data[4], data[5], data[6], data[7]]); + + Ok((first_word, second_word)) +} + impl<'store, STORE> TransactionBaseHost<'store, STORE> { /// Returns the underlying store of the base host. pub fn store(&self) -> &'store STORE { @@ -1129,6 +1210,56 @@ impl<'store, STORE> TransactionBaseHost<'store, STORE> { } } +/// Extracts and validates note inputs from the advice provider using trial unhashing. +/// +/// This function tries to determine the correct number of inputs by: +/// 1. Finding the last non-zero element as a starting point +/// 2. Building NoteInputs and checking if the hash matches inputs_commitment +/// 3. If not, incrementing num_inputs and trying again (up to 6 more times) +/// 4. If num_inputs grows to the size of inputs_data and there's still no match, returning an error +fn extract_note_inputs( + process: &ProcessState, + inputs_commitment: &Word, +) -> Result { + let inputs_data = process.advice_provider().get_mapped_values(inputs_commitment); + + let inputs = match inputs_data { + None => NoteInputs::default(), + Some(inputs) => { + // Start with the last non-zero element as a hint + let initial_num_inputs = + inputs.iter().rposition(|&x| x != ZERO).map(|pos| pos + 1).unwrap_or(0); + + // Try different input counts using trial unhashing + let mut num_inputs = initial_num_inputs; + + loop { + let candidate_inputs = NoteInputs::new(inputs[0..num_inputs].to_vec()) + .map_err(TransactionKernelError::MalformedNoteInputs)?; + + if candidate_inputs.commitment() == *inputs_commitment { + return Ok(candidate_inputs); + } + + num_inputs += 1; + if num_inputs > inputs.len() { + break; + } + } + + // If we've exhausted all attempts, return an error + return Err(TransactionKernelError::InvalidNoteInputs { + expected: *inputs_commitment, + actual: NoteInputs::new(inputs[0..num_inputs.min(inputs.len())].to_vec()) + .map(|i| i.commitment()) + .unwrap_or_default(), + }); + }, + }; + + Ok(inputs) +} + /// Extracts a word from a slice of field elements. pub(crate) fn extract_word(commitments: &[Felt], start: usize) -> Word { Word::from([ @@ -1187,4 +1318,19 @@ pub(super) enum TransactionEventData { /// The raw map key for which a witness is requested. map_key: Word, }, + /// The data necessary to request a note script from the data store. + NoteData { + /// The note index extracted from the stack. + note_idx: usize, + /// The note metadata extracted from the stack. + metadata: NoteMetadata, + /// The root of the note script being requested. + script_root: Word, + /// The recipient digest extracted from the stack. + recipient_digest: Word, + /// The note inputs extracted from the advice provider. + note_inputs: NoteInputs, + /// The serial number extracted from the advice provider. + serial_num: Word, + }, } diff --git a/crates/miden-tx/src/host/note_builder.rs b/crates/miden-tx/src/host/note_builder.rs index ae13b20d61..d4016f6e65 100644 --- a/crates/miden-tx/src/host/note_builder.rs +++ b/crates/miden-tx/src/host/note_builder.rs @@ -1,18 +1,7 @@ -use alloc::vec::Vec; - use miden_objects::asset::Asset; -use miden_objects::note::{ - Note, - NoteAssets, - NoteInputs, - NoteMetadata, - NoteRecipient, - NoteScript, - PartialNote, -}; -use miden_processor::AdviceProvider; +use miden_objects::note::{Note, NoteAssets, NoteMetadata, NoteRecipient, PartialNote}; -use super::{Felt, OutputNote, Word}; +use super::{OutputNote, Word}; use crate::errors::TransactionKernelError; // OUTPUT NOTE BUILDER @@ -31,93 +20,35 @@ impl OutputNoteBuilder { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - /// Returns a new [OutputNoteBuilder] read from the provided stack state and advice provider. - /// - /// The stack is expected to be in the following state: - /// - /// [NOTE_METADATA, RECIPIENT] - /// - /// Detailed note info such as assets and recipient (when available) are retrieved from the - /// advice provider. + /// Returns a new [OutputNoteBuilder] from the provided metadata, recipient digest, and optional + /// recipient. /// /// # Errors - /// Returns an error if: - /// - Note type specified via the stack is malformed. - /// - Sender account ID specified via the stack is invalid. - /// - A combination of note type, sender account ID, and note tag do not form a valid - /// [NoteMetadata] object. - /// - Recipient information in the advice provider is present but is malformed. - /// - A non-private note is missing recipient details. + /// Returns an error if the note is public but no recipient is provided. pub fn new( - stack: Vec, - adv_provider: &AdviceProvider, + metadata: NoteMetadata, + recipient_digest: Word, + recipient: Option, ) -> Result { - // read note metadata info from the stack and build the metadata object - let metadata_word = Word::from([stack[3], stack[2], stack[1], stack[0]]); - let metadata: NoteMetadata = metadata_word - .try_into() - .map_err(TransactionKernelError::MalformedNoteMetadata)?; - - // read recipient digest from the stack and try to build note recipient object if there is - // enough info available in the advice provider - let recipient_digest = Word::new([stack[8], stack[7], stack[6], stack[5]]); - - // This method returns an error if the mapped value is not found. - let recipient = if let Some(data) = adv_provider.get_mapped_values(&recipient_digest) { - if data.len() != 13 { - return Err(TransactionKernelError::MalformedRecipientData(data.to_vec())); - } - let inputs_commitment = Word::new([data[1], data[2], data[3], data[4]]); - let script_root = Word::new([data[5], data[6], data[7], data[8]]); - let serial_num = Word::from([data[9], data[10], data[11], data[12]]); - let script_data = adv_provider.get_mapped_values(&script_root).unwrap_or(&[]); - - let inputs_data = adv_provider.get_mapped_values(&inputs_commitment); - let inputs = match inputs_data { - None => NoteInputs::default(), - Some(inputs) => { - let num_inputs = data[0].as_int() as usize; - - // There must be at least `num_inputs` elements in the advice provider data, - // otherwise it is an error. - // - // It is possible to have more elements because of padding. The extra elements - // will be discarded below, and later their contents will be validated by - // computing the commitment and checking against the expected value. - if num_inputs > inputs.len() { - return Err(TransactionKernelError::TooFewElementsForNoteInputs { - specified: num_inputs as u64, - actual: inputs.len() as u64, - }); - } - - NoteInputs::new(inputs[0..num_inputs].to_vec()) - .map_err(TransactionKernelError::MalformedNoteInputs)? - }, - }; - - if inputs.commitment() != inputs_commitment { - return Err(TransactionKernelError::InvalidNoteInputs { - expected: inputs_commitment, - actual: inputs.commitment(), - }); - } - - let script = NoteScript::try_from(script_data).map_err(|source| { - TransactionKernelError::MalformedNoteScript { data: script_data.to_vec(), source } - })?; - let recipient = NoteRecipient::new(serial_num, script, inputs); - - Some(recipient) - } else if metadata.is_private() { - None - } else { - // if there are no recipient details and the note is not private, return an error + // For public notes, we must have a recipient + if !metadata.is_private() && recipient.is_none() { return Err(TransactionKernelError::PublicNoteMissingDetails( metadata, recipient_digest, )); - }; + } + + // If recipient is present, verify its digest matches the provided recipient_digest + if let Some(ref recipient) = recipient + && recipient.digest() != recipient_digest + { + return Err(TransactionKernelError::other(format!( + "recipient digest mismatch: expected {}, but recipient has digest {}", + recipient_digest, + recipient.digest() + ))); + } + Ok(Self { metadata, recipient_digest, diff --git a/crates/miden-tx/src/prover/prover_host.rs b/crates/miden-tx/src/prover/prover_host.rs index 2bb773656c..6f948217eb 100644 --- a/crates/miden-tx/src/prover/prover_host.rs +++ b/crates/miden-tx/src/prover/prover_host.rs @@ -123,6 +123,9 @@ where TransactionEventData::ForeignAccount { .. } => Ok(Vec::new()), TransactionEventData::AccountVaultAssetWitness { .. } => Ok(Vec::new()), TransactionEventData::AccountStorageMapWitness { .. } => Ok(Vec::new()), + // Note scripts should be in the advice provider at proving time, so there is + // nothing to do. + TransactionEventData::NoteData { .. } => Ok(Vec::new()), // We don't track enough information to handle this event. Since this just // improves error messages for users and the error should not be relevant during // proving, we ignore it. From 0215be70a1283d903119373167dead70c7637142 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sat, 1 Nov 2025 15:37:05 -0700 Subject: [PATCH 116/133] refactor: introduce TransactionKernelProcess --- crates/miden-tx/src/host/kernel_process.rs | 233 +++++++++++++++ crates/miden-tx/src/host/mod.rs | 318 +++++---------------- 2 files changed, 303 insertions(+), 248 deletions(-) create mode 100644 crates/miden-tx/src/host/kernel_process.rs diff --git a/crates/miden-tx/src/host/kernel_process.rs b/crates/miden-tx/src/host/kernel_process.rs new file mode 100644 index 0000000000..5140cc52e7 --- /dev/null +++ b/crates/miden-tx/src/host/kernel_process.rs @@ -0,0 +1,233 @@ +use miden_lib::transaction::memory::{ + ACCOUNT_STACK_TOP_PTR, + ACTIVE_INPUT_NOTE_PTR, + NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR, +}; +use miden_objects::account::AccountId; +use miden_objects::note::{NoteId, NoteInputs}; +use miden_objects::{Word, ZERO}; +use miden_processor::{EventError, ExecutionError, Felt, ProcessState}; + +use crate::errors::TransactionKernelError; + +// TRANSACTION KERNEL PROCESS +// ================================================================================================ + +pub(super) trait TransactionKernelProcess { + fn get_active_account_id(&self) -> Result; + + fn get_num_storage_slots(&self) -> Result; + + fn get_vault_root(&self, vault_root_ptr: Felt) -> Result; + + fn get_active_note_id(&self) -> Result, EventError>; + + fn read_note_recipient_info_from_adv_map( + &self, + recipient_digest: Word, + ) -> Result<(NoteInputs, Word, Word), TransactionKernelError>; + + fn read_note_inputs_from_adv_map( + &self, + inputs_commitment: &Word, + ) -> Result; + + fn has_advice_map_entry(&self, key: Word) -> bool; +} + +impl<'a> TransactionKernelProcess for ProcessState<'a> { + /// Returns the ID of the currently active account. + fn get_active_account_id(&self) -> Result { + let account_stack_top_ptr = + self.get_mem_value(self.ctx(), ACCOUNT_STACK_TOP_PTR).ok_or_else(|| { + TransactionKernelError::other("account stack top ptr should be initialized") + })?; + let account_stack_top_ptr = u32::try_from(account_stack_top_ptr).map_err(|_| { + TransactionKernelError::other("account stack top ptr should fit into a u32") + })?; + + let active_account_ptr = self + .get_mem_value(self.ctx(), account_stack_top_ptr) + .ok_or_else(|| TransactionKernelError::other("account id should be initialized"))?; + let active_account_ptr = u32::try_from(active_account_ptr).map_err(|_| { + TransactionKernelError::other("active account ptr should fit into a u32") + })?; + + let active_account_id_and_nonce = self + .get_mem_word(self.ctx(), active_account_ptr) + .map_err(|_| { + TransactionKernelError::other("active account ptr should be word-aligned") + })? + .ok_or_else(|| { + TransactionKernelError::other("active account id should be initialized") + })?; + + AccountId::try_from([active_account_id_and_nonce[1], active_account_id_and_nonce[0]]) + .map_err(|_| { + TransactionKernelError::other( + "active account id ptr should point to a valid account ID", + ) + }) + } + + /// Returns the number of storage slots initialized for the active account. + /// + /// # Errors + /// Returns an error if the memory location supposed to contain the account storage slot number + /// has not been initialized. + fn get_num_storage_slots(&self) -> Result { + let num_storage_slots_felt = self + .get_mem_value(self.ctx(), NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR) + .ok_or(TransactionKernelError::AccountStorageSlotsNumMissing( + NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR, + ))?; + + Ok(num_storage_slots_felt.as_int()) + } + + /// Returns the ID of the active note, or None if the note execution hasn't started yet or has + /// already ended. + /// + /// # Errors + /// Returns an error if the address of the active note is invalid (e.g., greater than + /// `u32::MAX`). + fn get_active_note_id(&self) -> Result, EventError> { + // get the note address in `Felt` or return `None` if the address hasn't been accessed + // previously. + let note_address_felt = match self.get_mem_value(self.ctx(), ACTIVE_INPUT_NOTE_PTR) { + Some(addr) => addr, + None => return Ok(None), + }; + // convert note address into u32 + let note_address = u32::try_from(note_address_felt).map_err(|_| { + EventError::from(format!( + "failed to convert {note_address_felt} into a memory address (u32)" + )) + })?; + // if `note_address` == 0 note execution has ended and there is no valid note address + if note_address == 0 { + Ok(None) + } else { + Ok(self + .get_mem_word(self.ctx(), note_address) + .map_err(ExecutionError::MemoryError)? + .map(NoteId::from)) + } + } + + /// Returns the vault root at the provided pointer. + fn get_vault_root(&self, vault_root_ptr: Felt) -> Result { + let vault_root_ptr = u32::try_from(vault_root_ptr).map_err(|_err| { + TransactionKernelError::other(format!( + "vault root ptr should fit into a u32, but was {vault_root_ptr}" + )) + })?; + self.get_mem_word(self.ctx(), vault_root_ptr) + .map_err(|_err| { + TransactionKernelError::other(format!( + "vault root ptr {vault_root_ptr} is not word-aligned" + )) + })? + .ok_or_else(|| { + TransactionKernelError::other(format!( + "vault root ptr {vault_root_ptr} was not initialized" + )) + }) + } + + fn read_note_recipient_info_from_adv_map( + &self, + recipient_digest: Word, + ) -> Result<(NoteInputs, Word, Word), TransactionKernelError> { + let (sn_script_hash, inputs_commitment) = + read_double_word_from_adv_map(self, recipient_digest)?; + let (sn_hash, script_root) = read_double_word_from_adv_map(self, sn_script_hash)?; + let (serial_num, _) = read_double_word_from_adv_map(self, sn_hash)?; + + let inputs = self.read_note_inputs_from_adv_map(&inputs_commitment)?; + + Ok((inputs, script_root, serial_num)) + } + + /// Extracts and validates note inputs from the advice provider using trial unhashing. + /// + /// This function tries to determine the correct number of inputs by: + /// 1. Finding the last non-zero element as a starting point + /// 2. Building NoteInputs and checking if the hash matches inputs_commitment + /// 3. If not, incrementing num_inputs and trying again (up to 6 more times) + /// 4. If num_inputs grows to the size of inputs_data and there's still no match, returning an + /// error + fn read_note_inputs_from_adv_map( + &self, + inputs_commitment: &Word, + ) -> Result { + let inputs_data = self.advice_provider().get_mapped_values(inputs_commitment); + + let inputs = match inputs_data { + None => NoteInputs::default(), + Some(inputs) => { + // Start with the last non-zero element as a hint + let initial_num_inputs = + inputs.iter().rposition(|&x| x != ZERO).map(|pos| pos + 1).unwrap_or(0); + + // Try different input counts using trial unhashing + let mut num_inputs = initial_num_inputs; + + loop { + let candidate_inputs = NoteInputs::new(inputs[0..num_inputs].to_vec()) + .map_err(TransactionKernelError::MalformedNoteInputs)?; + + if candidate_inputs.commitment() == *inputs_commitment { + return Ok(candidate_inputs); + } + + num_inputs += 1; + if num_inputs > inputs.len() { + break; + } + } + + // If we've exhausted all attempts, return an error + return Err(TransactionKernelError::InvalidNoteInputs { + expected: *inputs_commitment, + actual: NoteInputs::new(inputs[0..num_inputs.min(inputs.len())].to_vec()) + .map(|i| i.commitment()) + .unwrap_or_default(), + }); + }, + }; + + Ok(inputs) + } + + fn has_advice_map_entry(&self, key: Word) -> bool { + self.advice_provider().get_mapped_values(&key).is_some() + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Reads a double word (two [`Word`]s, 8 [`Felt`]s total) from the advice map. +/// +/// # Errors +/// Returns an error if the key is not present in the advice map or if the data is malformed +/// (not exactly 8 elements). +fn read_double_word_from_adv_map( + process: &ProcessState, + key: Word, +) -> Result<(Word, Word), TransactionKernelError> { + let data = process + .advice_provider() + .get_mapped_values(&key) + .ok_or_else(|| TransactionKernelError::MalformedRecipientData(vec![]))?; + + if data.len() != 8 { + return Err(TransactionKernelError::MalformedRecipientData(data.to_vec())); + } + + let first_word = Word::new([data[0], data[1], data[2], data[3]]); + let second_word = Word::new([data[4], data[5], data[6], data[7]]); + + Ok((first_word, second_word)) +} diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 0ce849b4ed..5dda66ee19 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -13,20 +13,19 @@ pub(crate) mod note_builder; use miden_lib::StdLibrary; use note_builder::OutputNoteBuilder; +mod kernel_process; +use kernel_process::TransactionKernelProcess; + mod script_mast_forest_store; pub use script_mast_forest_store::ScriptMastForestStore; mod tx_progress; + use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::sync::Arc; use alloc::vec::Vec; -use miden_lib::transaction::memory::{ - ACCOUNT_STACK_TOP_PTR, - ACTIVE_INPUT_NOTE_PTR, - NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR, -}; use miden_lib::transaction::{EventId, TransactionEvent, TransactionEventError}; use miden_objects::account::{ AccountCode, @@ -49,14 +48,13 @@ use miden_objects::transaction::{ TransactionSummary, }; use miden_objects::vm::RowIndex; -use miden_objects::{Hasher, Word, ZERO}; +use miden_objects::{Hasher, Word}; use miden_processor::{ AdviceError, AdviceMutation, ContextId, EventError, EventHandlerRegistry, - ExecutionError, Felt, MastForest, MastForestStore, @@ -347,7 +345,7 @@ where }, TransactionEvent::NoteExecutionStart => { - let note_id = Self::get_active_note_id(process)?.expect( + let note_id = process.get_active_note_id()?.expect( "Note execution interval measurement is incorrect: check the placement of the start and the end of the interval", ); self.tx_progress.start_note_execution(process.clk(), note_id); @@ -540,20 +538,23 @@ where let recipient_digest = process.get_stack_word(6); let note_idx = process.get_stack_item(10).as_int() as usize; - let recipient = if process.advice_provider().get_mapped_values(&recipient_digest).is_some() - { - let (sn_script_hash, inputs_commitment) = - read_double_word_from_adv_map(process, recipient_digest)?; - - let (sn_hash, script_root) = read_double_word_from_adv_map(process, sn_script_hash)?; - - let (serial_num, _) = read_double_word_from_adv_map(process, sn_hash)?; - - let inputs = extract_note_inputs(process, &inputs_commitment)?; + // try to read the full recipient from the advice provider + let recipient = if process.has_advice_map_entry(recipient_digest) { + let (inputs, script_root, serial_num) = + process.read_note_recipient_info_from_adv_map(recipient_digest)?; - let script_data = process.advice_provider().get_mapped_values(&script_root); + if let Some(script_data) = process.advice_provider().get_mapped_values(&script_root) { + let script = NoteScript::try_from(script_data).map_err(|source| { + TransactionKernelError::MalformedNoteScript { + data: script_data.to_vec(), + source, + } + })?; - if script_data.is_none() { + Some(NoteRecipient::new(serial_num, script, inputs)) + } else { + // we couldn't build the full recipient because script root was missing; return the + // info that we did read so that we could request the script from the data store return Ok(TransactionEventHandling::Unhandled(TransactionEventData::NoteData { note_idx, metadata, @@ -563,15 +564,6 @@ where serial_num, })); } - - let script = NoteScript::try_from(script_data.unwrap()).map_err(|source| { - TransactionKernelError::MalformedNoteScript { - data: script_data.unwrap().to_vec(), - source, - } - })?; - - Some(NoteRecipient::new(serial_num, script, inputs)) } else { None }; @@ -650,7 +642,7 @@ where let slot_index = process.get_stack_item(1); // get number of storage slots initialized by the account - let num_storage_slot = Self::get_num_storage_slots(process)?; + let num_storage_slot = process.get_num_storage_slots()?; if slot_index.as_int() >= num_storage_slot { return Err(TransactionKernelError::InvalidStorageSlotIndex { @@ -723,11 +715,11 @@ where map_key: Word, process: &ProcessState, ) -> Result { - let current_account_id = Self::get_current_account_id(process)?; + let current_account_id = process.get_active_account_id()?; let hashed_map_key = StorageMap::hash_key(map_key); let leaf_index = StorageMap::hashed_map_key_to_leaf_index(hashed_map_key); - if Self::advice_provider_has_merkle_path::<{ StorageMap::DEPTH }>( + if advice_provider_has_merkle_path::<{ StorageMap::DEPTH }>( process, current_map_root, leaf_index, @@ -784,7 +776,7 @@ where let slot_index = process.get_stack_item(1); // get number of storage slots initialized by the account - let num_storage_slot = Self::get_num_storage_slots(process)?; + let num_storage_slot = process.get_num_storage_slots()?; if slot_index.as_int() >= num_storage_slot { return Err(TransactionKernelError::InvalidStorageSlotIndex { @@ -913,7 +905,7 @@ where ) })?; let vault_root_ptr = stack_top[1]; - let vault_root = Self::get_vault_root(process, vault_root_ptr)?; + let vault_root = process.get_vault_root(vault_root_ptr)?; let vault_key = VaultKey::from_account_id(faucet_id).ok_or_else(|| { TransactionKernelError::other(format!( @@ -937,7 +929,7 @@ where })?; let vault_root_ptr = process.get_stack_item(5); - let vault_root = Self::get_vault_root(process, vault_root_ptr)?; + let vault_root = process.get_vault_root(vault_root_ptr)?; self.on_account_vault_asset_accessed(process, asset.vault_key(), vault_root) } @@ -951,13 +943,13 @@ where current_vault_root: Word, ) -> Result { let leaf_index = Felt::new(vault_key.to_leaf_index().value()); - let current_account_id = Self::get_current_account_id(process)?; + let active_account_id = process.get_active_account_id()?; // Note that we check whether a merkle path for the current vault root is present, not // necessarily for the root we are going to request. This is because the end goal is to // enable access to an asset against the current vault root, and so if this // condition is already satisfied, there is nothing to request. - if Self::advice_provider_has_merkle_path::<{ AssetVault::DEPTH }>( + if advice_provider_has_merkle_path::<{ AssetVault::DEPTH }>( process, current_vault_root, leaf_index, @@ -967,7 +959,7 @@ where } else { // For the native account we need to explicitly request the initial vault root, while // for foreign accounts the current vault root is always the initial one. - let vault_root = if current_account_id == self.initial_account_header().id() { + let vault_root = if active_account_id == self.initial_account_header().id() { self.initial_account_header().vault_root() } else { current_vault_root @@ -976,7 +968,7 @@ where // If the merkle path is not in the store return the data to request it. Ok(TransactionEventHandling::Unhandled( TransactionEventData::AccountVaultAssetWitness { - current_account_id, + current_account_id: active_account_id, vault_root, asset_key: vault_key, }, @@ -987,109 +979,6 @@ where // HELPER FUNCTIONS // -------------------------------------------------------------------------------------------- - /// Returns the ID of the active note, or None if the note execution hasn't started yet or has - /// already ended. - /// - /// # Errors - /// Returns an error if the address of the active note is invalid (e.g., greater than - /// `u32::MAX`). - fn get_active_note_id(process: &ProcessState) -> Result, EventError> { - // get the note address in `Felt` or return `None` if the address hasn't been accessed - // previously. - let note_address_felt = match process.get_mem_value(process.ctx(), ACTIVE_INPUT_NOTE_PTR) { - Some(addr) => addr, - None => return Ok(None), - }; - // convert note address into u32 - let note_address = u32::try_from(note_address_felt).map_err(|_| { - EventError::from(format!( - "failed to convert {note_address_felt} into a memory address (u32)" - )) - })?; - // if `note_address` == 0 note execution has ended and there is no valid note address - if note_address == 0 { - Ok(None) - } else { - Ok(process - .get_mem_word(process.ctx(), note_address) - .map_err(ExecutionError::MemoryError)? - .map(NoteId::from)) - } - } - - /// Returns the ID of the currently executing account. - fn get_current_account_id(process: &ProcessState) -> Result { - let account_stack_top_ptr = - process.get_mem_value(process.ctx(), ACCOUNT_STACK_TOP_PTR).ok_or_else(|| { - TransactionKernelError::other("account stack top ptr should be initialized") - })?; - let account_stack_top_ptr = u32::try_from(account_stack_top_ptr).map_err(|_| { - TransactionKernelError::other("account stack top ptr should fit into a u32") - })?; - - let current_account_ptr = process - .get_mem_value(process.ctx(), account_stack_top_ptr) - .ok_or_else(|| TransactionKernelError::other("account id should be initialized"))?; - let current_account_ptr = u32::try_from(current_account_ptr).map_err(|_| { - TransactionKernelError::other("current account ptr should fit into a u32") - })?; - - let current_account_id_and_nonce = process - .get_mem_word(process.ctx(), current_account_ptr) - .map_err(|_| { - TransactionKernelError::other("current account ptr should be word-aligned") - })? - .ok_or_else(|| { - TransactionKernelError::other("current account id should be initialized") - })?; - - AccountId::try_from([current_account_id_and_nonce[1], current_account_id_and_nonce[0]]) - .map_err(|_| { - TransactionKernelError::other( - "current account id ptr should point to a valid account ID", - ) - }) - } - - /// Returns the vault root at the provided pointer. - fn get_vault_root( - process: &ProcessState, - vault_root_ptr: Felt, - ) -> Result { - let vault_root_ptr = u32::try_from(vault_root_ptr).map_err(|_err| { - TransactionKernelError::other(format!( - "vault root ptr should fit into a u32, but was {vault_root_ptr}" - )) - })?; - process - .get_mem_word(process.ctx(), vault_root_ptr) - .map_err(|_err| { - TransactionKernelError::other(format!( - "vault root ptr {vault_root_ptr} is not word-aligned" - )) - })? - .ok_or_else(|| { - TransactionKernelError::other(format!( - "vault root ptr {vault_root_ptr} was not initialized" - )) - }) - } - - /// Returns the number of storage slots initialized for the current account. - /// - /// # Errors - /// Returns an error if the memory location supposed to contain the account storage slot number - /// has not been initialized. - fn get_num_storage_slots(process: &ProcessState) -> Result { - let num_storage_slots_felt = process - .get_mem_value(process.ctx(), NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR) - .ok_or(TransactionKernelError::AccountStorageSlotsNumMissing( - NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR, - ))?; - - Ok(num_storage_slots_felt.as_int()) - } - /// Builds a [TransactionSummary] by extracting data from the advice provider and validating /// commitments against the host's state. pub(crate) fn build_tx_summary( @@ -1154,53 +1043,6 @@ where Ok(TransactionSummary::new(account_delta, input_notes, output_notes, salt)) } - - /// Returns `true` if the advice provider has a merkle path for the provided root and leaf - /// index, `false` otherwise. - fn advice_provider_has_merkle_path( - process: &ProcessState, - root: Word, - leaf_index: Felt, - ) -> Result { - match process - .advice_provider() - .get_merkle_path(root, &Felt::from(TREE_DEPTH), &leaf_index) - { - // Merkle path is already in the store; consider the event handled. - Ok(_) => Ok(true), - // This means the merkle path is missing in the advice provider. - Err(AdviceError::MerkleStoreLookupFailed(_)) => Ok(false), - // We should never encounter this as long as our inputs to get_merkle_path are correct. - Err(err) => Err(TransactionKernelError::other_with_source( - "unexpected get_merkle_path error", - err, - )), - } - } -} - -/// Reads a double word (two [`Word`]s, 8 [`Felt`]s total) from the advice map. -/// -/// # Errors -/// Returns an error if the key is not present in the advice map or if the data is malformed -/// (not exactly 8 elements). -fn read_double_word_from_adv_map( - process: &ProcessState, - key: Word, -) -> Result<(Word, Word), TransactionKernelError> { - let data = process - .advice_provider() - .get_mapped_values(&key) - .ok_or_else(|| TransactionKernelError::MalformedRecipientData(vec![]))?; - - if data.len() != 8 { - return Err(TransactionKernelError::MalformedRecipientData(data.to_vec())); - } - - let first_word = Word::new([data[0], data[1], data[2], data[3]]); - let second_word = Word::new([data[4], data[5], data[6], data[7]]); - - Ok((first_word, second_word)) } impl<'store, STORE> TransactionBaseHost<'store, STORE> { @@ -1210,65 +1052,8 @@ impl<'store, STORE> TransactionBaseHost<'store, STORE> { } } -/// Extracts and validates note inputs from the advice provider using trial unhashing. -/// -/// This function tries to determine the correct number of inputs by: -/// 1. Finding the last non-zero element as a starting point -/// 2. Building NoteInputs and checking if the hash matches inputs_commitment -/// 3. If not, incrementing num_inputs and trying again (up to 6 more times) -/// 4. If num_inputs grows to the size of inputs_data and there's still no match, returning an error -fn extract_note_inputs( - process: &ProcessState, - inputs_commitment: &Word, -) -> Result { - let inputs_data = process.advice_provider().get_mapped_values(inputs_commitment); - - let inputs = match inputs_data { - None => NoteInputs::default(), - Some(inputs) => { - // Start with the last non-zero element as a hint - let initial_num_inputs = - inputs.iter().rposition(|&x| x != ZERO).map(|pos| pos + 1).unwrap_or(0); - - // Try different input counts using trial unhashing - let mut num_inputs = initial_num_inputs; - - loop { - let candidate_inputs = NoteInputs::new(inputs[0..num_inputs].to_vec()) - .map_err(TransactionKernelError::MalformedNoteInputs)?; - - if candidate_inputs.commitment() == *inputs_commitment { - return Ok(candidate_inputs); - } - - num_inputs += 1; - if num_inputs > inputs.len() { - break; - } - } - - // If we've exhausted all attempts, return an error - return Err(TransactionKernelError::InvalidNoteInputs { - expected: *inputs_commitment, - actual: NoteInputs::new(inputs[0..num_inputs.min(inputs.len())].to_vec()) - .map(|i| i.commitment()) - .unwrap_or_default(), - }); - }, - }; - - Ok(inputs) -} - -/// Extracts a word from a slice of field elements. -pub(crate) fn extract_word(commitments: &[Felt], start: usize) -> Word { - Word::from([ - commitments[start], - commitments[start + 1], - commitments[start + 2], - commitments[start + 3], - ]) -} +// TRANSACTION EVENT HANDLING +// ================================================================================================ /// Indicates whether a [`TransactionEvent`] was handled or not. /// @@ -1334,3 +1119,40 @@ pub(super) enum TransactionEventData { serial_num: Word, }, } + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Returns `true` if the advice provider has a merkle path for the provided root and leaf +/// index, `false` otherwise. +fn advice_provider_has_merkle_path( + process: &ProcessState, + root: Word, + leaf_index: Felt, +) -> Result { + match process + .advice_provider() + .get_merkle_path(root, &Felt::from(TREE_DEPTH), &leaf_index) + { + // Merkle path is already in the store; consider the event handled. + Ok(_) => Ok(true), + // This means the merkle path is missing in the advice provider. + Err(AdviceError::MerkleStoreLookupFailed(_)) => Ok(false), + // We should never encounter this as long as our inputs to get_merkle_path are correct. + Err(err) => Err(TransactionKernelError::other_with_source( + "unexpected get_merkle_path error", + err, + )), + } +} + +/// Extracts a word from a slice of field elements. +#[inline(always)] +fn extract_word(commitments: &[Felt], start: usize) -> Word { + Word::from([ + commitments[start], + commitments[start + 1], + commitments[start + 2], + commitments[start + 3], + ]) +} From d8bfad388ef4841bc56dba8787dd29e4e377f12f Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 3 Nov 2025 22:39:55 +0800 Subject: [PATCH 117/133] chore: use multiple maps in storage map test (#2039) * feat: Use multiple maps in storage map test * chore: Use two identical maps --- .../src/kernel_tests/tx/test_account.rs | 76 ------------ .../src/kernel_tests/tx/test_account_delta.rs | 117 ++++++++++++++++++ 2 files changed, 117 insertions(+), 76 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 874564229e..84e0f52bdc 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -987,82 +987,6 @@ async fn test_compute_storage_commitment() -> anyhow::Result<()> { Ok(()) } -/// Tests that the storage map updates for a _new public_ account in an executed and proven -/// transaction match up. -/// -/// This is an interesting test case because for new public accounts the prover converts the partial -/// account into a full account as a temporary measure. Because of the additional hashing of map -/// keys in storage maps, this test ensures that the partial storage map is correctly converted into -/// a full storage map. If we end up representing new public accounts as account deltas, this test -/// can likely go away. -#[tokio::test] -async fn proven_tx_storage_map_matches_executed_tx_for_new_account() -> anyhow::Result<()> { - // Build a public account so the proven transaction includes the account update. - let mock_slots = AccountStorage::mock_storage_slots(); - let account = AccountBuilder::new([1; 32]) - .storage_mode(AccountStorageMode::Public) - .with_auth_component(Auth::IncrNonce) - .with_component(MockAccountComponent::with_slots(mock_slots.clone())) - .build()?; - - // The index of the mock map in account storage is 2. - let map_index = 2u8; - // Fetch a random existing key from the map. - let StorageSlot::Map(mock_map) = &mock_slots[map_index as usize] else { - panic!("expected map"); - }; - let existing_key = mock_map.entries().next().unwrap().0; - - let value0 = Word::from([3, 4, 5, 6u32]); - - let code = format!( - " - use.mock::account - - begin - # Update an existing key. - push.{value0} - push.{existing_key} - push.{map_index} - # => [index, KEY, VALUE] - call.account::set_map_item - - exec.::std::sys::truncate_stack - end - " - ); - - let builder = ScriptBuilder::with_mock_libraries()?; - let source_manager = builder.source_manager(); - let tx_script = builder.compile_tx_script(code)?; - - let tx = TransactionContextBuilder::new(account.clone()) - .tx_script(tx_script) - .with_source_manager(source_manager) - .build()? - .execute() - .await?; - - let map_delta = tx.account_delta().storage().maps().get(&map_index).unwrap(); - assert_eq!( - map_delta.entries().get(&LexicographicWord::new(*existing_key)).unwrap(), - &value0 - ); - - let proven_tx = LocalTransactionProver::default().prove_dummy(tx.clone())?; - - let AccountUpdateDetails::Delta(delta) = proven_tx.account_update().details() else { - panic!("expected delta"); - }; - - let proven_tx_account = Account::try_from(delta)?; - let exec_tx_account = Account::try_from(tx.account_delta())?; - - assert_eq!(proven_tx_account.storage(), exec_tx_account.storage()); - - Ok(()) -} - /// Tests that an account with a non-empty map can be created. /// /// In particular, this tests the account delta logic for (non-empty) storage slots for _new_ diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs index 02c857d3e7..b405fab2bb 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_delta.rs @@ -5,10 +5,14 @@ use std::string::String; use anyhow::Context; use miden_lib::testing::account_component::MockAccountComponent; use miden_lib::utils::ScriptBuilder; +use miden_objects::account::delta::AccountUpdateDetails; use miden_objects::account::{ + Account, AccountBuilder, + AccountDelta, AccountId, AccountStorage, + AccountStorageMode, AccountType, StorageMap, StorageSlot, @@ -34,6 +38,7 @@ use miden_objects::testing::constants::{ use miden_objects::testing::storage::{STORAGE_INDEX_0, STORAGE_INDEX_2}; use miden_objects::transaction::TransactionScript; use miden_objects::{EMPTY_WORD, Felt, LexicographicWord, Word, ZERO}; +use miden_tx::LocalTransactionProver; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use winter_rand_utils::rand_value; @@ -752,6 +757,118 @@ async fn asset_and_storage_delta() -> anyhow::Result<()> { Ok(()) } +/// Tests that the storage map updates for a _new public_ account in an executed and proven +/// transaction match up. +/// +/// This is an interesting test case because: +/// - for new accounts in general, the storage map entries must be available in the advice provider +/// and the resulting delta must be convertible to a full account. +/// - it creates an account with two identical storage maps. +/// - The prover mutates the delta to account for fee logic. +#[tokio::test] +async fn proven_tx_storage_maps_matches_executed_tx_for_new_account() -> anyhow::Result<()> { + // Use two identical maps to test that they are properly handled + // (see also https://github.com/0xMiden/miden-base/issues/2037). + let map0 = StorageMap::with_entries([(rand_value(), rand_value())])?; + let map1 = map0.clone(); + let mut map2 = StorageMap::with_entries([ + (rand_value(), rand_value()), + (rand_value(), rand_value()), + (rand_value(), rand_value()), + (rand_value(), rand_value()), + ])?; + + // Build a public account so the proven transaction includes the account update. + let account = AccountBuilder::new([1; 32]) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(Auth::IncrNonce) + .with_component(MockAccountComponent::with_slots(vec![ + AccountStorage::mock_item_0().slot, + StorageSlot::Map(map0.clone()), + StorageSlot::Map(map1.clone()), + AccountStorage::mock_item_1().slot, + StorageSlot::Map(map2.clone()), + ])) + .build()?; + + let map0_index = 1; + let map1_index = 2; + let map2_index = 4; + // Fetch a random existing key from the map. + let existing_key = *map2.entries().next().unwrap().0; + let value0 = Word::from([3, 4, 5, 6u32]); + + let code = format!( + " + use.mock::account + + begin + # Update an existing key. + push.{value0} + push.{existing_key} + push.{map2_index} + # => [index, KEY, VALUE] + call.account::set_map_item + + exec.::std::sys::truncate_stack + end + " + ); + + let builder = ScriptBuilder::with_mock_libraries()?; + let source_manager = builder.source_manager(); + let tx_script = builder.compile_tx_script(code)?; + + let tx = TransactionContextBuilder::new(account.clone()) + .tx_script(tx_script) + .with_source_manager(source_manager) + .build()? + .execute() + .await?; + + map2.insert(existing_key, value0)?; + + for (map_index, expected_map) in [(map0_index, map0), (map1_index, map1), (map2_index, map2)] { + let map_delta = tx.account_delta().storage().maps().get(&map_index).unwrap(); + assert_eq!( + map_delta + .entries() + .iter() + .map(|(key, value)| (*key.inner(), *value)) + .collect::>(), + expected_map.into_entries() + ); + } + + let proven_tx = LocalTransactionProver::default().prove_dummy(tx.clone())?; + + let AccountUpdateDetails::Delta(proven_tx_delta) = proven_tx.account_update().details() else { + panic!("expected delta"); + }; + + let proven_tx_account = Account::try_from(proven_tx_delta)?; + let exec_tx_account = Account::try_from(tx.account_delta())?; + + assert_eq!(proven_tx_account.storage(), exec_tx_account.storage()); + + // Check the conversion back into a full-state delta works correctly. + let proven_tx_delta_converted = AccountDelta::try_from(proven_tx_account)?; + let exec_tx_delta_converted = AccountDelta::try_from(exec_tx_account)?; + + // Check that the deltas from proven and executed tx, which were converted from accounts are + // identical. This is essentially a roundtrip test. + assert_eq!(&proven_tx_delta_converted, proven_tx_delta); + assert_eq!(&exec_tx_delta_converted, tx.account_delta()); + assert_eq!(&proven_tx_delta_converted, tx.account_delta()); + + // The commitments should match as well. + assert_eq!(proven_tx_delta_converted.to_commitment(), proven_tx_delta.to_commitment()); + assert_eq!(exec_tx_delta_converted.to_commitment(), tx.account_delta().to_commitment()); + assert_eq!(proven_tx_delta_converted.to_commitment(), tx.account_delta().to_commitment()); + + Ok(()) +} + /// Tests that adding a fungible asset with amount zero to the account vault works and does not /// result in an account delta entry. #[tokio::test] From e8ac8600f15188bd02936c0f0ad24dba3ac71e92 Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Mon, 3 Nov 2025 20:53:23 +0530 Subject: [PATCH 118/133] chore: rename `VaultKey` to `AssetVaultKey` (#2024) * feat: unify vault_key vs. asset_key naming * fix: add changelog * Update CHANGELOG.md --------- Co-authored-by: Marti --- CHANGELOG.md | 2 +- crates/miden-objects/src/asset/fungible.rs | 7 ++--- crates/miden-objects/src/asset/mod.rs | 4 +-- crates/miden-objects/src/asset/nonfungible.rs | 6 ++--- .../src/asset/vault/asset_witness.rs | 8 +++--- crates/miden-objects/src/asset/vault/mod.rs | 6 ++--- .../miden-objects/src/asset/vault/partial.rs | 12 ++++----- .../src/asset/vault/vault_key.rs | 27 ++++++++++--------- crates/miden-objects/src/errors.rs | 8 +++--- .../miden-testing/src/tx_context/context.rs | 4 +-- crates/miden-tx/src/errors/mod.rs | 4 +-- crates/miden-tx/src/executor/data_store.rs | 4 +-- crates/miden-tx/src/executor/exec_host.rs | 4 +-- crates/miden-tx/src/host/mod.rs | 8 +++--- 14 files changed, 53 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67b7b6d762..3e20ca7935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,7 @@ - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963)). - Added `MastArtifact`, `PackageExport`, `PackageManifest`, `AttributeSet`, `QualifiedProcedureName`, `Section` and `SectionId` to re-export section ([#1984](https://github.com/0xMiden/miden-base/pull/1984) and [#2015](https://github.com/0xMiden/miden-base/pull/2015)). - [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). -- [BREAKING] Introduce `VaultKey` newtype wrapper for asset vault keys ([#1978]https://github.com/0xMiden/miden-base/pull/1978). +- [BREAKING] Introduce `AssetVaultKey` newtype wrapper for asset vault keys ([#1978](https://github.com/0xMiden/miden-base/pull/1978), [#2024](https://github.com/0xMiden/miden-base/pull/2024)). - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). - Added `network_fungible_faucet` and `MINT` & `BURN` notes ([#1925](https://github.com/0xMiden/miden-base/pull/1925)) - Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). diff --git a/crates/miden-objects/src/asset/fungible.rs b/crates/miden-objects/src/asset/fungible.rs index 94c90af4c2..95e7dfdcb6 100644 --- a/crates/miden-objects/src/asset/fungible.rs +++ b/crates/miden-objects/src/asset/fungible.rs @@ -2,7 +2,7 @@ use alloc::boxed::Box; use alloc::string::ToString; use core::fmt; -use super::vault::VaultKey; +use super::vault::AssetVaultKey; use super::{AccountType, Asset, AssetError, Felt, Word, ZERO, is_not_a_non_fungible_asset}; use crate::account::{AccountId, AccountIdPrefix}; use crate::utils::serde::{ @@ -84,8 +84,9 @@ impl FungibleAsset { } /// Returns the key which is used to store this asset in the account vault. - pub fn vault_key(&self) -> VaultKey { - VaultKey::from_account_id(self.faucet_id).expect("faucet ID should be of type fungible") + pub fn vault_key(&self) -> AssetVaultKey { + AssetVaultKey::from_account_id(self.faucet_id) + .expect("faucet ID should be of type fungible") } // OPERATIONS diff --git a/crates/miden-objects/src/asset/mod.rs b/crates/miden-objects/src/asset/mod.rs index 4113e5e070..3c3fc7d73c 100644 --- a/crates/miden-objects/src/asset/mod.rs +++ b/crates/miden-objects/src/asset/mod.rs @@ -22,7 +22,7 @@ mod token_symbol; pub use token_symbol::TokenSymbol; mod vault; -pub use vault::{AssetVault, AssetWitness, PartialVault, VaultKey}; +pub use vault::{AssetVault, AssetVaultKey, AssetWitness, PartialVault}; // ASSET // ================================================================================================ @@ -137,7 +137,7 @@ impl Asset { } /// Returns the key which is used to store this asset in the account vault. - pub fn vault_key(&self) -> VaultKey { + pub fn vault_key(&self) -> AssetVaultKey { match self { Self::Fungible(asset) => asset.vault_key(), Self::NonFungible(asset) => asset.vault_key(), diff --git a/crates/miden-objects/src/asset/nonfungible.rs b/crates/miden-objects/src/asset/nonfungible.rs index a30384921c..b0fa5f5ddd 100644 --- a/crates/miden-objects/src/asset/nonfungible.rs +++ b/crates/miden-objects/src/asset/nonfungible.rs @@ -3,7 +3,7 @@ use alloc::string::ToString; use alloc::vec::Vec; use core::fmt; -use super::vault::VaultKey; +use super::vault::AssetVaultKey; use super::{AccountIdPrefix, AccountType, Asset, AssetError, Felt, Hasher, Word}; use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use crate::{FieldElement, WORD_SIZE}; @@ -107,7 +107,7 @@ impl NonFungibleAsset { /// It also ensures that there is never any collision in the leaf index between a non-fungible /// asset and a fungible asset, as the former's vault key always has the fungible bit set to `0` /// and the latter's vault key always has the bit set to `1`. - pub fn vault_key(&self) -> VaultKey { + pub fn vault_key(&self) -> AssetVaultKey { let mut vault_key = self.0; // Swap prefix of faucet ID with hash0. @@ -117,7 +117,7 @@ impl NonFungibleAsset { vault_key[3] = AccountIdPrefix::clear_fungible_bit(self.faucet_id_prefix().version(), vault_key[3]); - VaultKey::new_unchecked(vault_key) + AssetVaultKey::new_unchecked(vault_key) } /// Return ID prefix of the faucet which issued this asset. diff --git a/crates/miden-objects/src/asset/vault/asset_witness.rs b/crates/miden-objects/src/asset/vault/asset_witness.rs index 0e56ddf20b..41154d23c8 100644 --- a/crates/miden-objects/src/asset/vault/asset_witness.rs +++ b/crates/miden-objects/src/asset/vault/asset_witness.rs @@ -1,6 +1,6 @@ use miden_crypto::merkle::{InnerNodeInfo, SmtLeaf, SmtProof}; -use super::vault_key::VaultKey; +use super::vault_key::AssetVaultKey; use crate::AssetError; use crate::asset::Asset; @@ -25,7 +25,7 @@ impl AssetWitness { for (vault_key, asset) in smt_proof.leaf().entries() { let asset = Asset::try_from(asset)?; if *vault_key != asset.vault_key().into() { - return Err(AssetError::VaultKeyMismatch { + return Err(AssetError::AssetVaultKeyMismatch { actual: *vault_key, expected: asset.vault_key().into(), }); @@ -47,7 +47,7 @@ impl AssetWitness { // -------------------------------------------------------------------------------------------- /// Searches for an [`Asset`] in the witness with the given `vault_key`. - pub fn find(&self, vault_key: VaultKey) -> Option { + pub fn find(&self, vault_key: AssetVaultKey) -> Option { self.assets().find(|asset| asset.vault_key() == vault_key) } @@ -121,7 +121,7 @@ mod tests { let err = AssetWitness::new(proof).unwrap_err(); - assert_matches!(err, AssetError::VaultKeyMismatch { actual, expected } => { + assert_matches!(err, AssetError::AssetVaultKeyMismatch { actual, expected } => { assert_eq!(actual, fungible_asset.vault_key().into()); assert_eq!(expected, non_fungible_asset.vault_key().into()); }); diff --git a/crates/miden-objects/src/asset/vault/mod.rs b/crates/miden-objects/src/asset/vault/mod.rs index 553fddf8bc..88b57e0dcf 100644 --- a/crates/miden-objects/src/asset/vault/mod.rs +++ b/crates/miden-objects/src/asset/vault/mod.rs @@ -25,7 +25,7 @@ mod asset_witness; pub use asset_witness::AssetWitness; mod vault_key; -pub use vault_key::VaultKey; +pub use vault_key::AssetVaultKey; // ASSET VAULT // ================================================================================================ @@ -95,7 +95,7 @@ impl AssetVault { // if the tree value is [0, 0, 0, 0], the asset is not stored in the vault match self.asset_tree.get_value( - &VaultKey::from_account_id(faucet_id) + &AssetVaultKey::from_account_id(faucet_id) .expect("faucet ID should be of type fungible") .into(), ) { @@ -118,7 +118,7 @@ impl AssetVault { /// Returns an opening of the leaf associated with `vault_key`. /// /// The `vault_key` can be obtained with [`Asset::vault_key`]. - pub fn open(&self, vault_key: VaultKey) -> AssetWitness { + pub fn open(&self, vault_key: AssetVaultKey) -> AssetWitness { let smt_proof = self.asset_tree.open(&vault_key.into()); // SAFETY: The asset vault should only contain valid assets. AssetWitness::new_unchecked(smt_proof) diff --git a/crates/miden-objects/src/asset/vault/partial.rs b/crates/miden-objects/src/asset/vault/partial.rs index b62ad53a97..ee792f9443 100644 --- a/crates/miden-objects/src/asset/vault/partial.rs +++ b/crates/miden-objects/src/asset/vault/partial.rs @@ -2,7 +2,7 @@ use alloc::string::ToString; use miden_crypto::merkle::{InnerNodeInfo, MerkleError, PartialSmt, SmtLeaf, SmtProof}; -use super::{AssetVault, VaultKey}; +use super::{AssetVault, AssetVaultKey}; use crate::Word; use crate::asset::{Asset, AssetWitness}; use crate::errors::PartialAssetVaultError; @@ -60,7 +60,7 @@ impl PartialVault { // vault. This is the most minimal and correct partial vault we can build. // TODO: Workaround for https://github.com/0xMiden/miden-base/issues/1966. Fix when implemented. partial_vault - .add(vault.open(VaultKey::new_unchecked(Word::empty()))) + .add(vault.open(AssetVaultKey::new_unchecked(Word::empty()))) .expect("adding the first proof should never fail"); partial_vault @@ -97,7 +97,7 @@ impl PartialVault { /// /// Returns an error if: /// - the key is not tracked by this partial vault. - pub fn open(&self, vault_key: VaultKey) -> Result { + pub fn open(&self, vault_key: AssetVaultKey) -> Result { let smt_proof = self .partial_smt .open(&vault_key.into()) @@ -114,7 +114,7 @@ impl PartialVault { /// /// Returns an error if: /// - the key is not tracked by this partial SMT. - pub fn get(&self, vault_key: VaultKey) -> Result, MerkleError> { + pub fn get(&self, vault_key: AssetVaultKey) -> Result, MerkleError> { self.partial_smt.get_value(&vault_key.into()).map(|word| { if word.is_empty() { None @@ -159,7 +159,7 @@ impl PartialVault { })?; if *vault_key != asset.vault_key().into() { - return Err(PartialAssetVaultError::VaultKeyMismatch { + return Err(PartialAssetVaultError::AssetVaultKeyMismatch { expected: asset.vault_key(), actual: *vault_key, }); @@ -220,7 +220,7 @@ mod tests { let partial_smt = PartialSmt::from_proofs([proof.clone()])?; let err = PartialVault::new(partial_smt).unwrap_err(); - assert_matches!(err, PartialAssetVaultError::VaultKeyMismatch { expected, actual } => { + assert_matches!(err, PartialAssetVaultError::AssetVaultKeyMismatch { expected, actual } => { assert_eq!(actual, invalid_vault_key); assert_eq!(expected, asset.vault_key()); }); diff --git a/crates/miden-objects/src/asset/vault/vault_key.rs b/crates/miden-objects/src/asset/vault/vault_key.rs index 5f26f59df6..c68cf7be10 100644 --- a/crates/miden-objects/src/asset/vault/vault_key.rs +++ b/crates/miden-objects/src/asset/vault/vault_key.rs @@ -30,10 +30,10 @@ use crate::asset::{Asset, FungibleAsset, NonFungibleAsset}; /// /// The fungible bit is the bit in the [`AccountId`] that encodes whether the ID is a faucet. #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] -pub struct VaultKey(Word); +pub struct AssetVaultKey(Word); -impl VaultKey { - /// Creates a new [`VaultKey`] from the given [`Word`] **without performing validation**. +impl AssetVaultKey { + /// Creates a new [`AssetVaultKey`] from the given [`Word`] **without performing validation**. /// /// ## Warning /// @@ -76,7 +76,7 @@ impl VaultKey { let mut key = Word::empty(); key[2] = faucet_id.suffix(); key[3] = faucet_id.prefix().as_felt(); - Some(VaultKey::new_unchecked(key)) + Some(AssetVaultKey::new_unchecked(key)) }, _ => None, } @@ -88,7 +88,7 @@ impl VaultKey { } } -impl fmt::Display for VaultKey { +impl fmt::Display for AssetVaultKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } @@ -97,25 +97,25 @@ impl fmt::Display for VaultKey { // CONVERSIONS // ================================================================================================ -impl From for Word { - fn from(vault_key: VaultKey) -> Self { +impl From for Word { + fn from(vault_key: AssetVaultKey) -> Self { vault_key.0 } } -impl From for VaultKey { +impl From for AssetVaultKey { fn from(asset: Asset) -> Self { asset.vault_key() } } -impl From for VaultKey { +impl From for AssetVaultKey { fn from(fungible_asset: FungibleAsset) -> Self { fungible_asset.vault_key() } } -impl From for VaultKey { +impl From for AssetVaultKey { fn from(non_fungible_asset: NonFungibleAsset) -> Self { non_fungible_asset.vault_key() } @@ -131,9 +131,9 @@ mod tests { use super::*; use crate::account::{AccountIdVersion, AccountStorageMode, AccountType}; - fn make_non_fungible_key(prefix: u64) -> VaultKey { + fn make_non_fungible_key(prefix: u64) -> AssetVaultKey { let word = [Felt::new(prefix), Felt::new(11), Felt::new(22), Felt::new(33)].into(); - VaultKey::new_unchecked(word) + AssetVaultKey::new_unchecked(word) } #[test] @@ -145,7 +145,8 @@ mod tests { AccountStorageMode::Public, ); - let key = VaultKey::from_account_id(id).expect("Expected VaultKey for FungibleFaucet"); + let key = + AssetVaultKey::from_account_id(id).expect("Expected AssetVaultKey for FungibleFaucet"); // faucet_id_prefix() should match AccountId prefix assert_eq!(key.faucet_id_prefix(), id.prefix()); diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index e46cd46bfd..362a3c19ec 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -28,7 +28,7 @@ use crate::account::{ TemplateTypeError, }; use crate::address::AddressType; -use crate::asset::VaultKey; +use crate::asset::AssetVaultKey; use crate::batch::BatchId; use crate::block::BlockNumber; use crate::note::{NoteAssets, NoteExecutionHint, NoteTag, NoteType, Nullifier}; @@ -435,8 +435,8 @@ pub enum AssetError { expected_ty = AccountType::NonFungibleFaucet )] NonFungibleFaucetIdTypeMismatch(AccountIdPrefix), - #[error("vault key {actual} does not match expected vault key {expected}")] - VaultKeyMismatch { actual: Word, expected: Word }, + #[error("asset vault key {actual} does not match expected asset vault key {expected}")] + AssetVaultKeyMismatch { actual: Word, expected: Word }, } // TOKEN SYMBOL ERROR @@ -485,7 +485,7 @@ pub enum PartialAssetVaultError { #[error("provided SMT entry {entry} is not a valid asset")] InvalidAssetInSmt { entry: Word, source: AssetError }, #[error("expected asset vault key to be {expected} but it was {actual}")] - VaultKeyMismatch { expected: VaultKey, actual: Word }, + AssetVaultKeyMismatch { expected: AssetVaultKey, actual: Word }, #[error("failed to add asset proof")] FailedToAddProof(#[source] MerkleError), #[error("asset is not tracked in the partial vault")] diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index 9fc1d0448f..c2d5a973f3 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -7,7 +7,7 @@ use miden_lib::transaction::TransactionKernel; use miden_objects::account::{Account, AccountId, PartialAccount, StorageMapWitness, StorageSlot}; use miden_objects::assembly::debuginfo::{SourceLanguage, Uri}; use miden_objects::assembly::{SourceManager, SourceManagerSync}; -use miden_objects::asset::{AssetWitness, VaultKey}; +use miden_objects::asset::{AssetVaultKey, AssetWitness}; use miden_objects::block::{AccountWitness, BlockHeader, BlockNumber}; use miden_objects::note::{Note, NoteScript}; use miden_objects::transaction::{ @@ -230,7 +230,7 @@ impl DataStore for TransactionContext { &self, account_id: AccountId, vault_root: Word, - asset_key: VaultKey, + asset_key: AssetVaultKey, ) -> impl FutureMaybeSend> { async move { if account_id == self.account().id() { diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index e959ec09ba..00272bf6a6 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -6,7 +6,7 @@ use core::error::Error; use miden_lib::transaction::TransactionAdviceMapMismatch; use miden_objects::account::AccountId; use miden_objects::assembly::diagnostics::reporting::PrintDiagnostic; -use miden_objects::asset::VaultKey; +use miden_objects::asset::AssetVaultKey; use miden_objects::block::BlockNumber; use miden_objects::crypto::merkle::SmtProofError; use miden_objects::note::{NoteId, NoteMetadata}; @@ -292,7 +292,7 @@ pub enum TransactionKernelError { )] GetVaultAssetWitness { vault_root: Word, - asset_key: VaultKey, + asset_key: AssetVaultKey, // thiserror will return this when calling Error::source on TransactionKernelError. source: DataStoreError, }, diff --git a/crates/miden-tx/src/executor/data_store.rs b/crates/miden-tx/src/executor/data_store.rs index cee0887bad..c9b18f08f5 100644 --- a/crates/miden-tx/src/executor/data_store.rs +++ b/crates/miden-tx/src/executor/data_store.rs @@ -1,7 +1,7 @@ use alloc::collections::BTreeSet; use miden_objects::account::{AccountId, PartialAccount, StorageMapWitness}; -use miden_objects::asset::{AssetWitness, VaultKey}; +use miden_objects::asset::{AssetVaultKey, AssetWitness}; use miden_objects::block::{BlockHeader, BlockNumber}; use miden_objects::note::NoteScript; use miden_objects::transaction::{AccountInputs, PartialBlockchain}; @@ -51,7 +51,7 @@ pub trait DataStore: MastForestStore { &self, account_id: AccountId, vault_root: Word, - vault_key: VaultKey, + vault_key: AssetVaultKey, ) -> impl FutureMaybeSend>; /// Returns a witness for a storage map item identified by `map_key` in the requested account's diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 113d9ea9ea..f87b929095 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -12,7 +12,7 @@ use miden_objects::account::{ }; use miden_objects::assembly::debuginfo::Location; use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; -use miden_objects::asset::{Asset, AssetWitness, FungibleAsset, VaultKey}; +use miden_objects::asset::{Asset, AssetVaultKey, AssetWitness, FungibleAsset}; use miden_objects::block::BlockNumber; use miden_objects::crypto::merkle::SmtProof; use miden_objects::note::{NoteInputs, NoteMetadata, NoteRecipient}; @@ -348,7 +348,7 @@ where &self, current_account_id: AccountId, vault_root: Word, - asset_key: VaultKey, + asset_key: AssetVaultKey, ) -> Result, TransactionKernelError> { let asset_witness = self .base_host diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 5dda66ee19..f25e78c252 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -37,7 +37,7 @@ use miden_objects::account::{ StorageMap, StorageSlotType, }; -use miden_objects::asset::{Asset, AssetVault, FungibleAsset, VaultKey}; +use miden_objects::asset::{Asset, AssetVault, AssetVaultKey, FungibleAsset}; use miden_objects::note::{NoteId, NoteInputs, NoteMetadata, NoteRecipient, NoteScript}; use miden_objects::transaction::{ InputNote, @@ -907,7 +907,7 @@ where let vault_root_ptr = stack_top[1]; let vault_root = process.get_vault_root(vault_root_ptr)?; - let vault_key = VaultKey::from_account_id(faucet_id).ok_or_else(|| { + let vault_key = AssetVaultKey::from_account_id(faucet_id).ok_or_else(|| { TransactionKernelError::other(format!( "provided faucet ID {faucet_id} is not valid for fungible assets" )) @@ -939,7 +939,7 @@ where fn on_account_vault_asset_accessed( &self, process: &ProcessState, - vault_key: VaultKey, + vault_key: AssetVaultKey, current_vault_root: Word, ) -> Result { let leaf_index = Felt::new(vault_key.to_leaf_index().value()); @@ -1092,7 +1092,7 @@ pub(super) enum TransactionEventData { /// The vault root identifying the asset vault from which a witness is requested. vault_root: Word, /// The asset for which a witness is requested. - asset_key: VaultKey, + asset_key: AssetVaultKey, }, /// The data necessary to request a storage map witness from the data store. AccountStorageMapWitness { From 10b023686d8d09e0a1c1978af72b62387eea4b92 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Mon, 3 Nov 2025 21:06:09 +0300 Subject: [PATCH 119/133] Rename `current account` to `active account` (#2030) * refactor: split miden::account * chore: update changelog * docs: update protocol library docs * chore: add new lines at the end of the new files * refactor: remove native_account::get_nonce, update changelog * refactor: synchronize procs order in active_account.masm and protocol library docs * chore: move vault root getters to the commitments section * refactor: use active instead of current, rearrange procs in kernel::account * refactor: make account_get_id handle both native and active accounts * chore: run tx benchmarks * refactor: udpate doc comments, udpate error message * docs: update doc comments and protocol library docs --- bin/bench-transaction/bench-tx.json | 42 +- .../asm/kernels/transaction/api.masm | 124 +-- .../asm/kernels/transaction/lib/account.masm | 729 +++++++++--------- .../asm/kernels/transaction/lib/epilogue.masm | 4 +- .../asm/kernels/transaction/lib/memory.masm | 96 ++- .../asm/kernels/transaction/lib/prologue.masm | 6 +- .../miden-lib/asm/miden/active_account.masm | 51 +- .../asm/miden/contracts/wallets/basic.masm | 2 +- crates/miden-lib/asm/miden/faucet.masm | 4 +- .../asm/miden/kernel_proc_offsets.masm | 123 ++- .../miden-lib/asm/miden/native_account.masm | 15 +- crates/miden-lib/asm/miden/tx.masm | 2 +- crates/miden-lib/asm/note_scripts/P2IDE.masm | 12 +- .../miden-lib/src/errors/tx_kernel_errors.rs | 8 +- .../src/transaction/kernel_procedures.rs | 8 +- crates/miden-lib/src/transaction/memory.rs | 2 +- crates/miden-objects/src/errors.rs | 2 +- .../src/kernel_tests/tx/test_account.rs | 4 +- .../miden-tx/src/host/account_procedures.rs | 6 +- docs/src/protocol_library.md | 14 +- 20 files changed, 636 insertions(+), 618 deletions(-) diff --git a/bin/bench-transaction/bench-tx.json b/bin/bench-transaction/bench-tx.json index 77adb4a920..dddf80ff3f 100644 --- a/bin/bench-transaction/bench-tx.json +++ b/bin/bench-transaction/bench-tx.json @@ -1,40 +1,40 @@ { "consume single P2ID note": { - "prologue": 3066, - "notes_processing": 1693, + "prologue": 3182, + "notes_processing": 1736, "note_execution": { - "0xf5b0275c217e79a596a2aa106ec86cc442fa6229f30ccc4a173a90c4d14aad0e": 1659 + "0xa988111a1c72fba94d4d1d5a5c301440d524c5edd6446babc6e09c51da0c92af": 1698 }, - "tx_script_processing": 40, + "tx_script_processing": 42, "epilogue": { - "total": 62513, - "auth_procedure": 61352, - "after_tx_cycles_obtained": 500 + "total": 63605, + "auth_procedure": 62403, + "after_tx_cycles_obtained": 517 } }, "consume two P2ID notes": { - "prologue": 3735, - "notes_processing": 3394, + "prologue": 3779, + "notes_processing": 3478, "note_execution": { - "0x147b8b1a9fb4255b1272ef71b5d51b7c3eb423e586873f39743689c7a720ecd0": 1692, - "0xd921698727a028c992fd6192f23b175b95a24859c7de645466e4d3c757b1c08a": 1659 + "0xd4a5cbccaeb197a18b8377d46c9737ed10c7049b619c1441f0414cb7c3f06af9": 1732, + "0xedc22bcd96965d66d045b2b78ecc3116bebc688619801d3ba2749484eb5eac3e": 1698 }, - "tx_script_processing": 40, + "tx_script_processing": 42, "epilogue": { - "total": 62513, - "auth_procedure": 61352, - "after_tx_cycles_obtained": 500 + "total": 63585, + "auth_procedure": 62393, + "after_tx_cycles_obtained": 517 } }, "create single P2ID note": { - "prologue": 1558, - "notes_processing": 26, + "prologue": 1575, + "notes_processing": 29, "note_execution": {}, - "tx_script_processing": 1484, + "tx_script_processing": 1516, "epilogue": { - "total": 63227, - "auth_procedure": 61533, - "after_tx_cycles_obtained": 500 + "total": 64309, + "auth_procedure": 62574, + "after_tx_cycles_obtained": 517 } } } \ No newline at end of file diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 728b06c800..627c0110aa 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -86,9 +86,10 @@ end # KERNEL PROCEDURES # ================================================================================================= -### ACCOUNT ##################################### +# ACCOUNT +# ------------------------------------------------------------------------------------------------- -#! Returns the native account commitment at the beginning of the transaction. +#! Returns the active account commitment at the beginning of the transaction. #! #! Inputs: [pad(16)] #! Outputs: [INIT_COMMITMENT, pad(12)] @@ -107,7 +108,7 @@ export.account_get_initial_commitment # => [INIT_COMMITMENT, pad(12)] end -#! Computes the account commitment of the current account. +#! Computes commitment to the state of the active account. #! #! Inputs: [pad(16)] #! Outputs: [ACCOUNT_COMMITMENT, pad(12)] @@ -116,9 +117,9 @@ end #! - ACCOUNT_COMMITMENT is the commitment of the account data. #! #! Invocation: dynexec -export.account_compute_current_commitment - # compute the current account commitment - exec.account::compute_current_commitment +export.account_compute_commitment + # compute the active account commitment + exec.account::compute_commitment # => [ACCOUNT_COMMITMENT, pad(16)] # truncate the stack @@ -157,53 +158,61 @@ export.account_compute_delta_commitment # => [ACCOUNT_COMMITMENT, pad(12)] end -#! Returns the ID of the current account. +#! Returns the ID of the specified account. #! -#! Inputs: [pad(16)] +#! Inputs: [is_native, pad(15)] #! Outputs: [account_id_prefix, account_id_suffix, pad(14)] #! #! Where: -#! - account_id_{prefix,suffix} are the prefix and suffix felts of the account ID of the currently -#! accessing account. +#! - is_native is a boolean flag that indicates whether the account ID was requested for the native +#! or the active account. +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the active account. #! #! Invocation: dynexec export.account_get_id - # get the current account ID + # get the native account ID + exec.memory::get_native_account_id + # => [native_account_id_prefix, native_account_id_suffix, is_native, pad(15)] + + # get the active account ID exec.account::get_id - # => [account_id_prefix, account_id_suffix, pad(16)] + # => [ + # active_account_id_prefix, active_account_id_suffix, + # native_account_id_prefix, native_account_id_suffix, + # is_native, pad(15) + # ] + + # prepare the stack for the first cdrop + movup.2 dup.4 + # => [ + # is_native, native_account_id_prefix, active_account_id_prefix, + # active_account_id_suffix, native_account_id_suffix, is_native, pad(15) + # ] + + # drop the prefix corresponding to the is_native flag + cdrop + # => [account_id_prefix, active_account_id_suffix, native_account_id_suffix, is_native, pad(15)] - # truncate the stack - movup.2 drop movup.2 drop - # => [account_id_prefix, account_id_suffix, pad(14)] -end + # prepare the stack for the second cdrop + movdn.3 swap movup.2 + # => [is_native, native_account_id_suffix, active_account_id_suffix, account_id_prefix, pad(15)] -#! Returns the native account ID. -#! -#! Inputs: [pad(16)] -#! Outputs: [account_id_prefix, account_id_suffix, pad(14)] -#! -#! Where: -#! - account_id_{prefix,suffix} are the prefix and suffix felts of the native account ID of the -#! transaction. -#! -#! Invocation: dynexec -export.account_get_native_id - # get the native account ID - exec.memory::get_native_account_id - # => [account_id_prefix, account_id_suffix, pad(16)] + # drop the suffix corresponding to the is_native flag + cdrop + # => [account_id_suffix, account_id_prefix, pad(15)] - # truncate the stack - movup.2 drop movup.2 drop + # rearrange the ID parts and truncate the stack + swap movup.2 drop # => [account_id_prefix, account_id_suffix, pad(14)] end -#! Returns the current account nonce. +#! Returns the active account nonce. #! #! Inputs: [pad(16)] #! Outputs: [nonce, pad(15)] #! #! Where: -#! - nonce is the current account's nonce. +#! - nonce is the active account's nonce. #! #! Invocation: dynexec export.account_get_nonce @@ -250,7 +259,7 @@ export.account_incr_nonce # => [final_nonce, pad(15)] end -#! Gets the account code commitment of the current account. +#! Gets the account code commitment of the active account. #! #! Inputs: [pad(16)] #! Outputs: [CODE_COMMITMENT, pad(12)] @@ -273,7 +282,7 @@ export.account_get_code_commitment # => [CODE_COMMITMENT, pad(12)] end -#! Returns the storage commitment of the native account at the beginning of the transaction. +#! Returns the storage commitment of the active account at the beginning of the transaction. #! #! Inputs: [pad(16)] #! Outputs: [INIT_STORAGE_COMMITMENT, pad(12)] @@ -292,7 +301,7 @@ export.account_get_initial_storage_commitment # => [INIT_STORAGE_COMMITMENT, pad(12)] end -#! Computes the latest account storage commitment of the current account. +#! Computes the latest account storage commitment of the active account. #! #! Inputs: [pad(16)] #! Outputs: [STORAGE_COMMITMENT, pad(12)] @@ -513,7 +522,7 @@ export.account_set_map_item # => [OLD_MAP_ROOT, OLD_VALUE, pad(8)] end -#! Returns the vault root of the native account at the beginning of the transaction. +#! Returns the vault root of the active account at the beginning of the transaction. #! #! Inputs: [pad(16)] #! Outputs: [INIT_VAULT_ROOT, pad(12)] @@ -614,7 +623,7 @@ export.account_remove_asset # => [ASSET, pad(12)] end -#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! Returns the balance of the fungible asset associated with the provided faucet_id in the active #! account's vault. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix, pad(14)] @@ -634,7 +643,7 @@ export.account_get_balance # => [balance, pad(15)] end -#! Returns the balance of the fungible asset associated with the provided faucet_id in the current +#! Returns the balance of the fungible asset associated with the provided faucet_id in the active #! account's vault at the beginning of the transaction. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix, pad(14)] @@ -654,7 +663,8 @@ export.account_get_initial_balance # => [init_balance, pad(15)] end -#! Returns a boolean indicating whether the non-fungible asset is present in the current account's vault. +#! Returns a boolean indicating whether the non-fungible asset is present in the active account's +#! vault. #! #! Inputs: [ASSET, pad(12)] #! Outputs: [has_asset, pad(15)] @@ -672,7 +682,7 @@ export.account_has_non_fungible_asset # => [has_asset, pad(15)] end -#! Returns 1 if a procedure was called during transaction execution, and 0 otherwise. +#! Returns 1 if a native account procedure was called during transaction execution, and 0 otherwise. #! #! Inputs: [PROC_ROOT, pad(12)] #! Outputs: [was_called, pad(15)] @@ -695,13 +705,13 @@ export.account_was_procedure_called # => [was_called, pad(15)] end -#! Returns the number of procedures in the current account. +#! Returns the number of procedures in the active account. #! #! Inputs: [pad(16)] #! Outputs: [num_procedures, pad(15)] #! #! Where: -#! - num_procedures is the number of procedures in the current account. +#! - num_procedures is the number of procedures in the active account. #! #! Invocation: dynexec export.account_get_num_procedures @@ -714,7 +724,7 @@ export.account_get_num_procedures # => [num_procedures, pad(15)] end -#! Returns the procedure root for the procedure at the specified index. +#! Returns the procedure root for the active account procedure at the specified index. #! #! Inputs: [index, pad(15)] #! Outputs: [PROC_ROOT, pad(12)] @@ -759,7 +769,8 @@ export.account_has_procedure # => [is_procedure_available, pad(15)] end -### FAUCET ###################################### +# FAUCET +# ------------------------------------------------------------------------------------------------- #! Mint an asset from the faucet the transaction is being executed against. #! @@ -888,7 +899,8 @@ export.faucet_is_non_fungible_asset_issued # => [is_issued, pad(15)] end -### INPUT NOTE ################################## +# INPUT NOTE +# ------------------------------------------------------------------------------------------------- #! Returns the assets information of the specified input note. #! @@ -1130,7 +1142,8 @@ export.input_note_get_script_root # => [SCRIPT_ROOT, pad(12)] end -### OUTPUT NOTE ################################# +# OUTPUT NOTE +# ------------------------------------------------------------------------------------------------- #! Creates a new note and returns its index. #! @@ -1165,7 +1178,7 @@ end #! - ASSET can be a fungible or non-fungible asset. #! #! Panics if: -#! - the procedure is called when the current account is not the native one. +#! - the procedure is called when the active account is not the native one. #! #! Invocation: dynexec export.output_note_add_asset @@ -1273,7 +1286,8 @@ export.output_note_get_metadata # => [METADATA, pad(12)] end -### TRANSACTION ################################# +# TRANSACTION +# ------------------------------------------------------------------------------------------------- #! Returns the input notes commitment. #! @@ -1415,7 +1429,7 @@ end #! #! This allows calling procedures on an account different from the native account. It loads the #! foreign account into memory, unless already loaded. It pushes the foreign account onto the -#! account stack, which makes the foreign account the current account. +#! account stack, which makes the foreign account the active account. #! #! Inputs: #! Operand stack: [foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] @@ -1458,25 +1472,25 @@ export.tx_start_foreign_context exec.memory::push_ptr_to_account_stack # OS => [foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] - # load the advice data into the current account memory section + # load the advice data into the active account memory section exec.account::load_foreign_account end # make sure that the state of the loaded foreign account corresponds to this commitment in the # account database - exec.account::validate_current_foreign_account + exec.account::validate_active_foreign_account # => [pad(16)] end #! Ends a foreign account context. #! -#! This pops the top of the account stack, making the previous account the current account. +#! This pops the top of the account stack, making the previous account the active account. #! #! Inputs: [pad(16)] #! Outputs: [pad(16)] #! #! Panics if: -#! - the current account is the native account. +#! - the active account is the native account. #! #! Invocation: dynexec export.tx_end_foreign_context diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index b7645c6164..280111be82 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -205,45 +205,30 @@ end # PROCEDURES # ================================================================================================= -#! Computes the commitment of the active account. -#! -#! Notice that there is no caching (and, hence, dirty flag) for the commitment of the entire -#! account. Assuming that the storage commitment is current, computing account commitment is -#! relatively cheap — essentially is consists of just 2 permutations of the hash function and takes -#! relatively small number of cycles, so it would not be worth adding a separate caching mechanism -#! for this. +# ID AND NONCE +# ------------------------------------------------------------------------------------------------- + +#! Returns the id of the active account. #! #! Inputs: [] -#! Outputs: [ACCOUNT_COMMITMENT] +#! Outputs: [act_acct_id_prefix, act_acct_id_suffix] #! #! Where: -#! - ACCOUNT_COMMITMENT is the commitment of the account data. -export.compute_current_commitment - # if outdated, recompute the storage commitment and store it in the memory - exec.refresh_storage_commitment - # => [] - - # prepare the stack for computing the account commitment - exec.memory::get_current_account_data_ptr padw padw padw - # => [RATE, RATE, PERM, account_data_ptr] - - # stream account data and compute sequential hash. We perform two `mem_stream` operations - # because the account data consists of exactly 4 words. - mem_stream hperm mem_stream hperm - # => [RATE, RATE, PERM, account_data_ptr'] - - # extract account commitment - exec.rpo::squeeze_digest - # => [ACCOUNT_COMMITMENT, account_data_ptr'] +#! - act_acct_id_{prefix,suffix} are the prefix and suffix felts of the ID of the active account. +export.memory::get_account_id->get_id - # drop account_data_ptr - movup.4 drop - # => [ACCOUNT_COMMITMENT] -end +#! Returns the nonce of the active account. +#! +#! Inputs: [] +#! Outputs: [nonce] +#! +#! Where: +#! - nonce is the account nonce. +export.memory::get_account_nonce->get_nonce #! Increments the account nonce by one and returns the new nonce. #! -#! Assumes that it is executed only when the current account is the native account. +#! Assumes that it is executed only when the active account is the native account. #! #! Inputs: [] #! Outputs: [new_nonce] @@ -279,24 +264,10 @@ export.incr_nonce # => [new_nonce] end -#! Returns the id of the current account. -#! -#! Inputs: [] -#! Outputs: [curr_acct_id_prefix, curr_acct_id_suffix] -#! -#! Where: -#! - curr_acct_id_{prefix,suffix} are the prefix and suffix felts of the account ID of the currently -#! accessing account. -export.memory::get_account_id->get_id +# COMMITMENTS +# ------------------------------------------------------------------------------------------------- -#! Returns the account nonce. -#! -#! Inputs: [] -#! Outputs: [nonce] -#! -#! Where: -#! - nonce is the account nonce. -export.memory::get_account_nonce->get_nonce +### ACCOUNT COMMITMENT ################################################### #! Returns the commitment of the active account at the beginning of the transaction. #! @@ -314,38 +285,60 @@ export.get_initial_commitment exec.memory::get_init_account_commitment else # for the foreign account current commitment equals to initial - exec.compute_current_commitment + exec.compute_commitment end # => [INIT_COMMITMENT] end -#! Returns the vault root of the active account at the beginning of the transaction. +#! Computes commitment to the state of the active account. +#! +#! Notice that there is no caching (and, hence, dirty flag) for the commitment of the entire +#! account. If the storage commitment is up-to-date (which is ensured by this procedure), computing +#! account commitment is relatively cheap — essentially is consists of just 2 permutations of the +#! hash function and takes relatively small number of cycles, so it would not be worth adding a +#! separate caching mechanism for this. #! #! Inputs: [] -#! Outputs: [INIT_ACCOUNT_VAULT_ROOT] +#! Outputs: [ACCOUNT_COMMITMENT] #! #! Where: -#! - INIT_ACCOUNT_VAULT_ROOT is the initial account vault root. -export.get_initial_vault_root - # Get the vault root of the active account. For the foreign account this root will be equal to - # the initial one. - exec.memory::get_account_vault_root - # => [ACTIVE_ACCOUNT_VAULT_ROOT] +#! - ACCOUNT_COMMITMENT is the commitment of the account data. +export.compute_commitment + # if outdated, recompute the storage commitment and store it in the memory + exec.refresh_storage_commitment + # => [] - # get the initial vault root of the native account - exec.memory::get_init_native_account_vault_root - # => [INIT_NATIVE_ACCOUNT_VAULT_ROOT, ACTIVE_ACCOUNT_VAULT_ROOT] + # prepare the stack for computing the account commitment + exec.memory::get_active_account_data_ptr padw padw padw + # => [RATE, RATE, PERM, account_data_ptr] - # check whether the active account is native - exec.memory::is_native_account - # => [is_native_account, INIT_NATIVE_ACCOUNT_VAULT_ROOT, ACTIVE_ACCOUNT_VAULT_ROOT] + # stream account data and compute sequential hash. We perform two `mem_stream` operations + # because the account data consists of exactly 4 words. + mem_stream hperm mem_stream hperm + # => [RATE, RATE, PERM, account_data_ptr'] - # if the active account is native, keep the INIT_NATIVE_ACCOUNT_VAULT_ROOT, or - # ACTIVE_ACCOUNT_VAULT_ROOT otherwise - cdropw - # => [INIT_ACCOUNT_VAULT_ROOT] + # extract account commitment + exec.rpo::squeeze_digest + # => [ACCOUNT_COMMITMENT, account_data_ptr'] + + # drop account_data_ptr + movup.4 drop + # => [ACCOUNT_COMMITMENT] end +### CODE COMMITMENT ###################################################### + +#! Gets the code commitment of the active account. +#! +#! Inputs: [] +#! Outputs: [CODE_COMMITMENT] +#! +#! Where: +#! - CODE_COMMITMENT is the commitment of the account code. +export.memory::get_account_code_commitment->get_code_commitment + +### STORAGE COMMITMENT ################################################### + #! Returns the storage commitment of the active account at the beginning of the transaction. #! #! Inputs: [] @@ -373,22 +366,13 @@ export.get_initial_storage_commitment # => [INIT_ACCOUNT_STORAGE_COMMITMENT] end -#! Gets the code commitment of the current account. -#! -#! Inputs: [] -#! Outputs: [CODE_COMMITMENT] -#! -#! Where: -#! - CODE_COMMITMENT is the commitment of the account code. -export.memory::get_account_code_commitment->get_code_commitment - -#! Computes the storage commitment of the current account. +#! Computes the storage commitment of the active account. #! #! Inputs: [] #! Outputs: [STORAGE_COMMITMENT] #! #! Where: -#! - STORAGE_COMMITMENT is the current commitment of the account storage. +#! - STORAGE_COMMITMENT is the commitment of the active account storage. export.compute_storage_commitment # if outdated, recompute the storage commitment and store it in the memory exec.refresh_storage_commitment @@ -398,113 +382,38 @@ export.compute_storage_commitment # => [STORAGE_COMMITMENT] end -#! Applies storage offset to provided storage slot index for storage access. -#! -#! Inputs: [storage_offset, storage_size, slot_index] -#! Outputs: [offset_slot_index] -#! -#! Where: -#! - storage_offset is the offset of the storage for this account component. -#! - storage_size is the number of storage slots accessible from this account component. -#! - slot_index is the index of the storage slot to be accessed. -#! - offset_slot_index is the final index of the storage slot with the storage offset applied to it. -#! -#! Panics if: -#! - the computed index is out of bounds -export.apply_storage_offset - # offset index - dup movup.3 add - # => [offset_slot_index, storage_offset, storage_size] - - # verify that slot_index is in bounds - movdn.2 add dup.1 gt assert.err=ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS - # => [offset_slot_index] -end +### VAULT ROOT ########################################################### -#! Validates all account procedure's storage metadata. +#! Returns the vault root of the active account at the beginning of the transaction. #! #! Inputs: [] -#! Outputs: [] +#! Outputs: [INIT_ACCOUNT_VAULT_ROOT] #! -#! Panics if: -#! - Storage offset + storage size > number of storage slots. -#! - Storage size is zero and storage offset is non-zero. -#! - This is validated to ensure users do not accidentally set a non-zero offset with a -#! zero size which would prevent any access to storage. -#! - The storage offset of a faucet account's procedure is 0 with a size != 0. -#! - This prevents access to the reserved storage slot. -export.validate_procedure_metadata - # get number of account procedures and number of storage slots - exec.memory::get_num_account_procedures exec.memory::get_num_storage_slots - # => [num_storage_slots, num_account_procedures] - - # prepare stack for looping - push.0.1 - # => [start_loop, index, num_storage_slots, num_account_procedures] - - # check if the account is a faucet - exec.get_id swap drop exec.account_id::is_faucet - # => [is_faucet, start_loop, index, num_storage_slots, num_account_procedures] - - # we do not check if num_account_procedures == 0 here because a valid - # account has between 1 and 256 procedures with associated offsets - if.true - # This branch handles procedures from faucet accounts. - while.true - # get storage offset and size from memory - dup exec.get_procedure_metadata - # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] - - # Procedures that do not access storage are defined with (offset, size) = (0, 0). - # But we want to fail on tuples defined with a zero size but non-zero offset, since that - # is a logic error. - # We assert this with: (size == 0 && offset != 0) == 0. - dup.1 eq.0 dup.1 eq.0 not and assertz.err=ERR_ACCOUNT_INVALID_STORAGE_OFFSET_FOR_SIZE - # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] - - # No procedure should access the reserved faucet slot (slot 0). However (0, 0) should - # still be allowed per the above. - # We assert this with: (offset == 0 && size != 0) == 0. - dup.1 eq.0 not dup.1 eq.0 and assertz.err=ERR_FAUCET_INVALID_STORAGE_OFFSET - # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] - - # assert that storage limit is in bounds - add dup.2 lte assert.err=ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS - # => [index, num_storage_slots, num_account_procedures] - - # check if we should continue looping - add.1 dup dup.3 lt - # => [should_loop, index, num_storage_slots, num_account_procedures] - end - else - # This branch handles procedures from regular accounts. - while.true - # get storage offset and size from memory - dup exec.get_procedure_metadata - # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] - - # Procedures that do not access storage are defined with (offset, size) = (0, 0). - # But we want to fail on tuples defined with a zero size but non-zero offset, since that - # is a logic error. - # We assert this with: (size == 0 && offset != 0) == 0. - dup.1 eq.0 dup.1 eq.0 not and assertz.err=ERR_ACCOUNT_INVALID_STORAGE_OFFSET_FOR_SIZE - # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] +#! Where: +#! - INIT_ACCOUNT_VAULT_ROOT is the initial account vault root. +export.get_initial_vault_root + # Get the vault root of the active account. For the foreign account this root will be equal to + # the initial one. + exec.memory::get_account_vault_root + # => [ACTIVE_ACCOUNT_VAULT_ROOT] - # assert that storage limit is in bounds - add dup.2 lte assert.err=ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS - # => [index, num_storage_slots, num_account_procedures] + # get the initial vault root of the native account + exec.memory::get_init_native_account_vault_root + # => [INIT_NATIVE_ACCOUNT_VAULT_ROOT, ACTIVE_ACCOUNT_VAULT_ROOT] - # check if we should continue looping - add.1 dup dup.3 lt - # => [should_loop, index, num_storage_slots, num_account_procedures] - end - end + # check whether the active account is native + exec.memory::is_native_account + # => [is_native_account, INIT_NATIVE_ACCOUNT_VAULT_ROOT, ACTIVE_ACCOUNT_VAULT_ROOT] - # clean stack - drop drop drop - # => [] + # if the active account is native, keep the INIT_NATIVE_ACCOUNT_VAULT_ROOT, or + # ACTIVE_ACCOUNT_VAULT_ROOT otherwise + cdropw + # => [INIT_ACCOUNT_VAULT_ROOT] end +# STORAGE +# ------------------------------------------------------------------------------------------------- + #! Gets an item from the account storage. #! #! Note: @@ -728,6 +637,29 @@ export.set_map_item.12 # => [OLD_MAP_ROOT, OLD_MAP_VALUE, ...] end +#! Applies storage offset to provided storage slot index for storage access. +#! +#! Inputs: [storage_offset, storage_size, slot_index] +#! Outputs: [offset_slot_index] +#! +#! Where: +#! - storage_offset is the offset of the storage for this account component. +#! - storage_size is the number of storage slots accessible from this account component. +#! - slot_index is the index of the storage slot to be accessed. +#! - offset_slot_index is the final index of the storage slot with the storage offset applied to it. +#! +#! Panics if: +#! - the computed index is out of bounds +export.apply_storage_offset + # offset index + dup movup.3 add + # => [offset_slot_index, storage_offset, storage_size] + + # verify that slot_index is in bounds + movdn.2 add dup.1 gt assert.err=ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS + # => [offset_slot_index] +end + #! Returns the type of the requested storage slot. #! #! Inputs: [index] @@ -751,15 +683,262 @@ export.get_storage_slot_type # => [slot_type] end -#! Returns the procedure information. +# VAULT +# ------------------------------------------------------------------------------------------------- + +#! Adds the specified asset to the account vault. #! -#! Inputs: [index] -#! Outputs: [PROC_ROOT, storage_offset, storage_size] +#! Inputs: [ASSET] +#! Outputs: [ASSET'] #! #! Where: -#! - PROC_ROOT is the hash of the procedure. -#! - storage_offset is the procedure storage offset. -#! - storage_size is the number of storage slots the procedure is allowed to access. +#! - ASSET is the asset that is added to the vault. +#! - ASSET' final asset in the account vault defined as follows: +#! - If ASSET is a non-fungible asset, then ASSET' is the same as ASSET. +#! - If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault +#! after ASSET was added to it. +#! +#! Panics if: +#! - the asset is not valid. +#! - the total value of the fungible asset is greater than or equal to 2^63 after the new asset was +#! added. +#! - the vault already contains the same non-fungible asset. +export.add_asset_to_vault + # duplicate the ASSET to be able to emit an event after an asset is being added + dupw + # => [ASSET, ASSET] + + # fetch the account vault root + exec.memory::get_account_vault_root_ptr movdn.4 + # => [ASSET, acct_vault_root_ptr, ASSET] + + # emit event to signal that an asset is going to be added to the account vault + emit.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT + + # add the asset to the account vault + exec.asset_vault::add_asset + # => [ASSET', ASSET] + + swapw + # => [ASSET, ASSET'] + + dupw exec.account_delta::add_asset + # => [ASSET, ASSET'] + + # emit event to signal that an asset is being added to the account vault + emit.ACCOUNT_VAULT_AFTER_ADD_ASSET_EVENT + dropw + # => [ASSET'] +end + +#! Removes the specified asset from the account vault. +#! +#! Inputs: [ASSET] +#! Outputs: [ASSET] +#! +#! Where: +#! - ASSET is the asset to remove from the vault. +#! +#! Panics if: +#! - the fungible asset is not found in the vault. +#! - the amount of the fungible asset in the vault is less than the amount to be removed. +#! - the non-fungible asset is not found in the vault. +export.remove_asset_from_vault + # fetch the vault root + exec.memory::get_account_vault_root_ptr movdn.4 + # => [ASSET, acct_vault_root_ptr] + + # emit event to signal that an asset is going to be removed from the account vault + emit.ACCOUNT_VAULT_BEFORE_REMOVE_ASSET_EVENT + + # remove the asset from the account vault + exec.asset_vault::remove_asset + # => [ASSET] + + dupw exec.account_delta::remove_asset + # => [ASSET] + + # emit event to signal that an asset is being removed from the account vault + emit.ACCOUNT_VAULT_AFTER_REMOVE_ASSET_EVENT + # => [ASSET] +end + +#! Returns the balance of the fungible asset associated with the provided faucet_id in the active +#! account's vault. +#! +#! Inputs: [faucet_id_prefix, faucet_id_suffix] +#! Outputs: [balance] +#! +#! Where: +#! - faucet_id_{prefix, suffix} are the prefix and suffix felts of the faucet id of the fungible +#! asset of interest. +#! - balance is the vault balance of the fungible asset. +#! +#! Panics if: +#! - the provided faucet ID is not an ID of a fungible faucet. +export.get_balance + # get the vault root + exec.memory::get_account_vault_root_ptr movdn.2 + # => [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] + + # emit event to signal that an asset's balance is requested + emit.ACCOUNT_VAULT_BEFORE_GET_BALANCE_EVENT + # => [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] + + # get the asset balance + exec.asset_vault::get_balance + # => [balance] +end + +#! Returns the balance of the fungible asset associated with the provided faucet_id in the active +#! account's vault at the beginning of the transaction. +#! +#! Inputs: [faucet_id_prefix, faucet_id_suffix] +#! Outputs: [init_balance] +#! +#! Where: +#! - faucet_id_{prefix, suffix} are the prefix and suffix felts of the faucet id of the fungible +#! asset of interest. +#! - init_balance is the vault balance of the fungible asset at the beginning of the transaction. +#! +#! Panics if: +#! - the provided faucet ID is not an ID of a fungible faucet. +export.get_initial_balance + # get the vault root associated with the initial vault root of the native account + exec.memory::get_account_initial_vault_root_ptr movdn.2 + # => [faucet_id_prefix, faucet_id_suffix, init_native_vault_root_ptr] + + # emit event to signal that an asset's balance is requested + emit.ACCOUNT_VAULT_BEFORE_GET_BALANCE_EVENT + # => [faucet_id_prefix, faucet_id_suffix, init_native_vault_root_ptr] + + # get the asset balance + exec.asset_vault::get_balance + # => [init_balance] +end + +#! Returns a boolean indicating whether the non-fungible asset is present in the active account's +#! vault. +#! +#! Inputs: [ASSET] +#! Outputs: [has_asset] +#! +#! Where: +#! - ASSET is the non-fungible asset of interest. +#! - has_asset is a boolean indicating whether the account vault has the asset of interest. +#! +#! Panics if: +#! - the ASSET is a fungible asset. +export.has_non_fungible_asset + # get the vault root + exec.memory::get_account_vault_root_ptr movdn.4 + # => [ASSET, vault_root_ptr] + + # emit event to signal that an asset's presence is being checked + emit.ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET_EVENT + # => [ASSET, vault_root_ptr] + + # check if the account vault has the non-fungible asset + exec.asset_vault::has_non_fungible_asset + # => [has_asset] +end + +# CODE +# ------------------------------------------------------------------------------------------------- + +#! Validates all account procedure's storage metadata. +#! +#! Inputs: [] +#! Outputs: [] +#! +#! Panics if: +#! - Storage offset + storage size > number of storage slots. +#! - Storage size is zero and storage offset is non-zero. +#! - This is validated to ensure users do not accidentally set a non-zero offset with a +#! zero size which would prevent any access to storage. +#! - The storage offset of a faucet account's procedure is 0 with a size != 0. +#! - This prevents access to the reserved storage slot. +export.validate_procedure_metadata + # get number of account procedures and number of storage slots + exec.memory::get_num_account_procedures exec.memory::get_num_storage_slots + # => [num_storage_slots, num_account_procedures] + + # prepare stack for looping + push.0.1 + # => [start_loop, index, num_storage_slots, num_account_procedures] + + # check if the account is a faucet + exec.get_id swap drop exec.account_id::is_faucet + # => [is_faucet, start_loop, index, num_storage_slots, num_account_procedures] + + # we do not check if num_account_procedures == 0 here because a valid + # account has between 1 and 256 procedures with associated offsets + if.true + # This branch handles procedures from faucet accounts. + while.true + # get storage offset and size from memory + dup exec.get_procedure_metadata + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] + + # Procedures that do not access storage are defined with (offset, size) = (0, 0). + # But we want to fail on tuples defined with a zero size but non-zero offset, since that + # is a logic error. + # We assert this with: (size == 0 && offset != 0) == 0. + dup.1 eq.0 dup.1 eq.0 not and assertz.err=ERR_ACCOUNT_INVALID_STORAGE_OFFSET_FOR_SIZE + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] + + # No procedure should access the reserved faucet slot (slot 0). However (0, 0) should + # still be allowed per the above. + # We assert this with: (offset == 0 && size != 0) == 0. + dup.1 eq.0 not dup.1 eq.0 and assertz.err=ERR_FAUCET_INVALID_STORAGE_OFFSET + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] + + # assert that storage limit is in bounds + add dup.2 lte assert.err=ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS + # => [index, num_storage_slots, num_account_procedures] + + # check if we should continue looping + add.1 dup dup.3 lt + # => [should_loop, index, num_storage_slots, num_account_procedures] + end + else + # This branch handles procedures from regular accounts. + while.true + # get storage offset and size from memory + dup exec.get_procedure_metadata + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] + + # Procedures that do not access storage are defined with (offset, size) = (0, 0). + # But we want to fail on tuples defined with a zero size but non-zero offset, since that + # is a logic error. + # We assert this with: (size == 0 && offset != 0) == 0. + dup.1 eq.0 dup.1 eq.0 not and assertz.err=ERR_ACCOUNT_INVALID_STORAGE_OFFSET_FOR_SIZE + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] + + # assert that storage limit is in bounds + add dup.2 lte assert.err=ERR_ACCOUNT_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS + # => [index, num_storage_slots, num_account_procedures] + + # check if we should continue looping + add.1 dup dup.3 lt + # => [should_loop, index, num_storage_slots, num_account_procedures] + end + end + + # clean stack + drop drop drop + # => [] +end + +#! Returns the procedure information. +#! +#! Inputs: [index] +#! Outputs: [PROC_ROOT, storage_offset, storage_size] +#! +#! Where: +#! - PROC_ROOT is the hash of the procedure. +#! - storage_offset is the procedure storage offset. +#! - storage_size is the number of storage slots the procedure is allowed to access. #! #! Panics if: #! - the procedure index is out of bounds. @@ -853,6 +1032,9 @@ export.assert_auth_procedure # => [] end +# VALIDATION +# ------------------------------------------------------------------------------------------------- + #! Validates that the account seed, provided via the advice map, satisfies the seed requirements. #! #! Validation is performed via the following steps: @@ -925,171 +1107,10 @@ export.validate_seed # => [] end -# ACCOUNT VAULT -# ================================================================================================= - -#! Adds the specified asset to the account vault. -#! -#! Inputs: [ASSET] -#! Outputs: [ASSET'] -#! -#! Where: -#! - ASSET is the asset that is added to the vault. -#! - ASSET' final asset in the account vault defined as follows: -#! - If ASSET is a non-fungible asset, then ASSET' is the same as ASSET. -#! - If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault -#! after ASSET was added to it. -#! -#! Panics if: -#! - the asset is not valid. -#! - the total value of the fungible asset is greater than or equal to 2^63 after the new asset was -#! added. -#! - the vault already contains the same non-fungible asset. -export.add_asset_to_vault - # duplicate the ASSET to be able to emit an event after an asset is being added - dupw - # => [ASSET, ASSET] - - # fetch the account vault root - exec.memory::get_account_vault_root_ptr movdn.4 - # => [ASSET, acct_vault_root_ptr, ASSET] - - # emit event to signal that an asset is going to be added to the account vault - emit.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT - - # add the asset to the account vault - exec.asset_vault::add_asset - # => [ASSET', ASSET] - - swapw - # => [ASSET, ASSET'] - - dupw exec.account_delta::add_asset - # => [ASSET, ASSET'] - - # emit event to signal that an asset is being added to the account vault - emit.ACCOUNT_VAULT_AFTER_ADD_ASSET_EVENT - dropw - # => [ASSET'] -end - -#! Removes the specified asset from the account vault. -#! -#! Inputs: [ASSET] -#! Outputs: [ASSET] -#! -#! Where: -#! - ASSET is the asset to remove from the vault. -#! -#! Panics if: -#! - the fungible asset is not found in the vault. -#! - the amount of the fungible asset in the vault is less than the amount to be removed. -#! - the non-fungible asset is not found in the vault. -export.remove_asset_from_vault - # fetch the vault root - exec.memory::get_account_vault_root_ptr movdn.4 - # => [ASSET, acct_vault_root_ptr] - - # emit event to signal that an asset is going to be removed from the account vault - emit.ACCOUNT_VAULT_BEFORE_REMOVE_ASSET_EVENT - - # remove the asset from the account vault - exec.asset_vault::remove_asset - # => [ASSET] - - dupw exec.account_delta::remove_asset - # => [ASSET] - - # emit event to signal that an asset is being removed from the account vault - emit.ACCOUNT_VAULT_AFTER_REMOVE_ASSET_EVENT - # => [ASSET] -end - -#! Returns the balance of the fungible asset associated with the provided faucet_id in the active -#! account's vault. -#! -#! Inputs: [faucet_id_prefix, faucet_id_suffix] -#! Outputs: [balance] -#! -#! Where: -#! - faucet_id_{prefix, suffix} are the prefix and suffix felts of the faucet id of the fungible -#! asset of interest. -#! - balance is the vault balance of the fungible asset. -#! -#! Panics if: -#! - the provided faucet ID is not an ID of a fungible faucet. -export.get_balance - # get the vault root - exec.memory::get_account_vault_root_ptr movdn.2 - # => [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] - - # emit event to signal that an asset's balance is requested - emit.ACCOUNT_VAULT_BEFORE_GET_BALANCE_EVENT - # => [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] - - # get the asset balance - exec.asset_vault::get_balance - # => [balance] -end - -#! Returns the balance of the fungible asset associated with the provided faucet_id in the active -#! account's vault at the beginning of the transaction. -#! -#! Inputs: [faucet_id_prefix, faucet_id_suffix] -#! Outputs: [init_balance] -#! -#! Where: -#! - faucet_id_{prefix, suffix} are the prefix and suffix felts of the faucet id of the fungible -#! asset of interest. -#! - init_balance is the vault balance of the fungible asset at the beginning of the transaction. -#! -#! Panics if: -#! - the provided faucet ID is not an ID of a fungible faucet. -export.get_initial_balance - # get the vault root associated with the initial vault root of the native account - exec.memory::get_account_initial_vault_root_ptr movdn.2 - # => [faucet_id_prefix, faucet_id_suffix, init_native_vault_root_ptr] - - # emit event to signal that an asset's balance is requested - emit.ACCOUNT_VAULT_BEFORE_GET_BALANCE_EVENT - # => [faucet_id_prefix, faucet_id_suffix, init_native_vault_root_ptr] - - # get the asset balance - exec.asset_vault::get_balance - # => [init_balance] -end - -#! Returns a boolean indicating whether the non-fungible asset is present in the active account's -#! vault. -#! -#! Inputs: [ASSET] -#! Outputs: [has_asset] -#! -#! Where: -#! - ASSET is the non-fungible asset of interest. -#! - has_asset is a boolean indicating whether the account vault has the asset of interest. -#! -#! Panics if: -#! - the ASSET is a fungible asset. -export.has_non_fungible_asset - # get the vault root - exec.memory::get_account_vault_root_ptr movdn.4 - # => [ASSET, vault_root_ptr] - - # emit event to signal that an asset's presence is being checked - emit.ACCOUNT_VAULT_BEFORE_HAS_NON_FUNGIBLE_ASSET_EVENT - # => [ASSET, vault_root_ptr] - - # check if the account vault has the non-fungible asset - exec.asset_vault::has_non_fungible_asset - # => [has_asset] -end - - # DATA LOADERS -# ================================================================================================= +# ------------------------------------------------------------------------------------------------- -#! Loads account data from the advice inputs into the _current_ account's memory section. +#! Loads account data from the advice inputs into the _active_ account's memory section. #! #! Inputs: #! Operand stack: [account_id_prefix, account_id_suffix] @@ -1182,7 +1203,7 @@ end #! Operand stack: [] #! #! Where: -#! - STORAGE_COMMITMENT is the commitment of the current account's storage. +#! - STORAGE_COMMITMENT is the commitment of the active account's storage. #! - STORAGE_SLOT_DATA is the data contained in the storage slot which is constructed as follows: #! [SLOT_VALUE, slot_type, 0, 0, 0] #! @@ -1251,7 +1272,7 @@ end #! Operand stack: [] #! #! Where: -#! - CODE_COMMITMENT is the commitment of the current account's code. +#! - CODE_COMMITMENT is the commitment of the active account's code. #! - ACCOUNT_PROCEDURE_DATA is the information about account procedure which is constructed as #! follows: [PROCEDURE_MAST_ROOT, storage_offset, storage_size, 0, 0] #! @@ -1662,30 +1683,30 @@ export.get_account_data_ptr # => [was_loaded, curr_account_ptr, foreign_account_id_prefix, foreign_account_id_suffix] end -#! Checks that the state of the current foreign account is valid. +#! Checks that the state of the active foreign account is valid. #! #! Inputs: [] #! Outputs: [] #! #! Panics if: -#! - the hash of the current account is not represented in the account database. -export.validate_current_foreign_account +#! - the hash of the active account is not represented in the account database. +export.validate_active_foreign_account # get the account database root exec.memory::get_account_db_root # => [ACCOUNT_DB_ROOT] - # get the current account ID + # get the active account ID push.0.0 exec.memory::get_account_id # => [account_id_prefix, account_id_suffix, 0, 0, ACCOUNT_DB_ROOT] - # retrieve the commitment of the foreign account from the current account tree + # retrieve the commitment of the foreign account from the active account tree # this would abort if the proof for the commitment was invalid for the account root, # so this implicitly verifies its correctness exec.smt::get # => [FOREIGN_ACCOUNT_COMMITMENT, ACCOUNT_DB_ROOT] # get the foreign account's commitment from memory and compare with the verified commitment - exec.compute_current_commitment assert_eqw.err=ERR_FOREIGN_ACCOUNT_INVALID_COMMITMENT + exec.compute_commitment assert_eqw.err=ERR_FOREIGN_ACCOUNT_INVALID_COMMITMENT # => [ACCOUNT_DB_ROOT] # clean the stack @@ -1716,7 +1737,7 @@ end #! Outputs: [] proc.refresh_storage_commitment # First we should determine whether the storage commitment should be recomputed. We should do so - # if the current account is native and the storage commitment is outdated (the dirty flag equals + # if the active account is native and the storage commitment is outdated (the dirty flag equals # 1). Otherwise the commitment value is guaranteed to be up-to-date. exec.memory::get_recompute_storage_commitment_flag # => [should_recompute_storage_commitment] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm index ab23d3b9cf..78092d52b6 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -461,11 +461,11 @@ export.finalize_transaction.12 # => [acct_data_end_ptr] # get the offset for the start of the account data section - exec.memory::get_current_account_data_ptr + exec.memory::get_active_account_data_ptr # => [acct_data_ptr, acct_data_end_ptr] # compute the final account commitment - exec.account::compute_current_commitment + exec.account::compute_commitment # => [FINAL_ACCOUNT_COMMITMENT, acct_data_ptr, acct_data_end_ptr] # insert final account data into the advice map diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index aa94e8129d..c24c6e214c 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -6,11 +6,11 @@ use.std::mem const.ERR_NOTE_NUM_OF_ASSETS_EXCEED_LIMIT="number of assets in a note exceed 255" -const.ERR_ACCOUNT_IS_NOT_NATIVE="the current account is not native" +const.ERR_ACCOUNT_IS_NOT_NATIVE="the active account is not native" const.ERR_ACCOUNT_STACK_OVERFLOW="depth of the nested FPI calls exceeded 64" -const.ERR_ACCOUNT_STACK_UNDERFLOW="failed to end foreign context because the current account is the native account" +const.ERR_ACCOUNT_STACK_UNDERFLOW="failed to end foreign context because the active account is the native account" const.ERR_FOREIGN_ACCOUNT_CONTEXT_AGAINST_NATIVE_ACCOUNT="creation of a foreign context against the native account is forbidden" @@ -45,7 +45,7 @@ const.NATIVE_ACCT_STORAGE_COMMITMENT_DIRTY_FLAG_PTR=16 const.TX_EXPIRATION_BLOCK_NUM_PTR=20 # The memory address at which the pointer to the account stack element containing the pointer to the -# currently accessing account data is stored. +# currently accessing account (active account) data is stored. const.ACCOUNT_STACK_TOP_PTR=24 # Pointer to the first element on the account stack. @@ -875,11 +875,11 @@ export.get_max_foreign_account_ptr push.MAX_FOREIGN_ACCOUNT_PTR end -#! Sets the memory pointer of the current account data to the native account (8192). +#! Sets the memory pointer of the active account data to the native account (8192). #! #! Inputs: [] #! Outputs: [] -export.set_current_account_data_ptr_to_native_account +export.set_active_account_data_ptr_to_native_account # store the native account data pointer into the first account stack element. push.NATIVE_ACCOUNT_DATA_PTR mem_store.MIN_ACCOUNT_STACK_PTR # => [native_acct_stack_ptr, account_stack_top_ptr] @@ -889,19 +889,19 @@ export.set_current_account_data_ptr_to_native_account # => [] end -#! Returns the memory pointer of the current account data. +#! Returns the memory pointer of the active account data. #! #! Inputs: [] -#! Outputs: [ptr] +#! Outputs: [active_account_data_ptr] #! #! Where: -#! - ptr is the memory address at which the data of the currently used account begins. -export.get_current_account_data_ptr +#! - active_account_data_ptr is the memory address at which the data of the active account begins. +export.get_active_account_data_ptr mem_load.ACCOUNT_STACK_TOP_PTR # => [account_stack_top_ptr] mem_load - # => [current_account_data_ptr] + # => [active_account_data_ptr] end #! Adds the pointer to the account stack. @@ -926,7 +926,7 @@ export.push_ptr_to_account_stack u32lt.MAX_ACCOUNT_STACK_PTR assert.err=ERR_ACCOUNT_STACK_OVERFLOW # => [account_stack_top_ptr, curr_account_data_ptr] - # check that the current account data pointer is not equal to the native account data pointer + # check that the active account data pointer is not equal to the native account data pointer dup.1 eq.NATIVE_ACCOUNT_DATA_PTR assertz.err=ERR_FOREIGN_ACCOUNT_CONTEXT_AGAINST_NATIVE_ACCOUNT # => [account_stack_top_ptr, curr_account_data_ptr] @@ -961,7 +961,7 @@ export.pop_ptr_from_account_stack # => [] end -#! Asserts that current account data pointer matches the data pointer of the native account (8192). +#! Asserts that active account data pointer matches the data pointer of the native account (8192). #! It is used to prevent usage of the account procedures which can mutate the account state with the #! foreign accounts. #! @@ -969,19 +969,19 @@ end #! Outputs: [] #! #! Panics if: -#! - the current account data pointer is not equal to native account data pointer (8192). +#! - the active account data pointer is not equal to native account data pointer (8192). export.assert_native_account exec.is_native_account assert.err=ERR_ACCOUNT_IS_NOT_NATIVE end -#! Returns 1 if the current account data pointer matches the data pointer of the native account, +#! Returns 1 if the active account data pointer matches the data pointer of the native account, #! 0 otherwise. #! #! Inputs: [] #! Outputs: [is_native_account] export.is_native_account - exec.get_current_account_data_ptr - # => [current_account_data_ptr] + exec.get_active_account_data_ptr + # => [active_account_data_ptr] eq.NATIVE_ACCOUNT_DATA_PTR # => [is_native_account] @@ -995,21 +995,20 @@ end #! Where: #! - ptr is the memory address at which the core account data ends. export.get_core_account_data_end_ptr - exec.get_current_account_data_ptr add.ACCT_CORE_DATA_SECTION_END_OFFSET + exec.get_active_account_data_ptr add.ACCT_CORE_DATA_SECTION_END_OFFSET end ### ACCOUNT ID AND NONCE ################################################# -#! Returns the ID of the current account. +#! Returns the ID of the active account. #! #! Inputs: [] #! Outputs: [account_id_prefix, account_id_suffix] #! #! Where: -#! - account_id_{prefix,suffix} are the prefix and suffix felts of the account ID of the currently -#! accessing account. +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the active account. export.get_account_id - padw exec.get_current_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET mem_loadw + padw exec.get_active_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET mem_loadw # => [nonce, 0, account_id_prefix, account_id_suffix] drop drop # => [account_id_prefix, account_id_suffix] @@ -1036,23 +1035,22 @@ end #! Outputs: [nonce, 0, account_id_prefix, account_id_suffix] #! #! Where: -#! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the currently accessing -#! account. -#! - nonce is the nonce of the currently accessing account. +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the active account. +#! - nonce is the nonce of the active account. export.set_account_id_and_nonce - exec.get_current_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET + exec.get_active_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET mem_storew end -#! Returns the nonce of the current account. +#! Returns the nonce of the active account. #! #! Inputs: [] #! Outputs: [nonce] #! #! Where: -#! - nonce is the nonce of the current account. +#! - nonce is the nonce of the active account. export.get_account_nonce - exec.get_current_account_data_ptr add.ACCT_NONCE_OFFSET + exec.get_active_account_data_ptr add.ACCT_NONCE_OFFSET mem_load end @@ -1068,15 +1066,15 @@ export.get_native_account_nonce mem_load end -#! Sets the nonce of the current account. +#! Sets the nonce of the active account. #! #! Inputs: [nonce] #! Outputs: [] #! #! Where: -#! - nonce is the nonce of the current account. +#! - nonce is the nonce of the active account. export.set_account_nonce - exec.get_current_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET padw + exec.get_active_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET padw # => [0, 0, 0, 0, account_id_and_nonce_ptr, new_nonce] dup.4 mem_loadw # => [old_nonce, 0, old_id_prefix, old_id_suffix, account_id_and_nonce_ptr, new_nonce] @@ -1094,7 +1092,7 @@ end #! Where: #! - account_vault_root_ptr is the memory pointer to the account asset vault root. export.get_account_vault_root_ptr - exec.get_current_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET + exec.get_active_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET end #! Returns the account vault root. @@ -1106,7 +1104,7 @@ end #! - ACCT_VAULT_ROOT is the account asset vault root. export.get_account_vault_root padw - exec.get_current_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET + exec.get_active_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET mem_loadw end @@ -1118,7 +1116,7 @@ end #! Where: #! - ACCT_VAULT_ROOT is the account vault root to be set. export.set_account_vault_root - exec.get_current_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET + exec.get_active_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET mem_storew end @@ -1143,7 +1141,7 @@ export.get_account_initial_vault_root_ptr exec.get_init_native_account_vault_root_ptr # => [native_account_initial_vault_root_ptr, account_vault_root_ptr] - # get the flag indicating whether the current account is native + # get the flag indicating whether the active account is native exec.is_native_account # => [is_native_account, native_account_initial_vault_root_ptr, account_vault_root_ptr] @@ -1163,7 +1161,7 @@ end #! - CODE_COMMITMENT is the code commitment of the account. export.get_account_code_commitment padw - exec.get_current_account_data_ptr add.ACCT_CODE_COMMITMENT_OFFSET + exec.get_active_account_data_ptr add.ACCT_CODE_COMMITMENT_OFFSET mem_loadw end @@ -1175,7 +1173,7 @@ end #! Where: #! - CODE_COMMITMENT is the code commitment to be set. export.set_account_code_commitment - exec.get_current_account_data_ptr add.ACCT_CODE_COMMITMENT_OFFSET + exec.get_active_account_data_ptr add.ACCT_CODE_COMMITMENT_OFFSET mem_storew end @@ -1209,7 +1207,7 @@ end #! Where: #! - num_procedures is the number of procedures contained in the account code. export.get_num_account_procedures - exec.get_current_account_data_ptr add.NUM_ACCT_PROCEDURES_OFFSET + exec.get_active_account_data_ptr add.NUM_ACCT_PROCEDURES_OFFSET mem_load end @@ -1221,7 +1219,7 @@ end #! Where: #! - num_procedures is the number of procedures contained in the account code. export.set_num_account_procedures - exec.get_current_account_data_ptr add.NUM_ACCT_PROCEDURES_OFFSET + exec.get_active_account_data_ptr add.NUM_ACCT_PROCEDURES_OFFSET mem_store end @@ -1233,7 +1231,7 @@ end #! Where: #! - account_procedures_section_ptr is the memory pointer to the account procedures section. export.get_account_procedures_section_ptr - exec.get_current_account_data_ptr add.ACCT_PROCEDURES_SECTION_OFFSET + exec.get_active_account_data_ptr add.ACCT_PROCEDURES_SECTION_OFFSET end #! Returns the memory pointer to the account procedures call tracking section. @@ -1244,7 +1242,7 @@ end #! Where: #! - procedures_call_tracking_ptr is the memory pointer to the procedure call tracking section. export.get_account_procedures_call_tracking_ptr - exec.get_current_account_data_ptr add.ACCT_PROCEDURES_CALL_TRACKING_OFFSET + exec.get_active_account_data_ptr add.ACCT_PROCEDURES_CALL_TRACKING_OFFSET end #! Returns the memory pointer to a specific account procedure. @@ -1270,7 +1268,7 @@ end #! - STORAGE_COMMITMENT is the account storage commitment. export.get_account_storage_commitment padw - exec.get_current_account_data_ptr add.ACCT_STORAGE_COMMITMENT_OFFSET + exec.get_active_account_data_ptr add.ACCT_STORAGE_COMMITMENT_OFFSET mem_loadw end @@ -1282,7 +1280,7 @@ end #! Where: #! - STORAGE_COMMITMENT is the account storage commitment. export.set_account_storage_commitment - exec.get_current_account_data_ptr add.ACCT_STORAGE_COMMITMENT_OFFSET + exec.get_active_account_data_ptr add.ACCT_STORAGE_COMMITMENT_OFFSET mem_storew end @@ -1303,7 +1301,7 @@ end #! Returns the flag indicating whether the account storage commitment should be recomputed. #! -#! This flag equals 1 if the current account is native and the storage commitment is outdated. +#! This flag equals 1 if the active account is native and the storage commitment is outdated. #! #! Inputs: [] #! Outputs: [should_recompute_storage_commitment] @@ -1333,7 +1331,7 @@ end #! Where: #! - num_storage_slots is the number of storage slots contained in the account storage. export.get_num_storage_slots - exec.get_current_account_data_ptr add.NUM_ACCT_STORAGE_SLOTS_OFFSET + exec.get_active_account_data_ptr add.NUM_ACCT_STORAGE_SLOTS_OFFSET mem_load end @@ -1345,7 +1343,7 @@ end #! Where: #! - num_storage_slots is the number of storage slots contained in the account storage. export.set_num_storage_slots - exec.get_current_account_data_ptr add.NUM_ACCT_STORAGE_SLOTS_OFFSET + exec.get_active_account_data_ptr add.NUM_ACCT_STORAGE_SLOTS_OFFSET mem_store end @@ -1371,7 +1369,7 @@ end #! Where: #! - storage_slots_section_ptr is the memory pointer to the account storage slots section. export.get_account_storage_slots_section_ptr - exec.get_current_account_data_ptr add.ACCT_STORAGE_SLOTS_SECTION_OFFSET + exec.get_active_account_data_ptr add.ACCT_STORAGE_SLOTS_SECTION_OFFSET end #! Returns the memory pointer to the native account's storage slots section. @@ -1396,7 +1394,7 @@ export.get_native_account_initial_storage_slots_ptr exec.get_native_account_data_ptr add.ACCT_INITIAL_STORAGE_SLOTS_SECTION_OFFSET end -#! Returns the memory pointer to the initial storage slots of the current account. +#! Returns the memory pointer to the initial storage slots of the active account. #! #! For the native account, this returns the pointer to the initial storage slots section. #! For foreign accounts, this returns the regular storage slots pointer since foreign accounts @@ -1417,7 +1415,7 @@ export.get_account_initial_storage_slots_ptr exec.get_native_account_initial_storage_slots_ptr # => [native_account_initial_storage_slots_ptr, account_storage_slots_ptr] - # get the flag indicating whether the current account is native + # get the flag indicating whether the active account is native exec.is_native_account # => [is_native_account, native_account_initial_storage_slots_ptr, account_storage_slots_ptr] diff --git a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm index 6e7fdbfd85..05384bbe02 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -402,14 +402,14 @@ end #! - ACCOUNT_STORAGE_COMMITMENT is the account's storage commitment. #! - ACCOUNT_CODE_COMMITMENT is the account's code commitment. proc.process_account_data - # Initialize the current account data pointer in the bookkeeping section with the native offset + # Initialize the active account data pointer in the bookkeeping section with the native offset # (2048) - exec.memory::set_current_account_data_ptr_to_native_account + exec.memory::set_active_account_data_ptr_to_native_account # Copy the account data from the advice provider to memory and hash it # --------------------------------------------------------------------------------------------- - exec.memory::get_current_account_data_ptr + exec.memory::get_active_account_data_ptr # => [acct_data_ptr] # read account details and compute its digest. See `Advice stack` above for details. diff --git a/crates/miden-lib/asm/miden/active_account.masm b/crates/miden-lib/asm/miden/active_account.masm index 3da76aec24..576e6b672c 100644 --- a/crates/miden-lib/asm/miden/active_account.masm +++ b/crates/miden-lib/asm/miden/active_account.masm @@ -12,20 +12,20 @@ use.miden::kernel_proc_offsets #! Outputs: [account_id_prefix, account_id_suffix] #! #! Where: -#! - account_id_{prefix,suffix} are the prefix and suffix felts of the account ID of the currently -#! accessing account. +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the active account. #! #! Invocation: exec export.get_id - # start padding the stack - push.0.0.0 + # pad the stack + padw padw padw push.0.0 + # => [pad(14)] - exec.kernel_proc_offsets::account_get_id_offset - # => [offset, 0, 0, 0] + # push the flag indicating that the ID of the current account was requested + push.0 + # => [is_native = 0, pad(14)] - # pad the stack - padw swapw padw padw swapdw - # => [offset, pad(15)] + exec.kernel_proc_offsets::account_get_id_offset + # => [offset, is_native = 0, pad(14)] syscall.exec_kernel_proc # => [account_id_prefix, account_id_suffix, pad(14)] @@ -49,14 +49,11 @@ end #! #! Invocation: exec export.get_nonce - # start padding the stack - push.0.0.0 + # pad the stack + padw padw padw push.0.0.0 + # => [pad(15)] exec.kernel_proc_offsets::account_get_nonce_offset - # => [offset, 0, 0, 0] - - # pad the stack - padw swapw padw padw swapdw # => [offset, pad(15)] syscall.exec_kernel_proc @@ -95,7 +92,7 @@ export.get_initial_commitment # => [INIT_COMMITMENT] end -#! Computes and returns the active account commitment from account data stored in memory. +#! Computes and returns commitment to the state of the active account. #! #! Inputs: [] #! Outputs: [ACCOUNT_COMMITMENT] @@ -109,7 +106,7 @@ export.compute_commitment padw padw padw push.0.0.0 # => [pad(15)] - exec.kernel_proc_offsets::account_compute_current_commitment_offset + exec.kernel_proc_offsets::account_compute_commitment_offset # => [offset, pad(15)] syscall.exec_kernel_proc @@ -143,11 +140,11 @@ end #! #! Invocation: exec export.get_code_commitment - exec.kernel_proc_offsets::account_get_code_commitment_offset - # => [offset] - # pad the stack - push.0.0.0 movup.3 padw swapw padw padw swapdw + padw padw padw push.0.0.0 + # => [pad(15)] + + exec.kernel_proc_offsets::account_get_code_commitment_offset # => [offset, pad(15)] syscall.exec_kernel_proc @@ -188,7 +185,7 @@ export.get_initial_storage_commitment # => [INIT_STORAGE_COMMITMENT] end -#! Computes the latest account storage commitment of the current account. +#! Computes the latest storage commitment of the active account. #! #! Notice that this procedure always returns the latest commitment, but it doesn't actually always #! recompute it: recomputation is performed only if the account's storage has been changed, @@ -207,11 +204,11 @@ end #! #! Invocation: exec export.compute_storage_commitment - exec.kernel_proc_offsets::account_compute_storage_commitment_offset - # => [offset] - # pad the stack - push.0.0.0 movup.3 padw swapw padw padw swapdw + padw padw padw push.0.0.0 + # => [pad(15)] + + exec.kernel_proc_offsets::account_compute_storage_commitment_offset # => [offset, pad(15)] syscall.exec_kernel_proc @@ -504,7 +501,7 @@ end #! Outputs: [num_procedures] #! #! Where: -#! - num_procedures is the number of procedures in the current account. +#! - num_procedures is the number of procedures in the active account. #! #! Invocation: exec export.get_num_procedures diff --git a/crates/miden-lib/asm/miden/contracts/wallets/basic.masm b/crates/miden-lib/asm/miden/contracts/wallets/basic.masm index bfa7ea3d59..7a18caa2d8 100644 --- a/crates/miden-lib/asm/miden/contracts/wallets/basic.masm +++ b/crates/miden-lib/asm/miden/contracts/wallets/basic.masm @@ -5,7 +5,7 @@ use.miden::output_note # ================================================================================================= const.PUBLIC_NOTE=1 -#! Adds the provided asset to the current account. +#! Adds the provided asset to the active account. #! #! Inputs: [ASSET, pad(12)] #! Outputs: [pad(16)] diff --git a/crates/miden-lib/asm/miden/faucet.masm b/crates/miden-lib/asm/miden/faucet.masm index eca12de7d4..1332f2c3ca 100644 --- a/crates/miden-lib/asm/miden/faucet.masm +++ b/crates/miden-lib/asm/miden/faucet.masm @@ -12,7 +12,7 @@ use.miden::kernel_proc_offsets #! - ASSET is the created fungible asset. #! #! Panics if: -#! - the current account is not a fungible faucet. +#! - the active account is not a fungible faucet. #! #! Invocation: exec export.create_fungible_asset @@ -35,7 +35,7 @@ end #! - ASSET is the created non-fungible asset. #! #! Panics if: -#! - the current account is not a non-fungible faucet. +#! - the active account is not a non-fungible faucet. #! #! Invocation: exec export.create_non_fungible_asset diff --git a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm index 36d495edce..3f7215e494 100644 --- a/crates/miden-lib/asm/miden/kernel_proc_offsets.masm +++ b/crates/miden-lib/asm/miden/kernel_proc_offsets.masm @@ -5,92 +5,91 @@ # Entire account commitment const.ACCOUNT_GET_INITIAL_COMMITMENT_OFFSET=0 -const.ACCOUNT_COMPUTE_CURRENT_COMMITMENT_OFFSET=1 +const.ACCOUNT_COMPUTE_COMMITMENT_OFFSET=1 # ID const.ACCOUNT_GET_ID_OFFSET=2 -const.ACCOUNT_GET_NATIVE_ID_OFFSET=3 # Nonce -const.ACCOUNT_GET_NONCE_OFFSET=4 # accessor -const.ACCOUNT_INCR_NONCE_OFFSET=5 # mutator +const.ACCOUNT_GET_NONCE_OFFSET=3 # accessor +const.ACCOUNT_INCR_NONCE_OFFSET=4 # mutator # Code -const.ACCOUNT_GET_CODE_COMMITMENT_OFFSET=6 +const.ACCOUNT_GET_CODE_COMMITMENT_OFFSET=5 # Storage -const.ACCOUNT_GET_INITIAL_STORAGE_COMMITMENT_OFFSET=7 -const.ACCOUNT_COMPUTE_STORAGE_COMMITMENT_OFFSET=8 -const.ACCOUNT_GET_ITEM_OFFSET=9 -const.ACCOUNT_GET_INITIAL_ITEM_OFFSET=10 -const.ACCOUNT_SET_ITEM_OFFSET=11 -const.ACCOUNT_GET_MAP_ITEM_OFFSET=12 -const.ACCOUNT_GET_INITIAL_MAP_ITEM_OFFSET=13 -const.ACCOUNT_SET_MAP_ITEM_OFFSET=14 +const.ACCOUNT_GET_INITIAL_STORAGE_COMMITMENT_OFFSET=6 +const.ACCOUNT_COMPUTE_STORAGE_COMMITMENT_OFFSET=7 +const.ACCOUNT_GET_ITEM_OFFSET=8 +const.ACCOUNT_GET_INITIAL_ITEM_OFFSET=9 +const.ACCOUNT_SET_ITEM_OFFSET=10 +const.ACCOUNT_GET_MAP_ITEM_OFFSET=11 +const.ACCOUNT_GET_INITIAL_MAP_ITEM_OFFSET=12 +const.ACCOUNT_SET_MAP_ITEM_OFFSET=13 # Vault -const.ACCOUNT_GET_INITIAL_VAULT_ROOT_OFFSET=15 -const.ACCOUNT_GET_VAULT_ROOT_OFFSET=16 -const.ACCOUNT_ADD_ASSET_OFFSET=17 -const.ACCOUNT_REMOVE_ASSET_OFFSET=18 -const.ACCOUNT_GET_BALANCE_OFFSET=19 -const.ACCOUNT_GET_INITIAL_BALANCE_OFFSET=20 -const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=21 +const.ACCOUNT_GET_INITIAL_VAULT_ROOT_OFFSET=14 +const.ACCOUNT_GET_VAULT_ROOT_OFFSET=15 +const.ACCOUNT_ADD_ASSET_OFFSET=16 +const.ACCOUNT_REMOVE_ASSET_OFFSET=17 +const.ACCOUNT_GET_BALANCE_OFFSET=18 +const.ACCOUNT_GET_INITIAL_BALANCE_OFFSET=19 +const.ACCOUNT_HAS_NON_FUNGIBLE_ASSET_OFFSET=20 # Delta -const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=22 +const.ACCOUNT_COMPUTE_DELTA_COMMITMENT_OFFSET=21 # Procedure introspection -const.ACCOUNT_GET_NUM_PROCEDURES_OFFSET=23 -const.ACCOUNT_GET_PROCEDURE_ROOT_OFFSET=24 -const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=25 -const.ACCOUNT_HAS_PROCEDURE_OFFSET=26 +const.ACCOUNT_GET_NUM_PROCEDURES_OFFSET=22 +const.ACCOUNT_GET_PROCEDURE_ROOT_OFFSET=23 +const.ACCOUNT_WAS_PROCEDURE_CALLED_OFFSET=24 +const.ACCOUNT_HAS_PROCEDURE_OFFSET=25 ### Faucet ###################################### -const.FAUCET_MINT_ASSET_OFFSET=27 -const.FAUCET_BURN_ASSET_OFFSET=28 -const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=29 -const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=30 +const.FAUCET_MINT_ASSET_OFFSET=26 +const.FAUCET_BURN_ASSET_OFFSET=27 +const.FAUCET_GET_TOTAL_FUNGIBLE_ASSET_ISSUANCE_OFFSET=28 +const.FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=29 ### Note ######################################## # input notes -const.INPUT_NOTE_GET_METADATA_OFFSET=31 -const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=32 -const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=33 -const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=34 -const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=35 -const.INPUT_NOTE_GET_RECIPIENT_OFFSET=36 +const.INPUT_NOTE_GET_METADATA_OFFSET=30 +const.INPUT_NOTE_GET_ASSETS_INFO_OFFSET=31 +const.INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=32 +const.INPUT_NOTE_GET_INPUTS_INFO_OFFSET=33 +const.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=34 +const.INPUT_NOTE_GET_RECIPIENT_OFFSET=35 # output notes -const.OUTPUT_NOTE_CREATE_OFFSET=37 -const.OUTPUT_NOTE_GET_METADATA_OFFSET=38 -const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=39 -const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=40 -const.OUTPUT_NOTE_ADD_ASSET_OFFSET=41 +const.OUTPUT_NOTE_CREATE_OFFSET=36 +const.OUTPUT_NOTE_GET_METADATA_OFFSET=37 +const.OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=38 +const.OUTPUT_NOTE_GET_RECIPIENT_OFFSET=39 +const.OUTPUT_NOTE_ADD_ASSET_OFFSET=40 ### Tx ########################################## # input notes -const.TX_GET_NUM_INPUT_NOTES_OFFSET=42 -const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=43 +const.TX_GET_NUM_INPUT_NOTES_OFFSET=41 +const.TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=42 # output notes -const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=44 -const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=45 +const.TX_GET_NUM_OUTPUT_NOTES_OFFSET=43 +const.TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=44 # block info -const.TX_GET_BLOCK_COMMITMENT_OFFSET=46 -const.TX_GET_BLOCK_NUMBER_OFFSET=47 -const.TX_GET_BLOCK_TIMESTAMP_OFFSET=48 +const.TX_GET_BLOCK_COMMITMENT_OFFSET=45 +const.TX_GET_BLOCK_NUMBER_OFFSET=46 +const.TX_GET_BLOCK_TIMESTAMP_OFFSET=47 # foreign context -const.TX_START_FOREIGN_CONTEXT_OFFSET=49 -const.TX_END_FOREIGN_CONTEXT_OFFSET=50 +const.TX_START_FOREIGN_CONTEXT_OFFSET=48 +const.TX_END_FOREIGN_CONTEXT_OFFSET=49 # expiration data -const.TX_GET_EXPIRATION_DELTA_OFFSET=51 # accessor -const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=52 # mutator +const.TX_GET_EXPIRATION_DELTA_OFFSET=50 # accessor +const.TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=51 # mutator # ACCESSORS # ------------------------------------------------------------------------------------------------- @@ -109,16 +108,16 @@ export.account_get_initial_commitment_offset push.ACCOUNT_GET_INITIAL_COMMITMENT_OFFSET end -#! Returns the offset of the `account_compute_current_commitment` kernel procedure. +#! Returns the offset of the `account_compute_commitment` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `account_compute_current_commitment` kernel procedure required -#! to get the address where this procedure is stored. -export.account_compute_current_commitment_offset - push.ACCOUNT_COMPUTE_CURRENT_COMMITMENT_OFFSET +#! - proc_offset is the offset of the `account_compute_commitment` kernel procedure required to get +#! the address where this procedure is stored. +export.account_compute_commitment_offset + push.ACCOUNT_COMPUTE_COMMITMENT_OFFSET end #! Returns the offset of the `account_compute_delta_commitment` kernel procedure. @@ -145,18 +144,6 @@ export.account_get_id_offset push.ACCOUNT_GET_ID_OFFSET end -#! Returns the offset of the `account_get_native_id` kernel procedure. -#! -#! Inputs: [] -#! Outputs: [proc_offset] -#! -#! Where: -#! - proc_offset is the offset of the `account_get_native_id` kernel procedure required to get the -#! address where this procedure is stored. -export.account_get_native_id_offset - push.ACCOUNT_GET_NATIVE_ID_OFFSET -end - #! Returns the offset of the `account_get_nonce` kernel procedure. #! #! Inputs: [] diff --git a/crates/miden-lib/asm/miden/native_account.masm b/crates/miden-lib/asm/miden/native_account.masm index d0bd83b44e..f09f638c0d 100644 --- a/crates/miden-lib/asm/miden/native_account.masm +++ b/crates/miden-lib/asm/miden/native_account.masm @@ -17,15 +17,16 @@ use.miden::kernel_proc_offsets #! #! Invocation: exec export.get_id - # start padding the stack - push.0.0.0 + # pad the stack + padw padw padw push.0.0 + # => [pad(14)] - exec.kernel_proc_offsets::account_get_native_id_offset - # => [offset, 0, 0, 0] + # push the flag indicating that the ID of the native account was requested + push.1 + # => [is_native = 1, pad(14)] - # pad the stack - padw swapw padw padw swapdw - # => [offset, pad(15)] + exec.kernel_proc_offsets::account_get_id_offset + # => [offset, is_native = 0, pad(14)] syscall.exec_kernel_proc # => [account_id_prefix, account_id_suffix, pad(14)] diff --git a/crates/miden-lib/asm/miden/tx.masm b/crates/miden-lib/asm/miden/tx.masm index a1c6addea9..45516b26e3 100644 --- a/crates/miden-lib/asm/miden/tx.masm +++ b/crates/miden-lib/asm/miden/tx.masm @@ -241,7 +241,7 @@ export.execute_foreign_procedure.4 dyncall # => [] - # reset the current account data offset to the native offset (2048) + # reset the active account data offset to the native offset (2048) push.0.0.0 padw padw padw exec.kernel_proc_offsets::tx_end_foreign_context_offset # => [offset, pad(15), ] diff --git a/crates/miden-lib/asm/note_scripts/P2IDE.masm b/crates/miden-lib/asm/note_scripts/P2IDE.masm index 42ccc111c9..87adda3cb8 100644 --- a/crates/miden-lib/asm/note_scripts/P2IDE.masm +++ b/crates/miden-lib/asm/note_scripts/P2IDE.masm @@ -55,11 +55,11 @@ proc.reclaim_note lte assert.err=ERR_P2IDE_RECLAIM_HEIGHT_NOT_REACHED # => [account_id_prefix, account_id_suffix] - # if current account is not the target, we need to ensure it is the sender + # if active account is not the target, we need to ensure it is the sender exec.active_note::get_sender # => [sender_account_id_prefix, sender_account_id_suffix, account_id_prefix, account_id_suffix] - # ensure current account ID = sender account ID + # ensure active account ID = sender account ID exec.account_id::is_equal assert.err=ERR_P2IDE_RECLAIM_ACCT_IS_NOT_SENDER # => [] @@ -120,21 +120,21 @@ begin exec.verify_unlocked # => [current_block_height, reclaim_block_height, target_account_id_prefix, target_account_id_suffix] - # get current account id + # get active account id exec.active_account::get_id dup.1 dup.1 # => [account_id_prefix, account_id_suffix, account_id_prefix, account_id_suffix, current_block_height, reclaim_block_height, target_account_id_prefix, target_account_id_suffix] - # determine if the current account is the target account + # determine if the active account is the target account movup.7 movup.7 exec.account_id::is_equal # => [is_target, account_id_prefix, account_id_suffix, current_block_height, reclaim_block_height] if.true - # we can safely consume the note since the current account is the target of the note + # we can safely consume the note since the active account is the target of the note dropw exec.active_note::add_assets_to_account # => [] else - # checks if current account is sender and if reclaim is enabled + # checks if active account is sender and if reclaim is enabled exec.reclaim_note # => [] end diff --git a/crates/miden-lib/src/errors/tx_kernel_errors.rs b/crates/miden-lib/src/errors/tx_kernel_errors.rs index 4b0778c4e0..3022640235 100644 --- a/crates/miden-lib/src/errors/tx_kernel_errors.rs +++ b/crates/miden-lib/src/errors/tx_kernel_errors.rs @@ -28,8 +28,8 @@ pub const ERR_ACCOUNT_ID_UNKNOWN_STORAGE_MODE: MasmError = MasmError::from_stati pub const ERR_ACCOUNT_ID_UNKNOWN_VERSION: MasmError = MasmError::from_static_str("unknown version in account ID"); /// Error Message: "storage size can only be zero if storage offset is also zero" pub const ERR_ACCOUNT_INVALID_STORAGE_OFFSET_FOR_SIZE: MasmError = MasmError::from_static_str("storage size can only be zero if storage offset is also zero"); -/// Error Message: "the current account is not native" -pub const ERR_ACCOUNT_IS_NOT_NATIVE: MasmError = MasmError::from_static_str("the current account is not native"); +/// Error Message: "the active account is not native" +pub const ERR_ACCOUNT_IS_NOT_NATIVE: MasmError = MasmError::from_static_str("the active account is not native"); /// Error Message: "account nonce is already at its maximum possible value" pub const ERR_ACCOUNT_NONCE_AT_MAX: MasmError = MasmError::from_static_str("account nonce is already at its maximum possible value"); /// Error Message: "account nonce can only be incremented once" @@ -50,8 +50,8 @@ pub const ERR_ACCOUNT_SETTING_MAP_ITEM_ON_NON_MAP_SLOT: MasmError = MasmError::f pub const ERR_ACCOUNT_SETTING_VALUE_ITEM_ON_NON_VALUE_SLOT: MasmError = MasmError::from_static_str("failed to write an account value item to a non-value storage slot"); /// Error Message: "depth of the nested FPI calls exceeded 64" pub const ERR_ACCOUNT_STACK_OVERFLOW: MasmError = MasmError::from_static_str("depth of the nested FPI calls exceeded 64"); -/// Error Message: "failed to end foreign context because the current account is the native account" -pub const ERR_ACCOUNT_STACK_UNDERFLOW: MasmError = MasmError::from_static_str("failed to end foreign context because the current account is the native account"); +/// Error Message: "failed to end foreign context because the active account is the native account" +pub const ERR_ACCOUNT_STACK_UNDERFLOW: MasmError = MasmError::from_static_str("failed to end foreign context because the active account is the native account"); /// Error Message: "computed account storage commitment does not match recorded account storage commitment" pub const ERR_ACCOUNT_STORAGE_COMMITMENT_MISMATCH: MasmError = MasmError::from_static_str("computed account storage commitment does not match recorded account storage commitment"); /// Error Message: "storage map entries provided as advice inputs do not have the same storage map root as the root of the map the new account commits to" diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index 91dd86b8d8..e19f407966 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -6,15 +6,13 @@ use miden_objects::{Word, word}; // ================================================================================================ /// Hashes of all dynamically executed kernel procedures. -pub const KERNEL_PROCEDURES: [Word; 53] = [ +pub const KERNEL_PROCEDURES: [Word; 52] = [ // account_get_initial_commitment word!("0x1c95a0386ebf3645c6271253a4ae49ea4be8610dea7b4436c58951277a75f0c1"), - // account_compute_current_commitment + // account_compute_commitment word!("0x1aed40e2cc4d3798448f4efdce1a14c9598611da065eebe58432f144c3bca9de"), // account_get_id - word!("0xc9f7e71b294e16d7a297ba283afb2f8c864817e40e73b6ef1d64efc310937fc7"), - // account_get_native_id - word!("0x05eb568956d0174066a1277442cc4602fcbbc6790bd64975416958d28274cb73"), + word!("0xd76288f2e94b9e6a8f7eeee45c4ee0a23997d78496f6132e3f55681efea809c4"), // account_get_nonce word!("0x4a1f11db21ddb1f0ebf7c9fd244f896a95e99bb136008185da3e7d6aa85827a3"), // account_incr_nonce diff --git a/crates/miden-lib/src/transaction/memory.rs b/crates/miden-lib/src/transaction/memory.rs index db503d2693..4fbb83178b 100644 --- a/crates/miden-lib/src/transaction/memory.rs +++ b/crates/miden-lib/src/transaction/memory.rs @@ -96,7 +96,7 @@ pub const NATIVE_ACCT_STORAGE_COMMITMENT_DIRTY_FLAG_PTR: MemoryAddress = 16; pub const TX_EXPIRATION_BLOCK_NUM_PTR: MemoryAddress = 20; /// The memory address at which the pointer to the stack element containing the pointer to the -/// currently active account data is stored. +/// active account data is stored. /// /// The stack starts at the address `29`. Stack has a length of `64` elements meaning that the /// maximum depth of FPI calls is `63` — the first slot is always occupied by the native account diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index 362a3c19ec..b1d30b7bad 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -120,7 +120,7 @@ pub enum AccountError { FinalAccountHeaderIdParsingFailed(#[source] AccountIdError), #[error("account header data has length {actual} but it must be of length {expected}")] HeaderDataIncorrectLength { actual: usize, expected: usize }, - #[error("current account nonce {current} plus increment {increment} overflows a felt to {new}")] + #[error("active account nonce {current} plus increment {increment} overflows a felt to {new}")] NonceOverflow { current: Felt, increment: Felt, diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 84e0f52bdc..10b8f23ffa 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -70,7 +70,7 @@ use crate::{ // ================================================================================================ #[tokio::test] -pub async fn compute_current_commitment() -> miette::Result<()> { +pub async fn compute_commitment() -> miette::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); // Precompute a commitment to a changed account so we can assert it during tx script execution. @@ -129,7 +129,7 @@ pub async fn compute_current_commitment() -> miette::Result<()> { # assert that the commitment has changed exec.word::eq - assertz.err="storage commitment should have been updated by compute_current_commitment" + assertz.err="storage commitment should have been updated by compute_commitment" # => [] end "#, diff --git a/crates/miden-tx/src/host/account_procedures.rs b/crates/miden-tx/src/host/account_procedures.rs index 8d7d2a9d8b..e7fbbd0300 100644 --- a/crates/miden-tx/src/host/account_procedures.rs +++ b/crates/miden-tx/src/host/account_procedures.rs @@ -61,7 +61,7 @@ impl AccountProcedureIndexMap { /// Returns an error if the procedure at the top of the operand stack is not present in this /// map. pub fn get_proc_index(&self, process: &ProcessState) -> Result { - // get current account code commitment + // get active account code commitment let code_commitment = { let account_stack_top_ptr = process .get_mem_value(process.ctx(), ACCOUNT_STACK_TOP_PTR) @@ -74,12 +74,12 @@ impl AccountProcedureIndexMap { .try_into() .expect("account stack top pointer should be less than u32::MAX"), ) - .expect("Current account pointer was not initialized") + .expect("active account pointer was not initialized") .as_int(); process .get_mem_word(process.ctx(), curr_data_ptr as u32 + ACCT_CODE_COMMITMENT_OFFSET) .expect("failed to read a word from memory") - .expect("current account code commitment was not initialized") + .expect("active account code commitment was not initialized") }; let proc_root = process.get_stack_word(1); diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index d08c3599f6..14085cabb1 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -8,16 +8,18 @@ The Miden protocol library provides a set of procedures that wrap transaction ke ## Contexts +Here and in other places we use the notion of _active account_: it is the account which is currently being accessed. + The Miden VM contexts from which procedures can be called are: - **Account**: Can only be called from native or foreign accounts. - - **Native**: Can only be called when the current account is the native account. + - **Native**: Can only be called when the active account is the native account. - **Auth**: Can only be called from the authentication procedure. Since it is called on the native account, it implies **Native** and **Account**. - - **Faucet**: Can only be called when the current account is a faucet. + - **Faucet**: Can only be called when the active account is a faucet. - **Note**: Can only be called from a note script. - **Any**: Can be called from any context. -If a procedure has multiple context requirements they are combined using `&`. For instance, "Native & Account" means the procedure can only be called when the current account is the native one _and_ only from the account context. +If a procedure has multiple context requirements they are combined using `&`. For instance, "Native & Account" means the procedure can only be called when the active account is the native one _and_ only from the account context. ## Implementation @@ -42,9 +44,9 @@ Active account procedures can be used to read from storage, fetch or compute com | `get_initial_item` | Gets the initial item from the account storage slot as it was at the beginning of the transaction.

**Inputs:** `[index]`
**Outputs:** `[VALUE]` | Account | | `get_map_item` | Returns the VALUE located under the specified KEY within the map contained in the given account storage slot.

**Inputs:** `[index, KEY]`
**Outputs:** `[VALUE]` | Account | | `get_initial_map_item` | Gets the initial VALUE from the account storage map as it was at the beginning of the transaction.

**Inputs:** `[index, KEY]`
**Outputs:** `[VALUE]` | Account | -| `get_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[balance]` | Any | -| `get_initial_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the current account's vault at the beginning of the transaction.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[init_balance]` | Any | -| `has_non_fungible_asset` | Returns a boolean indicating whether the non-fungible asset is present in the current account's vault.

**Inputs:** `[ASSET]`
**Outputs:** `[has_asset]` | Any | +| `get_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the active account's vault.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[balance]` | Any | +| `get_initial_balance` | Returns the balance of the fungible asset associated with the provided faucet_id in the active account's vault at the beginning of the transaction.

**Inputs:** `[faucet_id_prefix, faucet_id_suffix]`
**Outputs:** `[init_balance]` | Any | +| `has_non_fungible_asset` | Returns a boolean indicating whether the non-fungible asset is present in the active account's vault.

**Inputs:** `[ASSET]`
**Outputs:** `[has_asset]` | Any | | `get_initial_vault_root` | Returns the vault root of the active account at the beginning of the transaction.

**Inputs:** `[]`
**Outputs:** `[INIT_VAULT_ROOT]` | Any | | `get_vault_root` | Returns the vault root of the active account.

**Inputs:** `[]`
**Outputs:** `[VAULT_ROOT]` | Any | | `get_num_procedures` | Returns the number of procedures in the active account.

**Inputs:** `[]`
**Outputs:** `[num_procedures]` | Any | From d5e438c877985f0c449041a501be6a0ff5b6c638 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Mon, 3 Nov 2025 21:11:07 +0300 Subject: [PATCH 120/133] refactor: drop return values of the `output_note::add_asset` procedure (#2031) --- CHANGELOG.md | 1 + .../asm/kernels/transaction/api.masm | 8 +-- .../kernels/transaction/lib/output_note.masm | 6 +- .../asm/miden/contracts/faucets/mod.masm | 3 - .../asm/miden/contracts/wallets/basic.masm | 5 +- crates/miden-lib/asm/miden/output_note.masm | 8 +-- crates/miden-lib/src/testing/mock_util_lib.rs | 6 +- .../src/transaction/kernel_procedures.rs | 2 +- .../src/kernel_tests/tx/test_epilogue.rs | 4 +- .../src/kernel_tests/tx/test_lazy_loading.rs | 4 +- .../src/kernel_tests/tx/test_output_note.rs | 65 ++++++++----------- docs/src/protocol_library.md | 2 +- 12 files changed, 51 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e20ca7935..2867f591ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ - [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). - [BREAKING] Refactor `TransactionInputs` and remove `TransactionWitness` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). - [BREAKING] Assert nonce is non-zero after the auth procedure ([#1982](https://github.com/0xMiden/miden-base/pull/1982)). +- [BREAKING] Change the outputs of the `output_note::add_asset` procedure: now the values that are the same as the passed parameters are dropped ([#2031](https://github.com/0xMiden/miden-base/pull/2031)). ## 0.11.5 (2025-10-02) diff --git a/crates/miden-lib/asm/kernels/transaction/api.masm b/crates/miden-lib/asm/kernels/transaction/api.masm index 627c0110aa..48b764e071 100644 --- a/crates/miden-lib/asm/kernels/transaction/api.masm +++ b/crates/miden-lib/asm/kernels/transaction/api.masm @@ -1171,7 +1171,7 @@ end #! Adds the ASSET to the note specified by the index. #! #! Inputs: [note_idx, ASSET, pad(11)] -#! Outputs: [note_idx, ASSET, pad(11)] +#! Outputs: [pad(16)] #! #! Where: #! - note_idx is the index of the note to which the asset is added. @@ -1186,12 +1186,8 @@ export.output_note_add_asset exec.memory::assert_native_account # => [note_idx, ASSET, pad(11)] - # duplicate the asset word to be able to return it - movdn.4 dupw movup.8 - # => [note_idx, ASSET, ASSET, pad(11)] - exec.output_note::add_asset - # => [note_idx, ASSET, pad(11)] + # => [pad(16)] end #! Returns the information about assets in the output note with the specified index. diff --git a/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm b/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm index 706d0b7fc2..bc06593d67 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm @@ -177,7 +177,7 @@ end #! Adds the ASSET to the note specified by the index. #! #! Inputs: [note_idx, ASSET] -#! Outputs: [note_idx] +#! Outputs: [] #! #! Where: #! - note_idx is the index of the note to which the asset is added. @@ -232,6 +232,10 @@ export.add_asset # emit event to signal that a new asset was added to the note. emit.NOTE_AFTER_ADD_ASSET_EVENT # => [note_idx] + + # drop the note index + drop + # => [] end #! Assert that the provided note index is less than the total number of output notes. diff --git a/crates/miden-lib/asm/miden/contracts/faucets/mod.masm b/crates/miden-lib/asm/miden/contracts/faucets/mod.masm index 202db163da..a7b45ac1a6 100644 --- a/crates/miden-lib/asm/miden/contracts/faucets/mod.masm +++ b/crates/miden-lib/asm/miden/contracts/faucets/mod.masm @@ -74,9 +74,6 @@ export.distribute # load the ASSET and add it to the note movdn.4 exec.output_note::add_asset - # => [ASSET, note_idx] - - dropw drop # => [] end diff --git a/crates/miden-lib/asm/miden/contracts/wallets/basic.masm b/crates/miden-lib/asm/miden/contracts/wallets/basic.masm index 7a18caa2d8..0094eec5af 100644 --- a/crates/miden-lib/asm/miden/contracts/wallets/basic.masm +++ b/crates/miden-lib/asm/miden/contracts/wallets/basic.masm @@ -53,6 +53,9 @@ export.move_asset_to_note exec.native_account::remove_asset # => [ASSET, note_idx, pad(11)] + dupw dup.8 movdn.4 + # => [ASSET, note_idx, ASSET, note_idx, pad(11)] + exec.output_note::add_asset - # => [ASSET, note_idx, pad(11) ...] + # => [ASSET, note_idx, pad(11)] end diff --git a/crates/miden-lib/asm/miden/output_note.masm b/crates/miden-lib/asm/miden/output_note.masm index 05b92d1083..39c900edfc 100644 --- a/crates/miden-lib/asm/miden/output_note.masm +++ b/crates/miden-lib/asm/miden/output_note.masm @@ -110,7 +110,7 @@ end #! Adds the ASSET to the note specified by the index. #! #! Inputs: [ASSET, note_idx] -#! Outputs: [ASSET, note_idx] +#! Outputs: [] #! #! Where: #! - note_idx is the index of the note to which the asset is added. @@ -127,11 +127,11 @@ export.add_asset # => [offset, note_idx, ASSET, pad(10)] syscall.exec_kernel_proc - # => [note_idx, ASSET, pad(11)] + # => [pad(16)] # remove excess PADs from the stack - swapdw dropw dropw swapw movdn.7 drop drop drop movdn.4 - # => [ASSET, note_idx] + dropw dropw dropw dropw + # => [] end #! Returns the recipient of the output note with the specified index. diff --git a/crates/miden-lib/src/testing/mock_util_lib.rs b/crates/miden-lib/src/testing/mock_util_lib.rs index 92fc52a041..5f6c99cb37 100644 --- a/crates/miden-lib/src/testing/mock_util_lib.rs +++ b/crates/miden-lib/src/testing/mock_util_lib.rs @@ -22,7 +22,7 @@ const MOCK_UTIL_LIBRARY_CODE: &str = " end # Inputs: [ASSET] - # Outputs: [note_idx] + # Outputs: [] export.create_random_note_with_asset exec.create_random_note # => [note_idx, ASSET] @@ -30,8 +30,8 @@ const MOCK_UTIL_LIBRARY_CODE: &str = " movdn.4 # => [ASSET, note_idx] - exec.output_note::add_asset dropw - # => [note_idx] + exec.output_note::add_asset + # => [] end "; diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index e19f407966..e572b51e2d 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -88,7 +88,7 @@ pub const KERNEL_PROCEDURES: [Word; 52] = [ // output_note_get_recipient word!("0xc824115ed79a2e1670daed8c18fba1bc15f54c5ec0ec6699de69a00b21d9df92"), // output_note_add_asset - word!("0x70bed5d5bdd012db2dc3f47a72b4d1f4c58d2be9b7b86e63c95076ebb7a80a94"), + word!("0x9b6929d1ce24b3a97c6fb098f2fa8d0958beb15f91e268b9c787194b0a977a0d"), // tx_get_num_input_notes word!("0xfcc186d4b65c584f3126dda1460b01eef977efd76f9e36f972554af28e33c685"), // tx_get_input_notes_commitment diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 32ee7ff22d..9b778fd556 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -232,7 +232,7 @@ async fn epilogue_fails_when_num_output_assets_exceed_num_input_assets() -> anyh begin # create a note with the output asset push.{OUTPUT_ASSET} - exec.util::create_random_note_with_asset drop + exec.util::create_random_note_with_asset # => [] end ", @@ -285,7 +285,7 @@ async fn epilogue_fails_when_num_input_assets_exceed_num_output_assets() -> anyh begin # create a note with the output asset push.{OUTPUT_ASSET} - exec.util::create_random_note_with_asset drop + exec.util::create_random_note_with_asset # => [] end ", diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index 221477472f..83829fcd5e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -95,7 +95,7 @@ async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result # => [] # move asset to note to adhere to asset preservation rules - exec.util::create_random_note_with_asset drop + exec.util::create_random_note_with_asset # => [] push.{FUNGIBLE_ASSET2} @@ -103,7 +103,7 @@ async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result # => [ASSET] # move asset to note to adhere to asset preservation rules - exec.util::create_random_note_with_asset drop + exec.util::create_random_note_with_asset # => [] end ", diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 5272263bbf..341e77d6d1 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -315,9 +315,6 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { push.{asset_1} call.output_note::add_asset - # => [ASSET, note_idx] - - dropw drop # => [] # create output note 2 @@ -331,9 +328,6 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { push.{asset_2} call.output_note::add_asset - # => [ASSET, note_idx] - - dropw drop # => [] # compute the output notes commitment @@ -406,7 +400,6 @@ async fn test_create_note_and_add_asset() -> anyhow::Result<()> { use.miden::output_note use.$kernel::prologue - use.mock::account begin exec.prologue::prepare_transaction @@ -420,15 +413,18 @@ async fn test_create_note_and_add_asset() -> anyhow::Result<()> { call.output_note::create # => [note_idx] + # assert that the index of the created note equals zero + dup assertz.err=\"index of the created note should be zero\" + # => [note_idx] + push.{asset} - call.output_note::add_asset # => [ASSET, note_idx] - dropw - # => [note_idx] + call.output_note::add_asset + # => [] # truncate the stack - swapdw dropw dropw + dropw dropw dropw end ", recipient = recipient, @@ -446,11 +442,6 @@ async fn test_create_note_and_add_asset() -> anyhow::Result<()> { "asset must be stored at the correct memory location", ); - assert_eq!( - exec_output.get_stack_element(0), - ZERO, - "top item on the stack is the index to the output note" - ); Ok(()) } @@ -476,9 +467,7 @@ async fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { let code = format!( " use.miden::output_note - use.$kernel::prologue - use.mock::account begin exec.prologue::prepare_transaction @@ -491,24 +480,28 @@ async fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { call.output_note::create # => [note_idx] - push.{asset} - call.output_note::add_asset dropw + # assert that the index of the created note equals zero + dup assertz.err=\"index of the created note should be zero\" # => [note_idx] - push.{asset_2} - call.output_note::add_asset dropw + dup push.{asset} + call.output_note::add_asset # => [note_idx] - push.{asset_3} - call.output_note::add_asset dropw + dup push.{asset_2} + call.output_note::add_asset # => [note_idx] - push.{nft} - call.output_note::add_asset dropw + dup push.{asset_3} + call.output_note::add_asset # => [note_idx] + push.{nft} + call.output_note::add_asset + # => [] + # truncate the stack - swapdw dropw drop drop drop + repeat.7 dropw end end ", recipient = recipient, @@ -540,11 +533,6 @@ async fn test_create_note_and_add_multiple_assets() -> anyhow::Result<()> { "non_fungible_asset must be stored at the correct memory location", ); - assert_eq!( - exec_output.get_stack_element(0), - ZERO, - "top item on the stack is the index to the output note" - ); Ok(()) } @@ -574,18 +562,17 @@ async fn test_create_note_and_add_same_nft_twice() -> anyhow::Result<()> { push.{tag} call.output_note::create - # => [note_idx, pad(15)] + # => [note_idx] + + dup push.{nft} + # => [NFT, note_idx, note_idx] - push.{nft} call.output_note::add_asset - # => [NFT, note_idx, pad(15)] - dropw + # => [note_idx] push.{nft} call.output_note::add_asset - # => [NFT, note_idx, pad(15)] - - repeat.5 dropw end + # => [] end ", recipient = recipient, diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index 14085cabb1..aa49c87157 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -107,7 +107,7 @@ Output note procedures can be used to fetch data on output notes created by the | `create` | Creates a new output note and returns its index.

**Inputs:** `[tag, aux, note_type, execution_hint, RECIPIENT]`
**Outputs:** `[note_idx]` | Native & Account | | `get_assets_info` | Returns the information about assets in the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[ASSETS_COMMITMENT, num_assets]` | Any | | `get_assets` | Writes the assets of the output note with the specified index into memory starting at the specified address.

**Inputs:** `[dest_ptr, note_index]`
**Outputs:** `[num_assets, dest_ptr, note_index]` | Any | -| `add_asset` | Adds the `ASSET` to the output note specified by the index.

**Inputs:** `[ASSET, note_idx]`
**Outputs:** `[ASSET, note_idx]` | Native | +| `add_asset` | Adds the `ASSET` to the output note specified by the index.

**Inputs:** `[ASSET, note_idx]`
**Outputs:** `[]` | Native | | `get_recipient` | Returns the [recipient](note#note-recipient-restricting-consumption) of the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[RECIPIENT]` | Any | | `get_metadata` | Returns the [metadata](note#metadata) of the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[METADATA]` | Any | From 86ec4d39c115da7d9bf69672b793a7abfb70477f Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Tue, 4 Nov 2025 01:36:04 +0300 Subject: [PATCH 121/133] feat: add static analysis for well-known notes in `NoteConsumptionChecker` (#1998) --- crates/miden-lib/src/account/interface/mod.rs | 2 +- crates/miden-lib/src/note/mod.rs | 5 +- crates/miden-lib/src/note/well_known_note.rs | 233 ++++++++++- .../kernel_tests/tx/test_account_interface.rs | 363 +++++++++++++++++- crates/miden-testing/tests/auth/multisig.rs | 152 +------- crates/miden-testing/tests/scripts/faucet.rs | 2 +- crates/miden-tx/src/executor/mod.rs | 1 - crates/miden-tx/src/executor/notes_checker.rs | 37 +- crates/miden-tx/src/lib.rs | 1 - 9 files changed, 607 insertions(+), 189 deletions(-) diff --git a/crates/miden-lib/src/account/interface/mod.rs b/crates/miden-lib/src/account/interface/mod.rs index ed928ff10b..2586134cf2 100644 --- a/crates/miden-lib/src/account/interface/mod.rs +++ b/crates/miden-lib/src/account/interface/mod.rs @@ -22,7 +22,7 @@ use crate::account::components::{ rpo_falcon_512_multisig_library, }; use crate::errors::ScriptBuilderError; -use crate::note::well_known_note::WellKnownNote; +use crate::note::WellKnownNote; use crate::utils::ScriptBuilder; #[cfg(test)] diff --git a/crates/miden-lib/src/note/mod.rs b/crates/miden-lib/src/note/mod.rs index 31b5319288..b02d417867 100644 --- a/crates/miden-lib/src/note/mod.rs +++ b/crates/miden-lib/src/note/mod.rs @@ -17,10 +17,11 @@ use miden_objects::note::{ }; use miden_objects::{Felt, NoteError, Word}; use utils::build_swap_tag; -use well_known_note::WellKnownNote; pub mod utils; -pub mod well_known_note; + +mod well_known_note; +pub use well_known_note::{NoteConsumptionStatus, WellKnownNote}; // STANDARDIZED SCRIPTS // ================================================================================================ diff --git a/crates/miden-lib/src/note/well_known_note.rs b/crates/miden-lib/src/note/well_known_note.rs index 3a783b8840..f8055428f7 100644 --- a/crates/miden-lib/src/note/well_known_note.rs +++ b/crates/miden-lib/src/note/well_known_note.rs @@ -1,8 +1,14 @@ -use miden_objects::Word; +use alloc::boxed::Box; +use alloc::string::String; +use core::error::Error; + +use miden_objects::account::AccountId; +use miden_objects::block::BlockNumber; use miden_objects::note::{Note, NoteScript}; use miden_objects::utils::Deserializable; use miden_objects::utils::sync::LazyLock; use miden_objects::vm::Program; +use miden_objects::{Felt, Word}; use crate::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet}; use crate::account::interface::{AccountComponentInterface, AccountInterface}; @@ -226,4 +232,229 @@ impl WellKnownNote { }, } } + + /// Performs the inputs check of the provided well-known note against the target account and the + /// block number. + /// + /// This function returns: + /// - `Some` if we can definitively determine whether the note can be consumed not by the target + /// account. + /// - `None` if the consumption status of the note cannot be determined conclusively and further + /// checks are necessary. + pub fn is_consumable( + &self, + note: &Note, + target_account_id: AccountId, + block_ref: BlockNumber, + ) -> Option { + match self.is_consumable_inner(note, target_account_id, block_ref) { + Ok(status) => status, + Err(err) => { + let err: Box = Box::from(err); + Some(NoteConsumptionStatus::NeverConsumable(err)) + }, + } + } + + /// Performs the inputs check of the provided note against the target account and the block + /// number. + /// + /// It performs: + /// - for `P2ID` note: + /// - check that note inputs have correct number of values. + /// - assertion that the account ID provided by the note inputs is equal to the target + /// account ID. + /// - for `P2IDE` note: + /// - check that note inputs have correct number of values. + /// - check that the target account is either the receiver account or the sender account. + /// - check that depending on whether the target account is sender or receiver, it could be + /// either consumed, or consumed after timelock height, or consumed after reclaim height. + /// ``` + fn is_consumable_inner( + &self, + note: &Note, + target_account_id: AccountId, + block_ref: BlockNumber, + ) -> Result, StaticAnalysisError> { + match self { + WellKnownNote::P2ID => { + let input_account_id = parse_p2id_inputs(note.inputs().values())?; + + if input_account_id == target_account_id { + Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization)) + } else { + Ok(Some(NoteConsumptionStatus::NeverConsumable("account ID provided to the P2ID note inputs doesn't match the target account ID".into()))) + } + }, + WellKnownNote::P2IDE => { + let (receiver_account_id, reclaim_height, timelock_height) = + parse_p2ide_inputs(note.inputs().values())?; + + let current_block_height = block_ref.as_u32(); + + // block height after which sender account can consume the note + let consumable_after = reclaim_height.max(timelock_height); + + // handle the case when the target account of the transaction is sender + if target_account_id == note.metadata().sender() { + // For the sender, the current block height needs to have reached both reclaim + // and timelock height to be consumable. + if current_block_height >= consumable_after { + Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization)) + } else { + Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from( + consumable_after, + )))) + } + // handle the case when the target account of the transaction is receiver + } else if target_account_id == receiver_account_id { + // For the receiver, the current block height needs to have reached only the + // timelock height to be consumable: we can ignore the reclaim height in this + // case + if current_block_height >= timelock_height { + Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization)) + } else { + Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from( + timelock_height, + )))) + } + // if the target account is neither the sender nor the receiver (from the note's + // inputs), then this account cannot consume the note + } else { + Ok(Some(NoteConsumptionStatus::NeverConsumable( + "target account of the transaction does not match neither the receiver account specified by the P2IDE inputs, nor the sender account".into() + ))) + } + }, + + // the consumption status of any other note cannot be determined by the static analysis, + // further checks are necessary. + _ => Ok(None), + } + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Returns the receiver account ID parsed from the provided P2ID note inputs. +/// +/// # Errors +/// +/// Returns an error if: +/// - the length of the provided note inputs array is not equal to the expected inputs number of the +/// P2ID note. +/// - first two elements of the note inputs array does not form the valid account ID. +fn parse_p2id_inputs(note_inputs: &[Felt]) -> Result { + if note_inputs.len() != WellKnownNote::P2ID.num_expected_inputs() { + return Err(StaticAnalysisError::new(format!( + "P2ID note should have {} inputs, but {} was provided", + WellKnownNote::P2ID.num_expected_inputs(), + note_inputs.len() + ))); + } + + try_read_account_id_from_inputs(note_inputs) +} + +/// Returns the receiver account ID, reclaim height and timelock height parsed from the provided +/// P2IDE note inputs. +/// +/// # Errors +/// +/// Returns an error if: +/// - the length of the provided note inputs array is not equal to the expected inputs number of the +/// P2IDE note. +/// - first two elements of the note inputs array does not form the valid account ID. +/// - third note inputs array element (reclaim height) is not a valid u32 value. +/// - fourth note inputs array element (timelock height) is not a valid u32 value. +fn parse_p2ide_inputs(note_inputs: &[Felt]) -> Result<(AccountId, u32, u32), StaticAnalysisError> { + if note_inputs.len() != WellKnownNote::P2IDE.num_expected_inputs() { + return Err(StaticAnalysisError::new(format!( + "P2IDE note should have {} inputs, but {} was provided", + WellKnownNote::P2IDE.num_expected_inputs(), + note_inputs.len() + ))); + } + + let receiver_account_id = try_read_account_id_from_inputs(note_inputs)?; + + let reclaim_height = u32::try_from(note_inputs[2]) + .map_err(|_err| StaticAnalysisError::new("reclaim block height should be a u32"))?; + + let timelock_height = u32::try_from(note_inputs[3]) + .map_err(|_err| StaticAnalysisError::new("timelock block height should be a u32"))?; + + Ok((receiver_account_id, reclaim_height, timelock_height)) +} + +/// Reads the account ID from the first two note input values. +/// +/// Returns None if the note input values used to construct the account ID are invalid. +fn try_read_account_id_from_inputs(note_inputs: &[Felt]) -> Result { + if note_inputs.len() < 2 { + return Err(StaticAnalysisError::new(format!( + "P2ID and P2IDE notes should have at least 2 note inputs, but {} was provided", + note_inputs.len() + ))); + } + + AccountId::try_from([note_inputs[1], note_inputs[0]]).map_err(|source| { + StaticAnalysisError::with_source( + "failed to create an account ID from the first two note inputs", + source, + ) + }) +} + +// HELPER STRUCTURES +// ================================================================================================ + +/// Describes if a note could be consumed under a specific conditions: target account state +/// and block height. +/// +/// The status does not account for any authorization that may be required to consume the +/// note, nor does it indicate whether the account has sufficient fees to consume it. +#[derive(Debug)] +pub enum NoteConsumptionStatus { + /// The note can be consumed by the account at the specified block height. + Consumable, + /// The note can be consumed by the account after the required block height is achieved. + ConsumableAfter(BlockNumber), + /// The note can be consumed by the account if proper authorization is provided. + ConsumableWithAuthorization, + /// The note cannot be consumed by the account at the specified conditions (i.e., block + /// height and account state). + UnconsumableConditions, + /// The note cannot be consumed by the specified account under any conditions. + NeverConsumable(Box), +} + +#[derive(thiserror::Error, Debug)] +#[error("{message}")] +struct StaticAnalysisError { + /// Stack size of `Box` is smaller than String. + message: Box, + /// thiserror will return this when calling Error::source on StaticAnalysisError. + source: Option>, +} + +impl StaticAnalysisError { + /// Creates a new static analysis error from an error message. + pub fn new(message: impl Into) -> Self { + let message: String = message.into(); + Self { message: message.into(), source: None } + } + + /// Creates a new static analysis error from an error message and a source error. + pub fn with_source( + message: impl Into, + source: impl Error + Send + Sync + 'static, + ) -> Self { + let message: String = message.into(); + Self { + message: message.into(), + source: Some(Box::new(source)), + } + } } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index 968065e837..133f437676 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -1,20 +1,32 @@ +use alloc::string::{String, ToString}; use alloc::vec::Vec; use assert_matches::assert_matches; -use miden_lib::note::{create_p2id_note, create_p2ide_note}; +use miden_lib::note::{NoteConsumptionStatus, WellKnownNote, create_p2id_note, create_p2ide_note}; use miden_lib::testing::mock_account::MockAccountExt; use miden_lib::testing::note::NoteBuilder; use miden_lib::transaction::TransactionKernel; -use miden_objects::Word; use miden_objects::account::{Account, AccountId}; use miden_objects::asset::{Asset, FungibleAsset}; -use miden_objects::note::{Note, NoteType}; +use miden_objects::crypto::rand::FeltRng; +use miden_objects::note::{ + Note, + NoteAssets, + NoteExecutionHint, + NoteInputs, + NoteMetadata, + NoteRecipient, + NoteTag, + NoteType, +}; use miden_objects::testing::account_id::{ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_SENDER, }; use miden_objects::transaction::{InputNote, OutputNote}; +use miden_objects::{Felt, StarkField, Word, ZERO}; use miden_processor::ExecutionError; use miden_processor::crypto::RpoRandomCoin; use miden_tx::auth::UnreachableAuth; @@ -22,7 +34,6 @@ use miden_tx::{ FailedNote, NoteConsumptionChecker, NoteConsumptionInfo, - NoteConsumptionStatus, TransactionExecutor, TransactionExecutorError, }; @@ -439,7 +450,349 @@ async fn test_check_note_consumability_without_signatures() -> anyhow::Result<() ) .await?; - assert_eq!(consumability_info, NoteConsumptionStatus::ConsumableWithAuthorization); + assert_matches!(consumability_info, NoteConsumptionStatus::ConsumableWithAuthorization); Ok(()) } + +#[tokio::test] +async fn test_check_note_consumability_static_analysis_invalid_inputs() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let account = builder.add_existing_wallet(Auth::Noop)?; + let target_account_id = account.id(); + let sender_account_id = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(); + let wrong_target_id: AccountId = + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2.try_into().unwrap(); + + // create notes for testing + // -------------------------------------------------------------------------------------------- + let p2ide_wrong_inputs_number = create_p2ide_note_with_inputs([1, 2, 3], sender_account_id); + + let p2ide_invalid_target_id = create_p2ide_note_with_inputs([1, 2, 3, 4], sender_account_id); + + let p2ide_wrong_target = create_p2ide_note_with_inputs( + [wrong_target_id.suffix().as_int(), wrong_target_id.prefix().as_u64(), 3, 4], + sender_account_id, + ); + + let p2ide_invalid_reclaim = create_p2ide_note_with_inputs( + [ + target_account_id.suffix().as_int(), + target_account_id.prefix().as_u64(), + Felt::MODULUS - 1, + 4, + ], + sender_account_id, + ); + + let p2ide_invalid_timelock = create_p2ide_note_with_inputs( + [ + target_account_id.suffix().as_int(), + target_account_id.prefix().as_u64(), + 3, + Felt::MODULUS - 1, + ], + sender_account_id, + ); + + // finalize mock chain and create notes checker + // -------------------------------------------------------------------------------------------- + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + let tx_context = mock_chain + .build_tx_context( + TxContextInput::Account(account), + &[], + &vec![ + p2ide_wrong_inputs_number.clone(), + p2ide_invalid_target_id.clone(), + p2ide_invalid_reclaim.clone(), + p2ide_invalid_timelock.clone(), + ], + )? + .build()?; + + let block_ref = tx_context.tx_inputs().block_header().block_num(); + let tx_args = tx_context.tx_args(); + let executor = + TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context).with_tracing(); + let notes_checker = NoteConsumptionChecker::new(&executor); + + // check the note with invalid number of inputs + // -------------------------------------------------------------------------------------------- + let consumability_info: NoteConsumptionStatus = notes_checker + .can_consume( + target_account_id, + block_ref, + InputNote::Unauthenticated { note: p2ide_wrong_inputs_number.clone() }, + tx_args.clone(), + ) + .await?; + assert_matches!(consumability_info, NoteConsumptionStatus::NeverConsumable(reason) => { + assert_eq!(reason.to_string(), format!( + "P2IDE note should have {} inputs, but {} was provided", + WellKnownNote::P2IDE.num_expected_inputs(), + p2ide_wrong_inputs_number.recipient().inputs().num_values() + )); + }); + + // check the note with invalid target account ID + // -------------------------------------------------------------------------------------------- + let consumability_info: NoteConsumptionStatus = notes_checker + .can_consume( + target_account_id, + block_ref, + InputNote::Unauthenticated { note: p2ide_invalid_target_id.clone() }, + tx_args.clone(), + ) + .await?; + assert_matches!(consumability_info, NoteConsumptionStatus::NeverConsumable(reason) => { + assert_eq!(reason.to_string(), "failed to create an account ID from the first two note inputs"); + }); + + // check the note with a wrong target account ID (target is neither the sender nor the receiver) + // -------------------------------------------------------------------------------------------- + let consumability_info: NoteConsumptionStatus = notes_checker + .can_consume( + target_account_id, + block_ref, + InputNote::Unauthenticated { note: p2ide_wrong_target.clone() }, + tx_args.clone(), + ) + .await?; + assert_matches!(consumability_info, NoteConsumptionStatus::NeverConsumable(reason) => { + assert_eq!(reason.to_string(), "target account of the transaction does not match neither the receiver account specified by the P2IDE inputs, nor the sender account"); + }); + + // check the note with an invalid reclaim height + // -------------------------------------------------------------------------------------------- + let consumability_info: NoteConsumptionStatus = notes_checker + .can_consume( + target_account_id, + block_ref, + InputNote::Unauthenticated { note: p2ide_invalid_reclaim.clone() }, + tx_args.clone(), + ) + .await?; + assert_matches!(consumability_info, NoteConsumptionStatus::NeverConsumable(reason) => { + assert_eq!(reason.to_string(), "reclaim block height should be a u32"); + }); + + // check the note with an invalid timelock height + // -------------------------------------------------------------------------------------------- + let consumability_info: NoteConsumptionStatus = notes_checker + .can_consume( + target_account_id, + block_ref, + InputNote::Unauthenticated { note: p2ide_invalid_timelock.clone() }, + tx_args.clone(), + ) + .await?; + assert_matches!(consumability_info, NoteConsumptionStatus::NeverConsumable(reason) => { + assert_eq!(reason.to_string(), "timelock block height should be a u32"); + }); + + Ok(()) +} + +/// Tests the correctness of the [`NoteConsumptionChecker::can_consume()`]. +/// +/// In this test the target account is the receiver. +/// +/// It is expected that the current block height is 3. +#[rstest::rstest] +// rc == tl == curr +#[case(3, 3, String::from("Ok(ConsumableWithAuthorization)"))] +// rc < tl < curr +#[case(1, 2, String::from("Ok(ConsumableWithAuthorization)"))] +// rc < tl = curr +#[case(1, 3, String::from("Ok(ConsumableWithAuthorization)"))] +// rc = tl < curr +#[case(1, 1, String::from("Ok(ConsumableWithAuthorization)"))] +// tl < rc < curr +#[case(2, 1, String::from("Ok(ConsumableWithAuthorization)"))] +// tl < rc = curr +#[case(3, 1, String::from("Ok(ConsumableWithAuthorization)"))] +// curr < rc < tl +#[case(4, 5, String::from("Ok(ConsumableAfter(BlockNumber(5)))"))] +// curr < rc = tl +#[case(4, 4, String::from("Ok(ConsumableAfter(BlockNumber(4)))"))] +// curr = rc < tl +#[case(3, 4, String::from("Ok(ConsumableAfter(BlockNumber(4)))"))] +// rc < curr < tl +#[case(2, 4, String::from("Ok(ConsumableAfter(BlockNumber(4)))"))] +// rc < curr = tl +#[case(2, 3, String::from("Ok(ConsumableWithAuthorization)"))] +// curr < tl < rc +#[case(5, 4, String::from("Ok(ConsumableAfter(BlockNumber(4)))"))] +// curr = tl < rc +#[case(4, 3, String::from("Ok(ConsumableWithAuthorization)"))] +// tl < curr < rc +#[case(4, 2, String::from("Ok(ConsumableWithAuthorization)"))] +// tl < curr = rc +#[case(3, 2, String::from("Ok(ConsumableWithAuthorization)"))] +#[tokio::test] +async fn test_check_note_consumability_static_analysis_receiver( + #[case] reclaim_height: u64, + #[case] timelock_height: u64, + #[case] expected: String, +) -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let account = builder.add_existing_wallet(Auth::Noop)?; + let target_account_id = account.id(); + let sender_account_id = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(); + + let p2ide = create_p2ide_note_with_inputs( + [ + target_account_id.suffix().as_int(), + target_account_id.prefix().as_u64(), + reclaim_height, + timelock_height, + ], + sender_account_id, + ); + builder.add_output_note(OutputNote::Full(p2ide.clone())); + + let mut mock_chain = builder.build()?; + mock_chain.prove_until_block(3)?; + + let tx_context = mock_chain + .build_tx_context(TxContextInput::Account(account), &[p2ide.id()], &[])? + .build()?; + + let block_ref = tx_context.tx_inputs().block_header().block_num(); + let tx_args = tx_context.tx_args(); + + let executor = + TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context).with_tracing(); + let notes_checker = NoteConsumptionChecker::new(&executor); + + // check the note with invalid number of inputs + // -------------------------------------------------------------------------------------------- + let consumption_check_result = notes_checker + .can_consume( + target_account_id, + block_ref, + InputNote::Unauthenticated { note: p2ide }, + tx_args.clone(), + ) + .await; + + assert_eq!(format!("{:?}", consumption_check_result), expected); + + Ok(()) +} + +/// Tests the correctness of the [`NoteConsumptionChecker::can_consume()`] procedure. +/// +/// In this test the target account is the sender. +/// +/// It is expected that the current block height is 3. +#[rstest::rstest] +// rc == tl == curr +#[case(3, 3, String::from("Ok(ConsumableWithAuthorization)"))] +// rc < tl < curr +#[case(1, 2, String::from("Ok(ConsumableWithAuthorization)"))] +// rc < tl = curr +#[case(1, 3, String::from("Ok(ConsumableWithAuthorization)"))] +// rc = tl < curr +#[case(1, 1, String::from("Ok(ConsumableWithAuthorization)"))] +// tl < rc < curr +#[case(2, 1, String::from("Ok(ConsumableWithAuthorization)"))] +// tl < rc = curr +#[case(3, 1, String::from("Ok(ConsumableWithAuthorization)"))] +// curr < rc < tl +#[case(4, 5, String::from("Ok(ConsumableAfter(BlockNumber(5)))"))] +// curr < rc = tl +#[case(4, 4, String::from("Ok(ConsumableAfter(BlockNumber(4)))"))] +// curr = rc < tl +#[case(3, 4, String::from("Ok(ConsumableAfter(BlockNumber(4)))"))] +// rc < curr < tl +#[case(2, 4, String::from("Ok(ConsumableAfter(BlockNumber(4)))"))] +// rc < curr = tl +#[case(2, 3, String::from("Ok(ConsumableWithAuthorization)"))] +// curr < tl < rc +#[case(5, 4, String::from("Ok(ConsumableAfter(BlockNumber(5)))"))] +// curr = tl < rc +#[case(4, 3, String::from("Ok(ConsumableAfter(BlockNumber(4)))"))] +// tl < curr < rc +#[case(4, 2, String::from("Ok(ConsumableAfter(BlockNumber(4)))"))] +// tl < curr = rc +#[case(3, 2, String::from("Ok(ConsumableWithAuthorization)"))] +#[tokio::test] +async fn test_check_note_consumability_static_analysis_sender( + #[case] reclaim_height: u64, + #[case] timelock_height: u64, + #[case] expected: String, +) -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let account = builder.add_existing_wallet(Auth::Noop)?; + let sender_account_id = account.id(); + let target_account_id: AccountId = + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(); + + let p2ide = create_p2ide_note_with_inputs( + [ + target_account_id.suffix().as_int(), + target_account_id.prefix().as_u64(), + reclaim_height, + timelock_height, + ], + sender_account_id, + ); + builder.add_output_note(OutputNote::Full(p2ide.clone())); + + let mut mock_chain = builder.build()?; + mock_chain.prove_until_block(3)?; + + let tx_context = mock_chain + .build_tx_context(TxContextInput::Account(account), &[p2ide.id()], &[])? + .build()?; + + let block_ref = tx_context.tx_inputs().block_header().block_num(); + let tx_args = tx_context.tx_args(); + + let executor = + TransactionExecutor::<'_, '_, _, UnreachableAuth>::new(&tx_context).with_tracing(); + let notes_checker = NoteConsumptionChecker::new(&executor); + + // check the note with invalid number of inputs + // -------------------------------------------------------------------------------------------- + let consumption_check_result = notes_checker + .can_consume( + sender_account_id, + block_ref, + InputNote::Unauthenticated { note: p2ide }, + tx_args.clone(), + ) + .await; + + assert_eq!(format!("{:?}", consumption_check_result), expected); + + Ok(()) +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Creates a mock P2IDE note with the specified note inputs. +fn create_p2ide_note_with_inputs(inputs: impl IntoIterator, sender: AccountId) -> Note { + let serial_num = RpoRandomCoin::new(Default::default()).draw_word(); + let note_script = WellKnownNote::P2IDE.script(); + let recipient = NoteRecipient::new( + serial_num, + note_script, + NoteInputs::new(inputs.into_iter().map(Felt::new).collect()).unwrap(), + ); + + let tag = NoteTag::from_account_id(sender); + let metadata = + NoteMetadata::new(sender, NoteType::Public, tag, NoteExecutionHint::always(), ZERO) + .unwrap(); + + Note::new(NoteAssets::default(), metadata, recipient) +} diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index b22c39bf8b..0b8dab35d3 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -1,4 +1,3 @@ -use assert_matches::assert_matches; use miden_lib::account::components::rpo_falcon_512_multisig_library; use miden_lib::account::interface::AccountInterface; use miden_lib::account::wallets::BasicWallet; @@ -21,20 +20,15 @@ use miden_objects::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, }; -use miden_objects::transaction::{InputNote, OutputNote}; +use miden_objects::transaction::OutputNote; use miden_objects::vm::AdviceMap; use miden_objects::{Felt, Hasher, Word}; use miden_processor::AdviceInputs; use miden_processor::crypto::RpoRandomCoin; use miden_testing::utils::create_spawn_note; use miden_testing::{Auth, MockChainBuilder, assert_transaction_executor_error}; +use miden_tx::TransactionExecutorError; use miden_tx::auth::{BasicAuthenticator, SigningInputs, TransactionAuthenticator}; -use miden_tx::{ - NoteConsumptionChecker, - NoteConsumptionStatus, - TransactionExecutor, - TransactionExecutorError, -}; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; @@ -916,148 +910,6 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu Ok(()) } -/// Checks note consumability for authenticated and unauthenticated notes, both -/// without and with multisig signatures. -/// -/// Cases covered: -/// - Without signatures: both the authenticated note and the unauthenticated note are reported as -/// `ConsumableWithAuthorization` — the notes are consumable in principle, but fail due to missing -/// multisig authorization. -/// - With valid multisig signatures on an authenticated transaction: the authenticated note becomes -/// `Consumable`, while the unauthenticated variant remains `ConsumableWithAuthorization` because -/// signatures are bound to a different `TransactionSummary` (the authenticated context) and do -/// not authorize the unauthenticated note. -#[tokio::test] -async fn test_check_note_consumability_multisig() -> anyhow::Result<()> { - // Setup keys and authenticators - let (_secret_keys, public_keys, authenticators) = setup_keys_and_authenticators(2, 2)?; - - // Create multisig account - let multisig_account = create_multisig_account(2, &public_keys, 10, vec![])?; - - let mut mock_chain_builder = - MockChainBuilder::with_accounts([multisig_account.clone()]).unwrap(); - - let p2id_note = mock_chain_builder.add_p2id_note( - ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE.try_into().unwrap(), - multisig_account.id(), - &[FungibleAsset::mock(1)], - NoteType::Public, - )?; - let mock_chain = mock_chain_builder.build().unwrap(); - - let salt = Word::from([Felt::new(1); 4]); - - // get the transaction context without signatures - let tx_context_without_signatures = mock_chain - .build_tx_context(multisig_account.id(), &[p2id_note.id()], &[])? - .auth_args(salt) - .build()?; - - let block_ref = tx_context_without_signatures.tx_inputs().block_header().block_num(); - let tx_args = tx_context_without_signatures.tx_args(); - let tx_executor = TransactionExecutor::<'_, '_, _, BasicAuthenticator>::new( - &tx_context_without_signatures, - ); - - let notes_checker = NoteConsumptionChecker::new(&tx_executor); - - let note_authenticated = - tx_context_without_signatures.tx_inputs().input_notes().get_note(0).clone(); - assert_matches!(note_authenticated, InputNote::Authenticated { .. }); - - // this check should return `ConsumableWithAuthorization` variant: the note is consumable, but - // authentication is failing - let consumable_with_authorization = notes_checker - .can_consume(multisig_account.id(), block_ref, note_authenticated.clone(), tx_args.clone()) - .await?; - assert_matches!( - consumable_with_authorization, - NoteConsumptionStatus::ConsumableWithAuthorization - ); - // trying to consume the same note but as Unauthenticated should still return - // `ConsumableWithAuthorization` variant. - let consumable_with_authorization = notes_checker - .can_consume( - multisig_account.id(), - block_ref, - miden_objects::transaction::InputNote::Unauthenticated { - note: note_authenticated.note().clone(), - }, - tx_args.clone(), - ) - .await?; - assert_matches!( - consumable_with_authorization, - NoteConsumptionStatus::ConsumableWithAuthorization - ); - - // execute the transaction to get the summary - let tx_summary = match tx_context_without_signatures.execute().await.unwrap_err() { - TransactionExecutorError::Unauthorized(tx_effects) => tx_effects, - error => panic!("expected abort with tx effects: {error:?}"), - }; - // Get signatures from both approvers - let msg = tx_summary.as_ref().to_commitment(); - let tx_summary = SigningInputs::TransactionSummary(tx_summary); - - let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment().into(), &tx_summary) - .await?; - let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment().into(), &tx_summary) - .await?; - - // get the transaction context with signatures - let tx_context_with_signatures = mock_chain - .build_tx_context(multisig_account.id(), &[p2id_note.id()], &[])? - .extend_expected_output_notes(vec![OutputNote::Full(p2id_note)]) - .add_signature(public_keys[0].clone().into(), msg, sig_1) - .add_signature(public_keys[1].clone().into(), msg, sig_2) - .auth_args(salt) - .build()?; - - let block_num = tx_context_with_signatures.tx_inputs().block_header().block_num(); - let notes = tx_context_with_signatures.tx_inputs().input_notes().clone(); - let tx_args = tx_context_with_signatures.tx_args().clone(); - - let mut tx_executor = TransactionExecutor::new(&tx_context_with_signatures) - .with_source_manager(tx_context_with_signatures.source_manager()); - if let Some(authenticator) = tx_context_with_signatures.authenticator() { - tx_executor = tx_executor.with_authenticator(authenticator); - } - - let notes_checker = NoteConsumptionChecker::new(&tx_executor); - - // this check should return `Consumable` variant: we provided the signatures, so the transaction - // should execute successfully. - let note_authenticated = notes.get_note(0).clone(); - assert_matches!(note_authenticated, InputNote::Authenticated { .. }); - - let consumable_with_authorization = notes_checker - .can_consume(multisig_account.id(), block_num, note_authenticated.clone(), tx_args.clone()) - .await?; - assert_matches!(consumable_with_authorization, NoteConsumptionStatus::Consumable); - - // trying to consume the same note but as Unauthenticated should return - // `ConsumableWithAuthorization` variant, since the signatures are provided on a different - // `TransactionSummary` (on a transaction with authenticated note) - let consumable_with_authorization = notes_checker - .can_consume( - multisig_account.id(), - block_num, - InputNote::Unauthenticated { note: note_authenticated.note().clone() }, - tx_args, - ) - .await?; - assert_matches!( - consumable_with_authorization, - NoteConsumptionStatus::ConsumableWithAuthorization - ); - - Ok(()) -} - /// Tests that 1-of-2 approvers can consume a note but 2-of-2 are required to send a note. /// /// This test verifies that a multisig account with 2 approvers and threshold 2, but a procedure diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index cf657676a6..86e8d73fec 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use miden_lib::account::faucets::{BasicFungibleFaucet, FungibleFaucetExt, NetworkFungibleFaucet}; use miden_lib::errors::tx_kernel_errors::ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED; -use miden_lib::note::well_known_note::WellKnownNote; +use miden_lib::note::WellKnownNote; use miden_lib::testing::note::NoteBuilder; use miden_lib::utils::ScriptBuilder; use miden_objects::account::{ diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index cdf5d0711c..7c066ad254 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -38,7 +38,6 @@ pub use notes_checker::{ MAX_NUM_CHECKER_NOTES, NoteConsumptionChecker, NoteConsumptionInfo, - NoteConsumptionStatus, }; // TRANSACTION EXECUTOR diff --git a/crates/miden-tx/src/executor/notes_checker.rs b/crates/miden-tx/src/executor/notes_checker.rs index 37118588f5..eaebc8048f 100644 --- a/crates/miden-tx/src/executor/notes_checker.rs +++ b/crates/miden-tx/src/executor/notes_checker.rs @@ -1,7 +1,7 @@ use alloc::collections::BTreeMap; use alloc::vec::Vec; -use miden_lib::note::well_known_note::WellKnownNote; +use miden_lib::note::{NoteConsumptionStatus, WellKnownNote}; use miden_lib::transaction::TransactionKernel; use miden_objects::account::AccountId; use miden_objects::block::BlockNumber; @@ -147,7 +147,13 @@ where note: InputNote, tx_args: TransactionArgs, ) -> Result { - // TODO: apply the static analysis before executing the tx + // return the consumption status if we manage to determine it from the well-known note + if let Some(well_known_note) = WellKnownNote::from_note(note.note()) + && let Some(consumption_status) = + well_known_note.is_consumable(note.note(), target_account_id, block_ref) + { + return Ok(consumption_status); + } // Prepare transaction inputs. let mut tx_inputs = self @@ -177,7 +183,7 @@ where }, // execution failed during the note processing TransactionCheckerError::NoteExecution { .. } => { - Ok(NoteConsumptionStatus::Unconsumable) + Ok(NoteConsumptionStatus::UnconsumableConditions) }, // execution failed during the epilogue TransactionCheckerError::EpilogueExecution(epilogue_error) => { @@ -391,29 +397,6 @@ fn handle_epilogue_error(epilogue_error: TransactionExecutorError) -> NoteConsum NoteConsumptionStatus::ConsumableWithAuthorization }, // TODO: apply additional checks to get the verbose error reason - _ => NoteConsumptionStatus::Unconsumable, + _ => NoteConsumptionStatus::UnconsumableConditions, } } - -// HELPER STRUCTURES -// ================================================================================================ - -/// Describes if a note could be consumed under a specific conditions: target account state -/// and block height. -/// -/// The status does not account for any authorization that may be required to consume the -/// note, nor does it indicate whether the account has sufficient fees to consume it. -#[derive(Debug, PartialEq)] -pub enum NoteConsumptionStatus { - /// The note can be consumed by the account at the specified block height. - Consumable, - /// The note can be consumed by the account after the required block height is achieved. - ConsumableAfter(BlockNumber), - /// The note can be consumed by the account if proper authorization is provided. - ConsumableWithAuthorization, - /// The note cannot be consumed by the account at the specified conditions (i.e., block - /// height and account state). - Unconsumable, - /// The note cannot be consumed by the specified account under any conditions. - Incompatible, -} diff --git a/crates/miden-tx/src/lib.rs b/crates/miden-tx/src/lib.rs index a706897e39..8b3f225574 100644 --- a/crates/miden-tx/src/lib.rs +++ b/crates/miden-tx/src/lib.rs @@ -15,7 +15,6 @@ pub use executor::{ MastForestStore, NoteConsumptionChecker, NoteConsumptionInfo, - NoteConsumptionStatus, TransactionExecutor, TransactionExecutorHost, }; From 7b4f9bddbe703b3543d884cf25ca1374059ece56 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Mon, 3 Nov 2025 15:00:55 -0800 Subject: [PATCH 122/133] chore: minor comment fix --- crates/miden-lib/src/note/well_known_note.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/miden-lib/src/note/well_known_note.rs b/crates/miden-lib/src/note/well_known_note.rs index f8055428f7..0a0b200217 100644 --- a/crates/miden-lib/src/note/well_known_note.rs +++ b/crates/miden-lib/src/note/well_known_note.rs @@ -269,7 +269,6 @@ impl WellKnownNote { /// - check that the target account is either the receiver account or the sender account. /// - check that depending on whether the target account is sender or receiver, it could be /// either consumed, or consumed after timelock height, or consumed after reclaim height. - /// ``` fn is_consumable_inner( &self, note: &Note, From db004bd7bde91874f4d6d9189d790b2edaa7b551 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 4 Nov 2025 08:02:11 +0800 Subject: [PATCH 123/133] feat: Refactor `Address` to make routing parameters optional (#2032) * feat: Implement `RoutingParameters` * feat: Refactor `Address` and introduce `AddressId` * feat: Fail on trailing separator * chore: Generalize routing params error to `AddressError::DecodeError` * feat: Make Routing params optional and decouple tag len & if * chore: Test address encoding with/without routing params * feat: Add and update docs * chore: Remove `AddressInterface::Unspecified` * chore: add changelog * feat: Encode `AddressType::AccountId` as `232` * chore: Replace account ID with address ID * chore: Move default note tag len logic to `AddressId` * chore: Add docs to `RoutingParameters` * chore: Update docs with new account ID prefix `mm1a` * Update crates/miden-objects/src/address/mod.rs Co-authored-by: Marti * chore: Format and improve `Address` docs --------- Co-authored-by: Marti --- CHANGELOG.md | 1 + .../src/account/account_id/address_type.rs | 0 .../src/account/account_id/mod.rs | 10 +- .../src/account/account_id/v0/mod.rs | 18 +- .../miden-objects/src/address/address_id.rs | 117 ++++ crates/miden-objects/src/address/interface.rs | 7 +- crates/miden-objects/src/address/mod.rs | 571 +++++++----------- .../src/address/routing_parameters.rs | 301 +++++++++ crates/miden-objects/src/address/type.rs | 10 +- crates/miden-objects/src/errors.rs | 39 +- docs/src/account/id.md | 4 +- 11 files changed, 725 insertions(+), 353 deletions(-) delete mode 100644 crates/miden-objects/src/account/account_id/address_type.rs create mode 100644 crates/miden-objects/src/address/address_id.rs create mode 100644 crates/miden-objects/src/address/routing_parameters.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2867f591ce..b0a22856cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ - [BREAKING] Added `get_note_script()` method to `DataStore` trait to enable lazy loading of note scripts during transaction execution ([#1995](https://github.com/0xMiden/miden-base/pull/1995)). - [BREAKING] Separate account APIs in `miden::account` into `active_account` and `native_account` ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). - [BREAKING] Remove `miden::account::get_native_nonce` procedure ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). +- [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032)). ### Changes diff --git a/crates/miden-objects/src/account/account_id/address_type.rs b/crates/miden-objects/src/account/account_id/address_type.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/crates/miden-objects/src/account/account_id/mod.rs b/crates/miden-objects/src/account/account_id/mod.rs index 51aa5bd849..2c94bb9850 100644 --- a/crates/miden-objects/src/account/account_id/mod.rs +++ b/crates/miden-objects/src/account/account_id/mod.rs @@ -16,6 +16,7 @@ mod id_version; use alloc::string::{String, ToString}; use core::fmt; +use bech32::primitives::decode::ByteIter; pub use id_version::AccountIdVersion; use miden_core::Felt; use miden_core::utils::{ByteReader, Deserializable, Serializable}; @@ -290,8 +291,8 @@ impl AccountId { /// This is an example of an account ID in hex and bech32 representations: /// /// ```text - /// hex: 0xd7585ada5ab5d2b01c77fad88c0ae4 - /// bech32: mm1qrt4skk6t26a9vquwlad3rq2usul8fy2 + /// hex: 0x6d449e4034fadca075d1976fef7e38 + /// bech32: mm1apk5f8jqxnadegr46xtklmm78qhdgkwc /// ``` /// /// ## Rationale @@ -316,6 +317,11 @@ impl AccountId { .map(|(network_id, account_id)| (network_id, AccountId::V0(account_id))) } + /// Decodes the data from the bech32 byte iterator into an [`AccountId`]. + pub(crate) fn from_bech32_byte_iter(byte_iter: ByteIter<'_>) -> Result { + AccountIdV0::from_bech32_byte_iter(byte_iter).map(AccountId::V0) + } + /// Returns the [`AccountIdPrefix`] of this ID. /// /// The prefix of an account ID is guaranteed to be unique. diff --git a/crates/miden-objects/src/account/account_id/v0/mod.rs b/crates/miden-objects/src/account/account_id/v0/mod.rs index c19ac7c6c5..c14095819d 100644 --- a/crates/miden-objects/src/account/account_id/v0/mod.rs +++ b/crates/miden-objects/src/account/account_id/v0/mod.rs @@ -5,7 +5,7 @@ use core::fmt; use core::hash::Hash; use bech32::Bech32m; -use bech32::primitives::decode::CheckedHrpstring; +use bech32::primitives::decode::{ByteIter, CheckedHrpstring}; use miden_crypto::utils::hex_to_bytes; pub use prefix::AccountIdPrefixV0; @@ -252,6 +252,7 @@ impl AccountIdV0 { let network_id = NetworkId::from_hrp(hrp); let mut byte_iter = checked_string.byte_iter(); + // The length must be the serialized size of the account ID plus the address byte. if byte_iter.len() != Self::SERIALIZED_SIZE + 1 { return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength { @@ -267,6 +268,19 @@ impl AccountIdV0 { ))); } + Self::from_bech32_byte_iter(byte_iter).map(|account_id| (network_id, account_id)) + } + + /// Decodes the data from the bech32 byte iterator into an [`AccountId`]. + pub(crate) fn from_bech32_byte_iter(byte_iter: ByteIter<'_>) -> Result { + // The _remaining_ length of the iterator must be the serialized size of the account ID. + if byte_iter.len() != Self::SERIALIZED_SIZE { + return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength { + expected: Self::SERIALIZED_SIZE, + actual: byte_iter.len(), + })); + } + // Every byte is guaranteed to be overwritten since we've checked the length of the // iterator. let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE]; @@ -276,7 +290,7 @@ impl AccountIdV0 { let account_id = Self::try_from(id_bytes)?; - Ok((network_id, account_id)) + Ok(account_id) } /// Returns the [`AccountIdPrefixV0`] of this account ID. diff --git a/crates/miden-objects/src/address/address_id.rs b/crates/miden-objects/src/address/address_id.rs new file mode 100644 index 0000000000..2974e66ac3 --- /dev/null +++ b/crates/miden-objects/src/address/address_id.rs @@ -0,0 +1,117 @@ +use alloc::string::ToString; + +use bech32::Bech32m; +use bech32::primitives::decode::CheckedHrpstring; +use miden_processor::DeserializationError; + +use crate::AddressError; +use crate::account::{AccountId, AccountStorageMode}; +use crate::address::{AddressType, NetworkId}; +use crate::errors::Bech32Error; +use crate::note::NoteTag; +use crate::utils::serde::{ByteWriter, Deserializable, Serializable}; + +/// The identifier of an [`Address`](super::Address). +/// +/// See the address docs for more details. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AddressId { + AccountId(AccountId), +} + +impl AddressId { + /// Returns the [`AddressType`] of this ID. + pub fn address_type(&self) -> AddressType { + match self { + AddressId::AccountId(_) => AddressType::AccountId, + } + } + + /// Returns the default tag length of the ID. + /// + /// This is guaranteed to be in range `0..=30` (e.g. the maximum of + /// [`NoteTag::MAX_LOCAL_TAG_LENGTH`] and [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`]). + pub fn default_note_tag_len(&self) -> u8 { + match self { + AddressId::AccountId(id) => { + if id.storage_mode() == AccountStorageMode::Network { + NoteTag::DEFAULT_NETWORK_TAG_LENGTH + } else { + NoteTag::DEFAULT_LOCAL_TAG_LENGTH + } + }, + } + } + + /// Decodes a bech32 string into an identifier. + pub(crate) fn decode(bech32_string: &str) -> Result<(NetworkId, Self), AddressError> { + // We use CheckedHrpString with an explicit checksum algorithm so we don't allow the + // `Bech32` or `NoChecksum` algorithms. + let checked_string = CheckedHrpstring::new::(bech32_string).map_err(|source| { + // The CheckedHrpStringError does not implement core::error::Error, only + // std::error::Error, so for now we convert it to a String. Even if it will + // implement the trait in the future, we should include it as an opaque + // error since the crate does not have a stable release yet. + AddressError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into())) + })?; + + let hrp = checked_string.hrp(); + let network_id = NetworkId::from_hrp(hrp); + + let mut byte_iter = checked_string.byte_iter(); + + // We only know the expected length once we know the address type, but to get the + // address type, the length must be at least one. + let address_byte = byte_iter.next().ok_or_else(|| { + AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength { + expected: 1, + actual: byte_iter.len(), + }) + })?; + + let address_type = AddressType::try_from(address_byte)?; + + let identifier = match address_type { + AddressType::AccountId => AccountId::from_bech32_byte_iter(byte_iter) + .map_err(AddressError::AccountIdDecodeError) + .map(AddressId::AccountId)?, + }; + + Ok((network_id, identifier)) + } +} + +impl From for AddressId { + fn from(id: AccountId) -> Self { + Self::AccountId(id) + } +} + +impl Serializable for AddressId { + fn write_into(&self, target: &mut W) { + target.write_u8(self.address_type() as u8); + match self { + AddressId::AccountId(id) => { + id.write_into(target); + }, + } + } +} + +impl Deserializable for AddressId { + fn read_from( + source: &mut R, + ) -> Result { + let address_type: u8 = source.read_u8()?; + let address_type = AddressType::try_from(address_type) + .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; + + match address_type { + AddressType::AccountId => { + let id: AccountId = source.read()?; + Ok(AddressId::AccountId(id)) + }, + } + } +} diff --git a/crates/miden-objects/src/address/interface.rs b/crates/miden-objects/src/address/interface.rs index 9116f5a34b..562feb2585 100644 --- a/crates/miden-objects/src/address/interface.rs +++ b/crates/miden-objects/src/address/interface.rs @@ -19,16 +19,13 @@ use crate::AddressError; #[repr(u16)] #[non_exhaustive] pub enum AddressInterface { - /// Signals that the account interface is not specified. - Unspecified = Self::UNSPECIFIED, /// The basic wallet interface. BasicWallet = Self::BASIC_WALLET, } impl AddressInterface { // Constants for internal use only. - const UNSPECIFIED: u16 = 0; - const BASIC_WALLET: u16 = 1; + const BASIC_WALLET: u16 = 0; } impl TryFrom for AddressInterface { @@ -37,7 +34,6 @@ impl TryFrom for AddressInterface { /// Decodes an [`AddressInterface`] from its bytes representation. fn try_from(value: u16) -> Result { match value { - Self::UNSPECIFIED => Ok(Self::Unspecified), Self::BASIC_WALLET => Ok(Self::BasicWallet), other => Err(AddressError::UnknownAddressInterface(other)), } @@ -47,7 +43,6 @@ impl TryFrom for AddressInterface { impl Display for AddressInterface { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - Self::Unspecified => write!(f, "Unspecified"), Self::BasicWallet => write!(f, "BasicWallet"), } } diff --git a/crates/miden-objects/src/address/mod.rs b/crates/miden-objects/src/address/mod.rs index 190029321b..dd39555b6a 100644 --- a/crates/miden-objects/src/address/mod.rs +++ b/crates/miden-objects/src/address/mod.rs @@ -1,364 +1,224 @@ mod r#type; +use alloc::string::ToString; + +pub use r#type::AddressType; + +mod routing_parameters; +use alloc::borrow::ToOwned; + +pub use routing_parameters::RoutingParameters; mod interface; mod network_id; -use alloc::string::{String, ToString}; +use alloc::string::String; -use bech32::Bech32m; -use bech32::primitives::decode::{ByteIter, CheckedHrpstring}; pub use interface::AddressInterface; -use miden_core::utils::{ByteWriter, Deserializable, Serializable}; use miden_processor::DeserializationError; pub use network_id::{CustomNetworkId, NetworkId}; -pub use r#type::AddressType; use crate::AddressError; -use crate::account::{AccountId, AccountStorageMode}; -use crate::errors::Bech32Error; +use crate::account::AccountStorageMode; use crate::note::NoteTag; +use crate::utils::serde::{ByteWriter, Deserializable, Serializable}; + +mod address_id; +pub use address_id::AddressId; /// A user-facing address in Miden. -#[non_exhaustive] +/// +/// An address consists of an [`AddressId`] and optional [`RoutingParameters`]. +/// +/// A user who wants to receive a note creates an address and sends it to the sender of the note. +/// The sender creates a note intended for the holder of this address ID (e.g., it provides +/// discoverability and potentially access-control) and the routing parameters inform the sender +/// about various aspects like: +/// - what kind of note the receiver's account can consume. +/// - how the receiver discovers the note. +/// +/// It can be encoded to a string using [`Self::encode`] and decoded using [`Self::decode`]. +/// If routing parameters are present, the ID and parameters are separated by +/// [`Address::SEPARATOR`]. +/// +/// ## Example +/// +/// ```text +/// # account ID +/// mm1apk5f8jqxnadegr46xtklmm78qhdgkwc +/// # account ID + routing parameters +/// mm1apk5f8jqxnadegr46xtklmm78qhdgkwc_qrcqzlvsfdp +/// ``` +/// +/// The encoding of an address without routing parameters matches the encoding of the underlying +/// identifier exactly (e.g. an account ID). This provides compatibility between identifiers and +/// addresses and gives end-users a hint that an address is only an extension of the identifier +/// (e.g. their account's ID) that they are likely to recognize. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Address { - AccountId(AccountIdAddress), +pub struct Address { + id: AddressId, + routing_params: Option, } impl Address { - /// Returns a note tag derived from this address. - pub fn to_note_tag(&self) -> NoteTag { - match self { - Address::AccountId(addr) => addr.to_note_tag(), - } - } - - /// Returns the [`AddressInterface`] of the account to which the address points. - pub fn interface(&self) -> AddressInterface { - match self { - Address::AccountId(account_id_address) => account_id_address.interface(), - } - } - - /// Encodes the [`Address`] into a [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) string. - /// - /// ## Encoding - /// - /// The encoding of an address into bech32 is done as follows: - /// - Encode the underlying address to bytes. - /// - Into that data, insert the [`AddressType`] byte at index 0, shifting all other elements to - /// the right. - /// - Choose an HRP, defined as a [`NetworkId`], e.g. [`NetworkId::Mainnet`] whose string - /// representation is `mm`. - /// - Encode the resulting HRP together with the data into a bech32 string using the - /// [`bech32::Bech32m`] checksum algorithm. - /// - /// This is an example of an address in bech32 representation: - /// - /// ```text - /// mm1qpkdyek2c0ywwvzupakc7zlzty8qn2qnfc - /// ``` - /// - /// ## Rationale - /// - /// The address type is at the very beginning so that it can be decoded first to detect the type - /// of the address, without having to decode the entire data. Moreover, since the address type - /// is chosen as a multiple of 8, the first character of the bech32 string after the - /// `1` separator will be different for every address type. That makes the type of the address - /// conveniently human-readable. - /// - /// The only allowed checksum algorithm is [`Bech32m`] due to being the best available checksum - /// algorithm with no known weaknesses (unlike [`Bech32`](bech32::Bech32)). No checksum is - /// also not allowed since the intended use of bech32 is to have error - /// detection capabilities. - pub fn to_bech32(&self, network_id: NetworkId) -> String { - match self { - Address::AccountId(account_id_address) => account_id_address.to_bech32(network_id), - } - } - - /// Decodes a [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) string - /// into the [`NetworkId`] and an [`Address`]. - /// - /// See [`Address::to_bech32`] for details on the format. The procedure for decoding the bech32 - /// data into the address are the inverse operations of encoding. - pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AddressError> { - // We use CheckedHrpString with an explicit checksum algorithm so we don't allow the - // `Bech32` or `NoChecksum` algorithms. - let checked_string = CheckedHrpstring::new::(bech32_string).map_err(|source| { - // The CheckedHrpStringError does not implement core::error::Error, only - // std::error::Error, so for now we convert it to a String. Even if it will - // implement the trait in the future, we should include it as an opaque - // error since the crate does not have a stable release yet. - AddressError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into())) - })?; - - let hrp = checked_string.hrp(); - let network_id = NetworkId::from_hrp(hrp); - - let mut byte_iter = checked_string.byte_iter(); - - // We only know the expected length once we know the address type, but to get the address - // type, the length must be at least one. - let address_byte = byte_iter.next().ok_or_else(|| { - AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength { - expected: 1, - actual: byte_iter.len(), - }) - })?; - - let address_type = AddressType::try_from(address_byte)?; - - let address = match address_type { - AddressType::AccountId => { - AccountIdAddress::from_bech32_byte_iter(byte_iter).map(Address::from)? - }, - }; - - Ok((network_id, address)) - } - - /// Identifier for the internal type of address - fn address_scheme_id(&self) -> u8 { - match self { - Address::AccountId(_) => 0u8, - } - } -} - -impl Serializable for Address { - fn write_into(&self, target: &mut W) { - target.write_u8(self.address_scheme_id()); - match self { - Address::AccountId(addr) => { - let serialized: [u8; AccountIdAddress::SERIALIZED_SIZE] = (*addr).into(); - serialized.write_into(target); - }, - } - } -} - -impl Deserializable for Address { - fn read_from( - source: &mut R, - ) -> Result { - let address_scheme_id: u8 = source.read_u8()?; - match address_scheme_id { - // AccountIdAddress - 0u8 => { - let bytes: [u8; AccountIdAddress::SERIALIZED_SIZE] = source.read_array()?; - let account_id_address = AccountIdAddress::try_from(bytes) - .map_err(|err| DeserializationError::InvalidValue(format!("{}", err)))?; - Ok(Address::AccountId(account_id_address)) - }, - val => { - Err(DeserializationError::InvalidValue(format!("Invalid address scheme ID {val}"))) - }, - } - } -} - -// ACCOUNT ID ADDRESS -// ================================================================================================ - -/// An [`Address`] that targets a specific [`AccountId`] with an explicit tag length preference. -/// -/// The tag length preference determines how many bits of the account ID are encoded into -/// [`NoteTag`]s of notes targeted to this address. This lets the owner of the account choose -/// their level of privacy. A higher tag length makes the account more uniquely identifiable and -/// reduces privacy, while a shorter length increases privacy at the cost of matching more notes -/// published onchain. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct AccountIdAddress { - id: AccountId, - tag_len: u8, - interface: AddressInterface, -} - -impl AccountIdAddress { // CONSTANTS // -------------------------------------------------------------------------------------------- - /// The serialized size of an [`AccountIdAddress`] in bytes. - pub const SERIALIZED_SIZE: usize = AccountId::SERIALIZED_SIZE + 2; + /// The separator character in an encoded address between the ID and routing parameters. + pub const SEPARATOR: char = '_'; // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Creates a new account ID based address with the default tag length. + /// Returns a new address from an [`AddressId`] and routing parameters set to `None`. /// - /// The tag length defaults to [`NoteTag::DEFAULT_LOCAL_TAG_LENGTH`] for local, and - /// [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`] for network accounts. - pub fn new(id: AccountId, interface: AddressInterface) -> Self { - let tag_len = if id.storage_mode() == AccountStorageMode::Network { - NoteTag::DEFAULT_NETWORK_TAG_LENGTH - } else { - NoteTag::DEFAULT_LOCAL_TAG_LENGTH - }; - - Self { id, tag_len, interface } + /// To set routing parameters, use [`Self::with_routing_parameters`]. + pub fn new(id: impl Into) -> Self { + Self { id: id.into(), routing_params: None } } - // PUBLIC MUTATORS - // -------------------------------------------------------------------------------------------- - - /// Sets a custom tag length for the address, determining how many bits of the account ID - /// are encoded into [`NoteTag`]s. - /// /// For local (both public and private) accounts, up to 30 bits can be encoded into the tag. /// For network accounts, the tag length must be set to 30 bits. /// /// # Errors /// /// Returns an error if: - /// - The tag length exceeds [`NoteTag::MAX_LOCAL_TAG_LENGTH`] for local accounts. - /// - The tag length is not [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`] for network accounts. - pub fn with_tag_len(mut self, tag_len: u8) -> Result { - if self.id.storage_mode() == AccountStorageMode::Network { - if tag_len != NoteTag::DEFAULT_NETWORK_TAG_LENGTH { - return Err(AddressError::CustomTagLengthNotAllowedForNetworkAccounts(tag_len)); + /// - The tag length routing parameter is not [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`] for + /// network accounts. + pub fn with_routing_parameters( + mut self, + routing_params: RoutingParameters, + ) -> Result { + if let Some(tag_len) = routing_params.note_tag_len() { + match self.id { + AddressId::AccountId(account_id) => { + if account_id.storage_mode() == AccountStorageMode::Network + && tag_len != NoteTag::DEFAULT_NETWORK_TAG_LENGTH + { + return Err(AddressError::CustomTagLengthNotAllowedForNetworkAccounts( + tag_len, + )); + } + }, } - } else if tag_len > NoteTag::MAX_LOCAL_TAG_LENGTH { - return Err(AddressError::TagLengthTooLarge(tag_len)); } - self.tag_len = tag_len; + self.routing_params = Some(routing_params); + Ok(self) } - // PUBLIC ACCESSORS + // ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns the underlying account id. - pub fn id(&self) -> AccountId { + /// Returns the identifier of the address. + pub fn id(&self) -> AddressId { self.id } + /// Returns the [`AddressInterface`] of the account to which the address points. + pub fn interface(&self) -> Option { + self.routing_params.as_ref().map(RoutingParameters::interface) + } + /// Returns the preferred tag length. /// /// This is guaranteed to be in range `0..=30` (e.g. the maximum of /// [`NoteTag::MAX_LOCAL_TAG_LENGTH`] and [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`]). pub fn note_tag_len(&self) -> u8 { - self.tag_len - } - - /// Returns the [`AddressInterface`] of the account to which the address points. - pub fn interface(&self) -> AddressInterface { - self.interface + self.routing_params + .as_ref() + .and_then(RoutingParameters::note_tag_len) + .unwrap_or(self.id.default_note_tag_len()) } /// Returns a note tag derived from this address. pub fn to_note_tag(&self) -> NoteTag { - match self.id.storage_mode() { - AccountStorageMode::Network => NoteTag::from_network_account_id(self.id), - AccountStorageMode::Private | AccountStorageMode::Public => { - NoteTag::from_local_account_id(self.id, self.tag_len) - .expect("AccountIdAddress validated that tag len does not exceed MAX_LOCAL_TAG_LENGTH bits") + let note_tag_len = self.note_tag_len(); + + match self.id { + AddressId::AccountId(id) => { + match id.storage_mode() { + AccountStorageMode::Network => NoteTag::from_network_account_id(id), + AccountStorageMode::Private | AccountStorageMode::Public => { + NoteTag::from_local_account_id(id, note_tag_len) + .expect("address should validate that tag len does not exceed MAX_LOCAL_TAG_LENGTH bits") + } + } }, } } - // PRIVATE HELPERS - // ---------------------------------------------------------------------------------------- - - /// Encodes the [`AccountIdAddress`] to a bech32 string. + /// Encodes the [`Address`] into a string. /// - /// See [`Address::to_bech32`] for more details. - fn to_bech32(self, network_id: NetworkId) -> String { - let id_bytes: [u8; Self::SERIALIZED_SIZE] = self.into(); - - // Create an array that fits the encoded account ID address plus the address type byte. - let mut data = [0; Self::SERIALIZED_SIZE + 1]; - // Encode the address type into index 0. - data[0] = AddressType::AccountId as u8; - // Encode the 17 account ID address bytes into 1..18. - data[1..].copy_from_slice(&id_bytes); - - // SAFETY: Encoding panics if the total length of the hrp + data (encoded in GF(32)) + the - // separator + the checksum exceeds Bech32m::CODE_LENGTH, which is 1023. - // The total 18 bytes of data we encode result in (18 bytes * 8 bits / 5 bits per base32 - // symbol) = 29 characters. The hrp is at most 83 in length, so we are guaranteed to be - // below the limit. - bech32::encode::(network_id.into_hrp(), &data) - .expect("code length of bech32 should not be exceeded") - } - - /// Decodes the data from the bech32 byte iterator into an [`AccountIdAddress`]. + /// ## Encoding /// - /// See [`Address::from_bech32`] for details. - fn from_bech32_byte_iter(byte_iter: ByteIter<'_>) -> Result { - // The _remaining_ length of the iterator must be the serialized size of the account ID - // address. - if byte_iter.len() != Self::SERIALIZED_SIZE { - return Err(AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength { - expected: Self::SERIALIZED_SIZE, - actual: byte_iter.len(), - })); - } + /// The encoding of an address into a string is done as follows: + /// - Encode the underlying [`AddressId`] to a bech32 string. + /// - If routing parameters are present: + /// - Append the [`Address::SEPARATOR`] to that string. + /// - Append the encoded routing parameters to that string. + pub fn encode(&self, network_id: NetworkId) -> String { + let mut encoded = match self.id { + AddressId::AccountId(id) => id.to_bech32(network_id), + }; - // Every byte is guaranteed to be overwritten since we've checked the length of the - // iterator. - let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE]; - for (i, byte) in byte_iter.enumerate() { - id_bytes[i] = byte; + if let Some(routing_params) = &self.routing_params { + encoded.push(Self::SEPARATOR); + encoded.push_str(&routing_params.encode_to_string()); } - let account_id_address = Self::try_from(id_bytes)?; - - Ok(account_id_address) + encoded } -} -impl From for Address { - fn from(addr: AccountIdAddress) -> Self { - Address::AccountId(addr) - } -} + /// Decodes an address string into the [`NetworkId`] and an [`Address`]. + /// + /// See [`Address::encode`] for details on the format. The procedure for decoding the string + /// into the address are the inverse operations of encoding. + pub fn decode(address_str: &str) -> Result<(NetworkId, Self), AddressError> { + if address_str.ends_with(Self::SEPARATOR) { + return Err(AddressError::TrailingSeparator); + } -impl From for [u8; AccountIdAddress::SERIALIZED_SIZE] { - fn from(account_id_address: AccountIdAddress) -> Self { - let mut result = [0_u8; AccountIdAddress::SERIALIZED_SIZE]; + let mut split = address_str.split(Self::SEPARATOR); + let encoded_identifier = split + .next() + .ok_or_else(|| AddressError::decode_error("identifier missing in address string"))?; - // Encode the account ID into 0..15. - let encoded_account_id_address = <[u8; 15]>::from(account_id_address.id); - result[..15].copy_from_slice(&encoded_account_id_address); + let (network_id, identifier) = AddressId::decode(encoded_identifier)?; - let interface = account_id_address.interface as u16; - debug_assert_eq!( - interface >> 11, - 0, - "address interface should have its upper 5 bits unset" - ); + let mut address = Address::new(identifier); - // The interface takes up 11 bits and the tag length 5 bits, so we can merge them together. - let tag_len = (account_id_address.tag_len as u16) << 11; - let encoded = tag_len | interface; - let encoded: [u8; 2] = encoded.to_be_bytes(); + if let Some(encoded_routing_params) = split.next() { + let routing_params = RoutingParameters::decode(encoded_routing_params.to_owned())?; + address = address.with_routing_parameters(routing_params)?; + } - // Encode the interface and tag length into 15..17. - result[15] = encoded[0]; - result[16] = encoded[1]; + Ok((network_id, address)) + } +} - result +impl Serializable for Address { + fn write_into(&self, target: &mut W) { + self.id.write_into(target); + self.routing_params.write_into(target); } } -impl TryFrom<[u8; AccountIdAddress::SERIALIZED_SIZE]> for AccountIdAddress { - type Error = AddressError; +impl Deserializable for Address { + fn read_from( + source: &mut R, + ) -> Result { + let identifier: AddressId = source.read()?; + let routing_params: Option = source.read()?; - fn try_from(bytes: [u8; AccountIdAddress::SERIALIZED_SIZE]) -> Result { - let account_id_bytes: [u8; AccountId::SERIALIZED_SIZE] = bytes - [..AccountId::SERIALIZED_SIZE] - .try_into() - .expect("we should have sliced off exactly 15 bytes"); - let account_id = - AccountId::try_from(account_id_bytes).map_err(AddressError::AccountIdDecodeError)?; + let mut address = Self::new(identifier); - let interface_tag_len = u16::from_be_bytes([bytes[15], bytes[16]]); - let tag_len = (interface_tag_len >> 11) as u8; - let interface = interface_tag_len & 0b0000_0111_1111_1111; - let interface = AddressInterface::try_from(interface)?; + if let Some(routing_params) = routing_params { + address = address + .with_routing_parameters(routing_params) + .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; + } - Self::new(account_id, interface).with_tag_len(tag_len) + Ok(address) } } @@ -371,16 +231,18 @@ mod tests { use alloc::str::FromStr; use assert_matches::assert_matches; - use bech32::{Bech32, NoChecksum}; + use bech32::{Bech32, Bech32m, NoChecksum}; use super::*; - use crate::account::AccountType; + use crate::AccountIdError; + use crate::account::{AccountId, AccountType}; use crate::address::CustomNetworkId; + use crate::errors::Bech32Error; use crate::testing::account_id::{ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, AccountIdBuilder}; /// Tests that an account ID address can be encoded and decoded. #[test] - fn address_bech32_encode_decode_roundtrip() { + fn address_encode_decode_roundtrip() -> anyhow::Result<()> { // We use this to check that encoding does not panic even when using the longest possible // HRP. let longest_possible_hrp = @@ -410,59 +272,93 @@ mod tests { .into_iter() .enumerate() { - let account_id_address = - AccountIdAddress::new(account_id, AddressInterface::BasicWallet); - let address = Address::from(account_id_address); + // Encode/Decode without routing parameters should be valid. + let mut address = Address::new(account_id); + + let bech32_string = address.encode(network_id.clone()); + assert!( + !bech32_string.contains(Address::SEPARATOR), + "separator should not be present in address without routing params" + ); + let (decoded_network_id, decoded_address) = Address::decode(&bech32_string)?; + + assert_eq!(network_id, decoded_network_id, "network id failed in {idx}"); + assert_eq!(address, decoded_address, "address failed in {idx}"); + + let AddressId::AccountId(decoded_account_id) = address.id(); + assert_eq!(account_id, decoded_account_id); + + // Encode/Decode with routing parameters should be valid. + address = address.with_routing_parameters( + RoutingParameters::new(AddressInterface::BasicWallet) + .with_note_tag_len(NoteTag::DEFAULT_NETWORK_TAG_LENGTH)?, + )?; - let bech32_string = address.to_bech32(network_id.clone()); - let (decoded_network_id, decoded_address) = - Address::from_bech32(&bech32_string).unwrap(); + let bech32_string = address.encode(network_id.clone()); + assert!( + bech32_string.contains(Address::SEPARATOR), + "separator should be present in address without routing params" + ); + let (decoded_network_id, decoded_address) = Address::decode(&bech32_string)?; assert_eq!(network_id, decoded_network_id, "network id failed in {idx}"); assert_eq!(address, decoded_address, "address failed in {idx}"); - let Address::AccountId(decoded_account_id) = address; - assert_eq!(account_id, decoded_account_id.id()); - assert_eq!(account_id_address.note_tag_len(), decoded_account_id.note_tag_len()); + let AddressId::AccountId(decoded_account_id) = address.id(); + assert_eq!(account_id, decoded_account_id); } } + + Ok(()) + } + + #[test] + fn address_decoding_fails_on_trailing_separator() -> anyhow::Result<()> { + let id = AccountIdBuilder::new() + .account_type(AccountType::FungibleFaucet) + .build_with_rng(&mut rand::rng()); + + let address = Address::new(id); + let mut encoded_address = address.encode(NetworkId::Devnet); + encoded_address.push(Address::SEPARATOR); + + let err = Address::decode(&encoded_address).unwrap_err(); + assert_matches!(err, AddressError::TrailingSeparator); + + Ok(()) } /// Tests that an invalid checksum returns an error. #[test] - fn bech32_invalid_checksum() { + fn bech32_invalid_checksum() -> anyhow::Result<()> { let network_id = NetworkId::Mainnet; - let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); - let address = - Address::from(AccountIdAddress::new(account_id, AddressInterface::BasicWallet)); + let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; + let address = Address::new(account_id).with_routing_parameters( + RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(14)?, + )?; - let bech32_string = address.to_bech32(network_id); + let bech32_string = address.encode(network_id); let mut invalid_bech32_1 = bech32_string.clone(); invalid_bech32_1.remove(0); let mut invalid_bech32_2 = bech32_string.clone(); invalid_bech32_2.remove(7); - let error = Address::from_bech32(&invalid_bech32_1).unwrap_err(); + let error = Address::decode(&invalid_bech32_1).unwrap_err(); assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_))); - let error = Address::from_bech32(&invalid_bech32_2).unwrap_err(); + let error = Address::decode(&invalid_bech32_2).unwrap_err(); assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_))); + + Ok(()) } /// Tests that an unknown address type returns an error. #[test] fn bech32_unknown_address_type() { - let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); - let account_id_address = AccountIdAddress::new(account_id, AddressInterface::BasicWallet); - let mut id_address_bytes = <[u8; _]>::from(account_id_address).to_vec(); + let invalid_bech32_address = + bech32::encode::(NetworkId::Mainnet.into_hrp(), &[250]).unwrap(); - // Set invalid address type. - id_address_bytes.insert(0, 250); - - let invalid_bech32 = - bech32::encode::(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap(); - - let error = Address::from_bech32(&invalid_bech32).unwrap_err(); + let error = Address::decode(&invalid_bech32_address).unwrap_err(); assert_matches!( error, AddressError::Bech32DecodeError(Bech32Error::UnknownAddressType(250)) @@ -473,20 +369,18 @@ mod tests { #[test] fn bech32_invalid_other_checksum() { let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); - let account_id_address = AccountIdAddress::new(account_id, AddressInterface::BasicWallet); - let mut id_address_bytes = <[u8; _]>::from(account_id_address).to_vec(); - id_address_bytes.insert(0, AddressType::AccountId as u8); + let address_id_bytes = AddressId::from(account_id).to_bytes(); // Use Bech32 instead of Bech32m which is disallowed. let invalid_bech32_regular = - bech32::encode::(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap(); - let error = Address::from_bech32(&invalid_bech32_regular).unwrap_err(); + bech32::encode::(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap(); + let error = Address::decode(&invalid_bech32_regular).unwrap_err(); assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_))); // Use no checksum instead of Bech32m which is disallowed. let invalid_bech32_no_checksum = - bech32::encode::(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap(); - let error = Address::from_bech32(&invalid_bech32_no_checksum).unwrap_err(); + bech32::encode::(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap(); + let error = Address::decode(&invalid_bech32_no_checksum).unwrap_err(); assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_))); } @@ -494,25 +388,25 @@ mod tests { #[test] fn bech32_invalid_length() { let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); - let account_id_address = AccountIdAddress::new(account_id, AddressInterface::BasicWallet); - let mut id_address_bytes = <[u8; _]>::from(account_id_address).to_vec(); - id_address_bytes.insert(0, AddressType::AccountId as u8); + let mut address_id_bytes = AddressId::from(account_id).to_bytes(); // Add one byte to make the length invalid. - id_address_bytes.push(5); + address_id_bytes.push(5); let invalid_bech32 = - bech32::encode::(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap(); + bech32::encode::(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap(); - let error = Address::from_bech32(&invalid_bech32).unwrap_err(); + let error = Address::decode(&invalid_bech32).unwrap_err(); assert_matches!( error, - AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength { .. }) + AddressError::AccountIdDecodeError(AccountIdError::Bech32DecodeError( + Bech32Error::InvalidDataLength { .. } + )) ); } /// Tests that an Address can be serialized and deserialized #[test] - fn address_serialization() { + fn address_serialization() -> anyhow::Result<()> { let rng = &mut rand::rng(); for account_type in [ @@ -524,15 +418,16 @@ mod tests { .into_iter() { let account_id = AccountIdBuilder::new().account_type(account_type).build_with_rng(rng); - for account_id_address in [ - AccountIdAddress::new(account_id, AddressInterface::BasicWallet), - AccountIdAddress::new(account_id, AddressInterface::Unspecified), - ] { - let address = Address::from(account_id_address); - let serialized = address.to_bytes(); - let deserialized = Address::read_from_bytes(&serialized).unwrap(); - assert_eq!(address, deserialized); - } + let address = Address::new(account_id).with_routing_parameters( + RoutingParameters::new(AddressInterface::BasicWallet) + .with_note_tag_len(NoteTag::DEFAULT_NETWORK_TAG_LENGTH)?, + )?; + + let serialized = address.to_bytes(); + let deserialized = Address::read_from_bytes(&serialized)?; + assert_eq!(address, deserialized); } + + Ok(()) } } diff --git a/crates/miden-objects/src/address/routing_parameters.rs b/crates/miden-objects/src/address/routing_parameters.rs new file mode 100644 index 0000000000..f92ff8c755 --- /dev/null +++ b/crates/miden-objects/src/address/routing_parameters.rs @@ -0,0 +1,301 @@ +use alloc::borrow::ToOwned; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +use bech32::primitives::decode::CheckedHrpstring; +use bech32::{Bech32m, Hrp}; + +use crate::AddressError; +use crate::address::AddressInterface; +use crate::errors::Bech32Error; +use crate::note::NoteTag; +use crate::utils::serde::{ + ByteReader, + ByteWriter, + Deserializable, + DeserializationError, + Serializable, +}; +use crate::utils::sync::LazyLock; + +/// The HRP used for encoding routing parameters. +/// +/// This HRP is only used internally, but needs to be well-defined for other routing parameter +/// encode/decode implementations. +/// +/// `mrp` stands for Miden Routing Parameters. +static ROUTING_PARAMETERS_HRP: LazyLock = + LazyLock::new(|| Hrp::parse("mrp").expect("hrp should be valid")); + +/// The separator character used in bech32. +const BECH32_SEPARATOR: &str = "1"; + +/// The value to encode the absence of a note tag routing parameter (i.e. `None`). +/// +/// Note tag length is ensured to be <= [`NoteTag::MAX_LOCAL_TAG_LENGTH`] and so 1 << 5 = 32 is used +/// to encode `None`. +const ABSENT_NOTE_TAG_LEN: u8 = 1 << 5; + +/// The routing parameter key for the receiver profile. +const RECEIVER_PROFILE_KEY: u8 = 0; + +/// Parameters that define how a sender should route a note to the [`AddressId`](super::AddressId) +/// in an [`Address`](super::Address). +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RoutingParameters { + interface: AddressInterface, + note_tag_len: Option, +} + +impl RoutingParameters { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Creates new [`RoutingParameters`] from an [`AddressInterface`] and all other parameters + /// initialized to `None`. + pub fn new(interface: AddressInterface) -> Self { + Self { interface, note_tag_len: None } + } + + /// Sets the note tag length routing parameter. + /// + /// The tag length determines how many bits of the address ID are encoded into [`NoteTag`]s of + /// notes targeted to this address. This lets the receiver choose their level of privacy. A + /// higher tag length makes the address ID more uniquely identifiable and reduces privacy, + /// while a shorter length increases privacy at the cost of matching more notes + /// published onchain. + /// + /// # Errors + /// + /// Returns an error if: + /// - The tag length exceeds the maximum of [`NoteTag::MAX_LOCAL_TAG_LENGTH`] and + /// [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`]. + pub fn with_note_tag_len(mut self, note_tag_len: u8) -> Result { + if note_tag_len > NoteTag::MAX_LOCAL_TAG_LENGTH { + return Err(AddressError::TagLengthTooLarge(note_tag_len)); + } + + self.note_tag_len = Some(note_tag_len); + Ok(self) + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the note tag length preference. + /// + /// This is guaranteed to be in range `0..=30` (e.g. the maximum of + /// [`NoteTag::MAX_LOCAL_TAG_LENGTH`] and [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`]). + pub fn note_tag_len(&self) -> Option { + self.note_tag_len + } + + /// Returns the [`AddressInterface`] of the account to which the address points. + pub fn interface(&self) -> AddressInterface { + self.interface + } + + // HELPERS + // -------------------------------------------------------------------------------------------- + + /// Encodes [`RoutingParameters`] to a byte vector. + pub(crate) fn encode_to_bytes(&self) -> Vec { + let mut encoded = Vec::new(); + + let note_tag_len = self.note_tag_len.unwrap_or(ABSENT_NOTE_TAG_LEN); + + let interface = self.interface as u16; + debug_assert_eq!( + interface >> 11, + 0, + "address interface should have its upper 5 bits unset" + ); + + // The interface takes up 11 bits and the tag length 5 bits, so we can merge them + // together. + let tag_len = (note_tag_len as u16) << 11; + let receiver_profile: u16 = tag_len | interface; + let receiver_profile: [u8; 2] = receiver_profile.to_be_bytes(); + + // Append the receiver profile key and the encoded value to the vector. + encoded.push(RECEIVER_PROFILE_KEY); + encoded.extend(receiver_profile); + + encoded + } + + /// Encodes [`RoutingParameters`] to a bech32 string _without_ the leading hrp and separator. + pub(crate) fn encode_to_string(&self) -> String { + let encoded = self.encode_to_bytes(); + + let bech32_str = + bech32::encode::(*ROUTING_PARAMETERS_HRP, &encoded).expect("TODO"); + let encoded_str = bech32_str + .strip_prefix(ROUTING_PARAMETERS_HRP.as_str()) + .expect("bech32 str should start with the hrp"); + let encoded_str = encoded_str + .strip_prefix(BECH32_SEPARATOR) + .expect("encoded str should start with bech32 separator `1`"); + encoded_str.to_owned() + } + + /// Decodes [`RoutingParameters`] from a bech32 string _without_ the leading hrp and separator. + pub(crate) fn decode(mut bech32_string: String) -> Result { + // ------ Decode bech32 string into bytes ------ + + // Reinsert the expected HRP into the string that is stripped during encoding. + bech32_string.insert_str(0, BECH32_SEPARATOR); + bech32_string.insert_str(0, ROUTING_PARAMETERS_HRP.as_str()); + + // We use CheckedHrpString with an explicit checksum algorithm so we don't allow the + // `Bech32` or `NoChecksum` algorithms. + let checked_string = + CheckedHrpstring::new::(&bech32_string).map_err(|source| { + // The CheckedHrpStringError does not implement core::error::Error, only + // std::error::Error, so for now we convert it to a String. Even if it will + // implement the trait in the future, we should include it as an opaque + // error since the crate does not have a stable release yet. + AddressError::decode_error_with_source( + "failed to decode routing parameters bech32 string", + Bech32Error::DecodeError(source.to_string().into()), + ) + })?; + + Self::decode_from_bytes(checked_string.byte_iter()) + } + + /// Decodes [`RoutingParameters`] from a byte iterator. + pub(crate) fn decode_from_bytes( + mut byte_iter: impl ExactSizeIterator, + ) -> Result { + let mut interface = None; + let mut note_tag_len = None; + + while let Some(key) = byte_iter.next() { + match key { + RECEIVER_PROFILE_KEY => { + if byte_iter.len() < 2 { + return Err(AddressError::decode_error( + "expected two bytes to decode receiver profile", + )); + }; + + let byte0 = byte_iter.next().expect("byte0 should exist"); + let byte1 = byte_iter.next().expect("byte1 should exist"); + let receiver_profile = u16::from_be_bytes([byte0, byte1]); + + let tag_len = (receiver_profile >> 11) as u8; + note_tag_len = if tag_len == ABSENT_NOTE_TAG_LEN { + None + } else { + Some(tag_len) + }; + + let addr_interface = receiver_profile & 0b0000_0111_1111_1111; + let addr_interface = + AddressInterface::try_from(addr_interface).map_err(|err| { + AddressError::decode_error_with_source( + "failed to decode address interface", + err, + ) + })?; + interface = Some(addr_interface); + }, + other => { + return Err(AddressError::UnknownRoutingParameterKey(other)); + }, + } + } + + let interface = interface.ok_or_else(|| { + AddressError::decode_error("interface must be present in routing parameters") + })?; + + let mut routing_parameters = RoutingParameters::new(interface); + routing_parameters.note_tag_len = note_tag_len; + + Ok(routing_parameters) + } +} + +impl Serializable for RoutingParameters { + fn write_into(&self, target: &mut W) { + let bytes = self.encode_to_bytes(); + // Due to the bech32 constraint of max 633 bytes, a u16 is sufficient. + let num_bytes = bytes.len() as u16; + + target.write_u16(num_bytes); + target.write_many(bytes); + } +} + +impl Deserializable for RoutingParameters { + fn read_from(source: &mut R) -> Result { + let num_bytes = source.read_u16()?; + let bytes: Vec = source.read_many(num_bytes as usize)?; + + Self::decode_from_bytes(bytes.into_iter()) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use bech32::{Bech32m, Checksum, Hrp}; + + use super::*; + + /// Checks the assumptions about the total length allowed in bech32 encoding. + /// + /// The assumption is that encoding should error if the total length of the hrp + data (encoded + /// in GF(32)) + the separator + the checksum exceeds Bech32m::CODE_LENGTH. + #[test] + fn bech32_code_length_assertions() -> anyhow::Result<()> { + let hrp = Hrp::parse("mrp").unwrap(); + let separator_len = BECH32_SEPARATOR.len(); + // The fixed number of characters included in a bech32 string. + let fixed_num_bytes = hrp.as_str().len() + separator_len + Bech32m::CHECKSUM_LENGTH; + let num_allowed_chars = Bech32m::CODE_LENGTH - fixed_num_bytes; + // Multiply by the 5 bits per base32 character and divide by 8 bits per byte. + let num_allowed_bytes = num_allowed_chars * 5 / 8; + + // The number of bytes that routing parameters effectively have available. + assert_eq!(num_allowed_bytes, 633); + + // This amount of data is the max that should be okay to encode. + let data_ok = vec![5; num_allowed_bytes]; + // One more byte than the max allowed amount should result in an error. + let data_too_long = vec![5; num_allowed_bytes + 1]; + + assert!(bech32::encode::(hrp, &data_ok).is_ok()); + assert!(bech32::encode::(hrp, &data_too_long).is_err()); + + Ok(()) + } + + #[test] + fn routing_parameters_bech32_encode_decode_roundtrip() -> anyhow::Result<()> { + let routing_params = + RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(8)?; + assert_eq!(routing_params, RoutingParameters::decode(routing_params.encode_to_string())?); + + Ok(()) + } + + /// Tests that routing parameters can be serialized and deserialized. + #[test] + fn routing_parameters_serialization() -> anyhow::Result<()> { + let routing_params = + RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(6)?; + + assert_eq!( + routing_params, + RoutingParameters::read_from_bytes(&routing_params.to_bytes()).unwrap() + ); + + Ok(()) + } +} diff --git a/crates/miden-objects/src/address/type.rs b/crates/miden-objects/src/address/type.rs index 2ed771a9ee..188755e28d 100644 --- a/crates/miden-objects/src/address/type.rs +++ b/crates/miden-objects/src/address/type.rs @@ -6,15 +6,23 @@ use crate::errors::Bech32Error; /// The byte values of this address type should be chosen as a multiple of 8. That way, the first /// character of the bech32 string after the `1` separator will be different for every address type. /// This makes the type of the address conveniently human-readable. +/// +/// For instance, [`AddressType::AccountId`] is chosen as 232 which is 0b1110_1000 in binary. Base32 +/// encodes one character for every 5 bits and 0b11101 (= 29) translates to `a`. So, every account +/// ID address will start with `mm1a`. +/// +/// See the table in the [bech32 spec](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32) +/// for a convenient overview. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(u8)] +#[non_exhaustive] pub enum AddressType { AccountId = Self::ACCOUNT_ID, } impl AddressType { // Constants for internal use only. - const ACCOUNT_ID: u8 = 0; + const ACCOUNT_ID: u8 = 232; } impl TryFrom for AddressType { diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index b1d30b7bad..f536dfe6cf 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -281,16 +281,51 @@ pub enum AccountTreeError { #[derive(Debug, Error)] pub enum AddressError { - #[error("tag length {0} should be {expected} bits for network accounts", expected = crate::note::NoteTag::DEFAULT_NETWORK_TAG_LENGTH)] + #[error("tag length {0} should be {expected} bits for network accounts", + expected = NoteTag::DEFAULT_NETWORK_TAG_LENGTH + )] CustomTagLengthNotAllowedForNetworkAccounts(u8), - #[error("tag length {0} is too large, must be less than or equal to {max}", max = crate::note::NoteTag::MAX_LOCAL_TAG_LENGTH)] + #[error("tag length {0} is too large, must be less than or equal to {max}", + max = NoteTag::MAX_LOCAL_TAG_LENGTH + )] TagLengthTooLarge(u8), #[error("unknown address interface `{0}`")] UnknownAddressInterface(u16), #[error("failed to decode account ID")] AccountIdDecodeError(#[source] AccountIdError), + #[error("address separator must not be included without routing parameters")] + TrailingSeparator, #[error("failed to decode bech32 string into an address")] Bech32DecodeError(#[source] Bech32Error), + #[error("{error_msg}")] + DecodeError { + error_msg: Box, + // thiserror will return this when calling Error::source on NoteError. + source: Option>, + }, + #[error("found unknown routing parameter key {0}")] + UnknownRoutingParameterKey(u8), +} + +impl AddressError { + /// Creates an [`AddressError::DecodeError`] variant from an error message. + pub fn decode_error(message: impl Into) -> Self { + let message: String = message.into(); + Self::DecodeError { error_msg: message.into(), source: None } + } + + /// Creates an [`AddressError::DecodeError`] variant from an error message and + /// a source error. + pub fn decode_error_with_source( + message: impl Into, + source: impl Error + Send + Sync + 'static, + ) -> Self { + let message: String = message.into(); + Self::DecodeError { + error_msg: message.into(), + source: Some(Box::new(source)), + } + } } // BECH32 ERROR diff --git a/docs/src/account/id.md b/docs/src/account/id.md index bc620c1c6b..92cc68db4d 100644 --- a/docs/src/account/id.md +++ b/docs/src/account/id.md @@ -48,10 +48,10 @@ Users can choose whether their accounts are stored publicly or privately. The pr An `Account` ID can be encoded in different formats: 1. [**Bech32**](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) (user-facing): - - Example: `mm1qrt4skk6t26a9vquwlad3rq2usul8fy2` + - Example: `mm1apk5f8jqxnadegr46xtklmm78qhdgkwc` - **Benefits**: - Built-in error detection via checksum algorithm - - Human-readable prefix indicates network type + - Human-readable prefix indicates network ID - Less prone to transcription errors - **Structure**: - [Human-readable prefix](https://github.com/satoshilabs/slips/blob/master/slip-0173.md) that From b160cabba65b6af4bbc6473e12bf0692dff7c855 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Tue, 4 Nov 2025 06:55:14 +0300 Subject: [PATCH 124/133] chore: migrate to the VM v0.19.0 (#2042) --- CHANGELOG.md | 2 + Cargo.lock | 377 ++++++++++-------- Cargo.toml | 23 +- .../src/local_block_prover.rs | 8 +- .../asm/kernels/transaction/lib/account.masm | 18 +- .../transaction/lib/account_delta.masm | 4 +- .../kernels/transaction/lib/asset_vault.masm | 28 +- .../asm/kernels/transaction/lib/epilogue.masm | 18 +- .../asm/kernels/transaction/lib/link_map.masm | 12 +- .../asm/kernels/transaction/lib/memory.masm | 122 +++--- .../asm/kernels/transaction/lib/note.masm | 2 +- .../kernels/transaction/lib/output_note.masm | 8 +- .../asm/kernels/transaction/lib/prologue.masm | 6 +- .../asm/kernels/transaction/main.masm | 2 +- .../kernels/transaction/tx_script_main.masm | 2 +- crates/miden-lib/asm/miden/active_note.masm | 2 +- crates/miden-lib/asm/miden/auth/mod.masm | 16 +- .../asm/miden/auth/rpo_falcon512.masm | 4 +- .../asm/miden/contracts/faucets/mod.masm | 2 +- crates/miden-lib/asm/miden/tx.masm | 2 +- crates/miden-lib/asm/note_scripts/MINT.masm | 4 +- crates/miden-lib/asm/note_scripts/P2ID.masm | 2 +- crates/miden-lib/asm/note_scripts/P2IDE.masm | 2 +- crates/miden-lib/asm/note_scripts/SWAP.masm | 8 +- .../src/errors/transaction_errors.rs | 2 - crates/miden-lib/src/transaction/events.rs | 10 +- .../src/transaction/kernel_procedures.rs | 28 +- crates/miden-lib/src/transaction/mod.rs | 8 +- crates/miden-objects/Cargo.toml | 2 +- .../src/account/storage/map/partial.rs | 41 +- .../src/account/storage/partial.rs | 2 +- .../miden-objects/src/asset/vault/partial.rs | 58 +-- .../src/block/partial_account_tree.rs | 62 +-- .../src/block/partial_nullifier_tree.rs | 34 +- crates/miden-objects/src/note/script.rs | 4 +- .../miden-objects/src/transaction/tx_args.rs | 2 +- .../miden-testing/src/kernel_tests/tx/mod.rs | 22 +- .../src/kernel_tests/tx/test_account.rs | 30 +- .../src/kernel_tests/tx/test_active_note.rs | 8 +- .../src/kernel_tests/tx/test_asset.rs | 6 +- .../src/kernel_tests/tx/test_asset_vault.rs | 10 +- .../src/kernel_tests/tx/test_fpi.rs | 4 +- .../src/kernel_tests/tx/test_input_note.rs | 2 +- .../src/kernel_tests/tx/test_note.rs | 24 +- .../src/kernel_tests/tx/test_output_note.rs | 4 +- .../src/kernel_tests/tx/test_tx.rs | 2 +- crates/miden-testing/src/mock_host.rs | 2 +- crates/miden-testing/src/utils.rs | 4 +- crates/miden-testing/tests/scripts/faucet.rs | 4 +- crates/miden-tx/src/executor/mod.rs | 2 +- crates/miden-tx/src/executor/notes_checker.rs | 2 +- .../miden-tx/src/host/account_procedures.rs | 2 +- crates/miden-tx/src/host/link_map.rs | 4 +- crates/miden-tx/src/host/mod.rs | 46 +-- 54 files changed, 576 insertions(+), 529 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a22856cf..7192c2ce7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ - [BREAKING] Added `get_note_script()` method to `DataStore` trait to enable lazy loading of note scripts during transaction execution ([#1995](https://github.com/0xMiden/miden-base/pull/1995)). - [BREAKING] Separate account APIs in `miden::account` into `active_account` and `native_account` ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). - [BREAKING] Remove `miden::account::get_native_nonce` procedure ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). +- [BREAKING] Refactor `PartialVault`, `PartialStorageMap`, `PartialAccountTree` and `PartialNullifierTree` to allow construction from a root ([#2042](https://github.com/0xMiden/miden-base/pull/2042)). - [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032)). ### Changes @@ -86,6 +87,7 @@ - [BREAKING] Refactor `TransactionInputs` and remove `TransactionWitness` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). - [BREAKING] Assert nonce is non-zero after the auth procedure ([#1982](https://github.com/0xMiden/miden-base/pull/1982)). - [BREAKING] Change the outputs of the `output_note::add_asset` procedure: now the values that are the same as the passed parameters are dropped ([#2031](https://github.com/0xMiden/miden-base/pull/2031)). +- [BREAKING] Upgrade VM to 0.19 ([#2042](https://github.com/0xMiden/miden-base/pull/2042)). ## 0.11.5 (2025-10-02) diff --git a/Cargo.lock b/Cargo.lock index a7b66b72d3..c766403552 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,7 +44,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -52,9 +52,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -291,9 +291,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "blake3" @@ -343,9 +343,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.41" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", "jobserver", @@ -355,9 +355,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" @@ -423,18 +423,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstyle", "clap_lex", @@ -442,9 +442,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "color-eyre" @@ -614,6 +614,33 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version 0.4.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "debugid" version = "0.8.0" @@ -685,6 +712,30 @@ dependencies = [ "spki", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" @@ -734,9 +785,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -817,6 +868,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -855,9 +912,9 @@ dependencies = [ [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "fs-err" @@ -964,9 +1021,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -982,21 +1039,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -1025,9 +1082,9 @@ dependencies = [ [[package]] name = "half" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", @@ -1036,9 +1093,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" dependencies = [ "allocator-api2", "equivalent", @@ -1047,12 +1104,6 @@ dependencies = [ "serde", ] -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -1085,12 +1136,12 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown", ] [[package]] @@ -1120,26 +1171,15 @@ dependencies = [ "generic-array", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1150,9 +1190,9 @@ checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -1217,15 +1257,15 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1359,20 +1399,21 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] [[package]] name = "miden-air" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b72121bacf147becf7eadcfc7bcd62d41c1e97c0d2cfdc1fd5dba2531721a8" +checksum = "02cfe0ccecbd2e7a8a2cd97544d4cef438cb495e84e1a5769371dc822ff67268" dependencies = [ "miden-core", + "miden-utils-indexing", "thiserror", "winter-air", "winter-prover", @@ -1380,9 +1421,9 @@ dependencies = [ [[package]] name = "miden-assembly" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc57c069054e1f34052e60b55e7c7e066c09fe6f23fc6ca747b25b9afa9b6abd" +checksum = "95978901bbe5cfc5632a85776d122ae3376a0728e5a4b4211c478d0a87c9e715" dependencies = [ "log", "miden-assembly-syntax", @@ -1394,9 +1435,9 @@ dependencies = [ [[package]] name = "miden-assembly-syntax" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73b2c264d5f65a3f62f49a6c4dfedf37152454d6d963330bd0c5b183b2e2e6c" +checksum = "f6e4ae33c9801102e32e04d51917333b0576d245e996f5ff43b0e2820f156876" dependencies = [ "aho-corasick", "lalrpop", @@ -1425,14 +1466,15 @@ dependencies = [ [[package]] name = "miden-core" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "996f8d8e2a4dd4eef54225f6f116c73b14f03256bc23ea5d4260fe803dd103e2" +checksum = "6806a8fd613e996b6d818a973ca40c484787848f14bea0a0c7aa1158ba9b26c2" dependencies = [ "enum_dispatch", "miden-crypto", "miden-debug-types", "miden-formatting", + "miden-utils-indexing", "num-derive", "num-traits", "thiserror", @@ -1442,19 +1484,20 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b287c7a76b95be7ef5588e98a9dbe1973395dbb19eb59dd34d82b06e47f02a" +checksum = "7ffedb6b42cbc44634c3f9c58ee912b0d585fabdf3a19af56d8f185eb50125ef" dependencies = [ "blake3", "cc", "chacha20poly1305", + "ed25519-dalek", "flume", - "getrandom 0.2.16", "glob", - "hashbrown 0.15.5", + "hashbrown", "hkdf", "k256", + "miden-crypto-derive", "num", "num-complex", "rand", @@ -1463,23 +1506,35 @@ dependencies = [ "rand_hc", "rayon", "sha3", + "subtle", "thiserror", "winter-crypto", "winter-math", "winter-utils", - "zeroize", + "x25519-dalek", +] + +[[package]] +name = "miden-crypto-derive" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d3b266500c6fe3584dacc773285a6e8c70b367b7ec0316fb17632a47f98b397" +dependencies = [ + "quote", + "syn", ] [[package]] name = "miden-debug-types" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c24f4040fd3a5b714f2492c723b91dffe29b1af591d15f393dd102063725cd0" +checksum = "43b37453feb8f4392af5eef29a11ac099b76cbcc47ffc034944a573d06a1cc1e" dependencies = [ "memchr", "miden-crypto", "miden-formatting", "miden-miette", + "miden-utils-indexing", "miden-utils-sync", "paste", "serde", @@ -1517,9 +1572,9 @@ dependencies = [ [[package]] name = "miden-mast-package" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c25726075ff6b7262fbb12d4914672ae6999fbeaa6e3883c9cff69348ad1c09c" +checksum = "bb106cd425958cb2f6fab03741d4d8905b31dca48c0942dfd61adfe6970c695f" dependencies = [ "derive_more", "miden-assembly-syntax", @@ -1578,7 +1633,7 @@ dependencies = [ "bech32", "color-eyre", "criterion 0.5.1", - "getrandom 0.3.3", + "getrandom 0.3.4", "log", "miden-air", "miden-assembly", @@ -1605,15 +1660,18 @@ dependencies = [ [[package]] name = "miden-processor" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943b22d826ce116cbaf8633c699e45eece83dc4c2facb1677e067b42f433aba8" +checksum = "e97fbbc6911627d1fd25ad6f598a7ccd79721e9c89c4890a105f0a906ea3baf7" dependencies = [ + "itertools 0.14.0", "miden-air", "miden-core", "miden-debug-types", "miden-utils-diagnostics", + "miden-utils-indexing", "paste", + "rayon", "thiserror", "tokio", "tracing", @@ -1622,9 +1680,9 @@ dependencies = [ [[package]] name = "miden-prover" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743e27899f2a56e92da93efc287fe76a635446251dbecc2138e8b85ecdcfd0bd" +checksum = "3e07a8cb521312c8454544453b212b6dee3e7a983467f18ef8e296754ed7fe79" dependencies = [ "miden-air", "miden-debug-types", @@ -1636,9 +1694,9 @@ dependencies = [ [[package]] name = "miden-stdlib" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cebf10ea08fc7e015e4391953e8953ed689104973084e1c4f057473017e6518" +checksum = "79bafb0b8715b8323eecef2b118a1dfa7112cc6cc247df742ccb8f8026c4da37" dependencies = [ "env_logger", "fs-err", @@ -1701,9 +1759,9 @@ dependencies = [ [[package]] name = "miden-utils-diagnostics" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9da59c10824c85d585145aff6ef760f0baaaf6c4576c8ad241c281ce2e345b79" +checksum = "a7d2d3d0909fa09ba58c6965f0dfafefeb299822dc417893786c119211cac89c" dependencies = [ "miden-crypto", "miden-debug-types", @@ -1712,11 +1770,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "miden-utils-indexing" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f35669a21d99c0d5f96720ba80205438f5e63a4132dfa4d1089bc95efce7760" +dependencies = [ + "thiserror", +] + [[package]] name = "miden-utils-sync" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498138c030b4bf3808e2ef87ba1170b22d5572a8ef6a0acd5a5f10d2bfc91e72" +checksum = "0d9f2e82680ceff6618373c61d5947eda6eaf95f3503cf8310d3fb12a1dbdf9f" dependencies = [ "lock_api", "loom", @@ -1725,9 +1792,9 @@ dependencies = [ [[package]] name = "miden-verifier" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf15bf92a77f8f7d0291b155af55bb5576fe4277d897dc035e73dc39275bace" +checksum = "a7e25b94000d5eaa24375c1c0a6fb49e1f349a35b59f51717359f06bc56fe14f" dependencies = [ "miden-air", "miden-core", @@ -1756,17 +1823,6 @@ dependencies = [ "adler", ] -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - [[package]] name = "nanorand" version = "0.7.0" @@ -1914,9 +1970,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -2115,21 +2171,20 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ - "bitflags 2.9.4", - "lazy_static", + "bitflags 2.10.0", "num-traits", "rand", "rand_chacha", @@ -2197,7 +2252,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -2253,14 +2308,14 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] name = "regex" -version = "1.11.3" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -2270,9 +2325,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -2281,9 +2336,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "relative-path" @@ -2369,7 +2424,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2382,7 +2437,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.11.0", @@ -2696,9 +2751,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -2707,9 +2762,9 @@ dependencies = [ [[package]] name = "target-triple" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" +checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" [[package]] name = "tempfile" @@ -2718,7 +2773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.2", "windows-sys 0.61.2", @@ -2804,24 +2859,19 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", - "io-uring", - "libc", - "mio", "pin-project-lite", - "slab", "tokio-macros", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -2987,9 +3037,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.111" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ded9fdb81f30a5708920310bfcd9ea7482ff9cba5f54601f7a19a877d5c2392" +checksum = "559b6a626c0815c942ac98d434746138b4f89ddd6a1b8cbb168c6845fb3376c5" dependencies = [ "dissimilar", "glob", @@ -3015,9 +3065,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-linebreak" @@ -3106,15 +3156,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -3126,9 +3167,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -3137,25 +3178,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3163,31 +3190,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -3691,6 +3718,16 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", +] + [[package]] name = "zerocopy" version = "0.8.27" diff --git a/Cargo.toml b/Cargo.toml index dedae10568..a4f5124b46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ exclude = [".github/"] homepage = "https://miden.xyz" license = "MIT" repository = "https://github.com/0xMiden/miden-base" -rust-version = "1.89" +rust-version = "1.90" version = "0.12.0" [profile.release] @@ -48,16 +48,17 @@ miden-tx = { default-features = false, path = "crates/miden-tx", ve miden-tx-batch-prover = { default-features = false, path = "crates/miden-tx-batch-prover", version = "0.12" } # Miden dependencies -miden-assembly = { default-features = false, version = "0.18" } -miden-assembly-syntax = { default-features = false, version = "0.18" } -miden-core = { default-features = false, version = "0.18" } -miden-crypto = { default-features = false, version = "0.17" } -miden-mast-package = { default-features = false, version = "0.18" } -miden-processor = { default-features = false, version = "0.18" } -miden-prover = { default-features = false, version = "0.18" } -miden-stdlib = { default-features = false, version = "0.18" } -miden-utils-sync = { default-features = false, version = "0.18" } -miden-verifier = { default-features = false, version = "0.18" } +miden-air = { default-features = false, version = "0.19" } +miden-assembly = { default-features = false, version = "0.19" } +miden-assembly-syntax = { default-features = false, version = "0.19" } +miden-core = { default-features = false, version = "0.19" } +miden-crypto = { default-features = false, version = "0.18" } +miden-mast-package = { default-features = false, version = "0.19" } +miden-processor = { default-features = false, version = "0.19" } +miden-prover = { default-features = false, version = "0.19" } +miden-stdlib = { default-features = false, version = "0.19" } +miden-utils-sync = { default-features = false, version = "0.19" } +miden-verifier = { default-features = false, version = "0.19" } # External dependencies anyhow = { default-features = false, features = ["backtrace", "std"], version = "1.0" } diff --git a/crates/miden-block-prover/src/local_block_prover.rs b/crates/miden-block-prover/src/local_block_prover.rs index e98e9533bc..5637009e29 100644 --- a/crates/miden-block-prover/src/local_block_prover.rs +++ b/crates/miden-block-prover/src/local_block_prover.rs @@ -208,17 +208,13 @@ fn compute_nullifiers( let nullifiers: Vec = created_nullifiers.keys().copied().collect(); - let mut partial_nullifier_tree = PartialNullifierTree::new(); - // First, reconstruct the current nullifier tree with the merkle paths of the nullifiers we want // to update. // Due to the guarantees of ProposedBlock we can safely assume that each nullifier is mapped to // its corresponding nullifier witness, so we don't have to check again whether they match. - for witness in created_nullifiers.into_values() { - partial_nullifier_tree - .track_nullifier(witness) + let mut partial_nullifier_tree = + PartialNullifierTree::with_witnesses(created_nullifiers.into_values()) .map_err(ProvenBlockError::NullifierWitnessRootMismatch)?; - } // Check the nullifier tree root in the previous block header matches the reconstructed tree's // root. diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account.masm b/crates/miden-lib/asm/kernels/transaction/lib/account.masm index 280111be82..d4f6b69e42 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account.masm @@ -606,13 +606,13 @@ export.set_map_item.12 # => [OLD_MAP_VALUE, NEW_ROOT, KEY, NEW_VALUE] # store OLD_MAP_VALUE and NEW_ROOT until the end of the procedure - loc_storew.4 dropw loc_storew.8 dropw + loc_storew_be.4 dropw loc_storew_be.8 dropw # => [KEY, NEW_VALUE] dupw.1 # => [NEW_VALUE, KEY, NEW_VALUE] - padw loc_loadw.4 + padw loc_loadw_be.4 # => [OLD_MAP_VALUE, NEW_VALUE, KEY, NEW_VALUE] dupw.2 @@ -629,7 +629,7 @@ export.set_map_item.12 # => [KEY, NEW_VALUE] # load OLD_MAP_VALUE and NEW_ROOT on the top of the stack - loc_loadw.8 swapw loc_loadw.4 swapw + loc_loadw_be.8 swapw loc_loadw_be.4 swapw # => [NEW_ROOT, OLD_MAP_VALUE, ...] # set the root of the map in the respective account storage slot @@ -957,7 +957,7 @@ export.get_procedure_info # => [proc_ptr, metadata_ptr] # load procedure information from memory - padw movup.4 mem_loadw padw movup.8 mem_loadw + padw movup.4 mem_loadw_be padw movup.8 mem_loadw_be # => [METADATA, PROC_ROOT] # more explicitly: # => [0, 0, storage_size, storage_offset, PROC_ROOT] @@ -1483,7 +1483,7 @@ end #! - VALUE is the value of the item. export.get_item_raw # get the item from storage - swap mul.8 add padw movup.4 mem_loadw + swap mul.8 add padw movup.4 mem_loadw_be # => [VALUE] end @@ -1554,7 +1554,7 @@ proc.set_item_raw # => [acct_storage_slots_section_offset, index, NEW_VALUE, OLD_VALUE] # update storage - swap mul.8 add mem_storew + swap mul.8 add mem_storew_be # => [NEW_VALUE, OLD_VALUE] # update the storage commitment dirty flag, indicating that the commitment is outdated @@ -1587,7 +1587,7 @@ proc.get_procedure_root # => [proc_ptr] # load procedure root from memory - padw movup.4 mem_loadw + padw movup.4 mem_loadw_be # => [PROC_ROOT] end @@ -1608,7 +1608,7 @@ proc.get_procedure_metadata # => [storage_offset_ptr, EMPTY_WORD] # load procedure metadata from memory and keep relevant data - mem_loadw drop drop swap + mem_loadw_be drop drop swap # => [storage_offset, storage_size] end @@ -1653,7 +1653,7 @@ export.get_account_data_ptr # => [curr_account_ptr', foreign_account_id_prefix, foreign_account_id_suffix] # load the first data word at the current account pointer - padw dup.4 mem_loadw + padw dup.4 mem_loadw_be # => [FIRST_DATA_WORD, curr_account_ptr', foreign_account_id_prefix, foreign_account_id_suffix] # check whether the last value in the word equals zero diff --git a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm index 43f75f3657..d3777b9e80 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/account_delta.masm @@ -731,7 +731,7 @@ export.set_map_item.8 # => [KEY, PREV_VALUE, NEW_VALUE] # store KEY in local - loc_storew.4 + loc_storew_be.4 # => [KEY, PREV_VALUE, NEW_VALUE] loc_load.0 @@ -762,7 +762,7 @@ export.set_map_item.8 # => [INITIAL_VALUE, NEW_VALUE] # load key and index from locals - padw loc_loadw.4 loc_load.0 + padw loc_loadw_be.4 loc_load.0 # => [account_delta_storage_map_ptr, KEY, INITIAL_VALUE, NEW_VALUE] exec.link_map::set drop diff --git a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm index 736d4dd8eb..6d439c8e71 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/asset_vault.masm @@ -63,7 +63,7 @@ export.get_balance # => [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] # load the asset vault root from memory - padw movup.6 mem_loadw + padw movup.6 mem_loadw_be # => [ASSET_VAULT_ROOT, faucet_id_prefix, faucet_id_suffix] # prepare the key for fungible asset lookup (pad least significant elements with zeros) @@ -109,7 +109,7 @@ export.peek_balance # => [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] # load the asset vault root from memory - padw movup.6 mem_loadw + padw movup.6 mem_loadw_be # => [ASSET_VAULT_ROOT, faucet_id_prefix, faucet_id_suffix] # prepare the vault key for fungible asset lookup (pad least significant elements with zeros) @@ -159,7 +159,7 @@ export.has_non_fungible_asset # => [ASSET_KEY, vault_root_ptr] # prepare the stack to read non-fungible asset from vault - padw movup.8 mem_loadw swapw + padw movup.8 mem_loadw_be swapw # => [ASSET_KEY, ACCT_VAULT_ROOT] # lookup asset @@ -208,7 +208,7 @@ export.add_fungible_asset # the current asset may be the empty word if it does not exist and so its faucet id would be zeroes # we therefore overwrite the faucet id with the faucet id from ASSET to account for this edge case - mem_loadw swapw + mem_loadw_be swapw # => [ASSET_KEY, VAULT_ROOT, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] emit.SMT_PEEK_EVENT adv_loadw @@ -279,7 +279,7 @@ export.add_fungible_asset # => [VAULT_ROOT', ASSET', vault_root_ptr] # update the vault root - movup.8 mem_storew dropw + movup.8 mem_storew_be dropw # => [ASSET'] end @@ -306,7 +306,7 @@ export.add_non_fungible_asset padw dup.12 # => [vault_root_ptr, pad(4), ASSET_KEY, ASSET, vault_root_ptr] - mem_loadw swapw + mem_loadw_be swapw # => [ASSET_KEY, VAULT_ROOT, ASSET, vault_root_ptr] dupw.2 # => [ASSET, ASSET_KEY, VAULT_ROOT, ASSET, vault_root_ptr] @@ -320,7 +320,7 @@ export.add_non_fungible_asset # => [VAULT_ROOT', ASSET, vault_root_ptr] # update the vault root - movup.8 mem_storew dropw + movup.8 mem_storew_be dropw # => [ASSET] end @@ -398,7 +398,7 @@ export.remove_fungible_asset.4 # => [ASSET, ASSET_KEY, PEEKED_ASSET, vault_root_ptr] # store ASSET so we can return it later - loc_storew.0 + loc_storew_be.0 # => [ASSET, ASSET_KEY, PEEKED_ASSET, vault_root_ptr] dup.3 dup.12 @@ -431,7 +431,7 @@ export.remove_fungible_asset.4 cdropw # => [EMPTY_WORD_OR_ASSET', ASSET_KEY, PEEKED_ASSET, vault_root_ptr] - dup.12 padw movup.4 mem_loadw + dup.12 padw movup.4 mem_loadw_be # => [VAULT_ROOT, EMPTY_WORD_OR_ASSET', ASSET_KEY, PEEKED_ASSET, vault_root_ptr] movdnw.2 @@ -447,10 +447,10 @@ export.remove_fungible_asset.4 # => [NEW_VAULT_ROOT, vault_root_ptr] # update vault root - movup.4 mem_storew + movup.4 mem_storew_be # => [NEW_VAULT_ROOT] - loc_loadw.0 + loc_loadw_be.0 # => [ASSET] end @@ -471,7 +471,7 @@ export.remove_non_fungible_asset # => [pad(4), ASSET_KEY, ASSET, vault_root_ptr] # load vault root - dup.12 mem_loadw + dup.12 mem_loadw_be # => [VAULT_ROOT, ASSET_KEY, ASSET, vault_root_ptr] # prepare insertion of an EMPTY_WORD into the vault at the asset key to remove the asset @@ -487,7 +487,7 @@ export.remove_non_fungible_asset # => [VAULT_ROOT', ASSET, vault_root_ptr] # update the vault root - movup.8 mem_storew dropw + movup.8 mem_storew_be dropw # => [ASSET] end @@ -536,7 +536,7 @@ end #! - asset is the peeked asset from the vault. proc.peek_asset # load the asset vault root from memory - padw movup.8 mem_loadw + padw movup.8 mem_loadw_be # => [ASSET_VAULT_ROOT, ASSET_KEY] swapw diff --git a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm index 78092d52b6..f7281e5be9 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -167,7 +167,7 @@ proc.build_output_vault # num_assets, note_data_ptr, output_notes_end_ptr] # read the output note asset from memory - padw dup.5 mem_loadw + padw dup.5 mem_loadw_be # => [ASSET, output_vault_root_ptr, assets_start_ptr, assets_end_ptr, # output_vault_root_ptr, num_assets, note_data_ptr, output_notes_end_ptr] @@ -215,7 +215,7 @@ proc.execute_auth_procedure push.0 exec.memory::get_account_procedure_ptr # => [auth_procedure_ptr, AUTH_ARGS, pad(12)] - padw dup.4 mem_loadw + padw dup.4 mem_loadw_be # => [AUTH_PROC_ROOT, auth_procedure_ptr, AUTH_ARGS, pad(12)] # if auth procedure was called already, it must have been called by a user, which is disallowed @@ -401,7 +401,7 @@ export.finalize_transaction.12 # assert no net creation or destruction of assets over the transaction exec.memory::get_input_vault_root exec.memory::get_output_vault_root assert_eqw.err=ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME - # => [ACCOUNT_UPDATE_COMMITMENT] + # => [] # ------ Compute output notes commitment ------ @@ -413,7 +413,7 @@ export.finalize_transaction.12 # => [OUTPUT_NOTES_COMMITMENT] # store commitment in local - loc_storew.0 dropw + loc_storew_be.0 dropw # => [] # ------ Compute account delta commitment ------ @@ -422,7 +422,7 @@ export.finalize_transaction.12 # => [ACCOUNT_DELTA_COMMITMENT] # store commitment in local - loc_storew.8 + loc_storew_be.8 # => [ACCOUNT_DELTA_COMMITMENT] # ------ Assert that account was changed or notes were consumed ------ @@ -451,7 +451,7 @@ export.finalize_transaction.12 # => [FEE_ASSET] # store fee asset in local - loc_storew.4 dropw + loc_storew_be.4 dropw # => [] # ------ Insert final account data into advice provider ------ @@ -479,7 +479,7 @@ export.finalize_transaction.12 # ------ Compute and insert account update commitment ------ # load account delta commitment from local - padw loc_loadw.8 + padw loc_loadw_be.8 # => [ACCOUNT_DELTA_COMMITMENT, FINAL_ACCOUNT_COMMITMENT] # insert into advice map ACCOUNT_UPDATE_COMMITMENT: (FINAL_ACCOUNT_COMMITMENT, ACCOUNT_DELTA_COMMITMENT), @@ -496,10 +496,10 @@ export.finalize_transaction.12 # => [ACCOUNT_UPDATE_COMMITMENT, tx_expiration_block_num] # load fee asset from local - padw loc_loadw.4 swapw + padw loc_loadw_be.4 swapw # => [ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET, tx_expiration_block_num] # load output notes commitment from local - padw loc_loadw.0 + padw loc_loadw_be.0 # => [OUTPUT_NOTES_COMMITMENT, ACCOUNT_UPDATE_COMMITMENT, FEE_ASSET, tx_expiration_block_num] end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm b/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm index f11f04dbff..bef4faffff 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/link_map.masm @@ -723,10 +723,10 @@ proc.set_value dup movdn.5 # => [entry_ptr, VALUE0, entry_ptr, VALUE1] - add.VALUE0_OFFSET mem_storew dropw + add.VALUE0_OFFSET mem_storew_be dropw # => [entry_ptr, VALUE1] - add.VALUE1_OFFSET mem_storew dropw + add.VALUE1_OFFSET mem_storew_be dropw # => [] end @@ -750,7 +750,7 @@ proc.get_value0 padw movup.4 # => [entry_ptr, pad(4)] - add.VALUE0_OFFSET mem_loadw + add.VALUE0_OFFSET mem_loadw_be # => [VALUE0] end @@ -762,7 +762,7 @@ proc.get_value1 padw movup.4 # => [entry_ptr, pad(4)] - add.VALUE1_OFFSET mem_loadw + add.VALUE1_OFFSET mem_loadw_be # => [VALUE1] end @@ -771,7 +771,7 @@ end #! Inputs: [entry_ptr, KEY] #! Outputs: [] proc.set_key - add.KEY_OFFSET mem_storew dropw + add.KEY_OFFSET mem_storew_be dropw end #! Returns the key of the entry pointer. @@ -782,7 +782,7 @@ proc.get_key padw movup.4 # => [entry_ptr, pad(4)] - add.KEY_OFFSET mem_loadw + add.KEY_OFFSET mem_loadw_be # => [KEY] end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm index c24c6e214c..5223ca6e48 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -335,7 +335,7 @@ end #! Where: #! - INPUT_VAULT_ROOT is the input vault root. export.get_input_vault_root - padw mem_loadw.INPUT_VAULT_ROOT_PTR + padw mem_loadw_be.INPUT_VAULT_ROOT_PTR end #! Sets the input vault root. @@ -346,7 +346,7 @@ end #! Where: #! - INPUT_VAULT_ROOT is the input vault root. export.set_input_vault_root - mem_storew.INPUT_VAULT_ROOT_PTR + mem_storew_be.INPUT_VAULT_ROOT_PTR end #! Returns the pointer to the memory address at which the output vault root is stored. @@ -369,7 +369,7 @@ end #! Where: #! - OUTPUT_VAULT_ROOT is the output vault root. export.get_output_vault_root - padw mem_loadw.OUTPUT_VAULT_ROOT_PTR + padw mem_loadw_be.OUTPUT_VAULT_ROOT_PTR end #! Sets the output vault root. @@ -380,7 +380,7 @@ end #! Where: #! - OUTPUT_VAULT_ROOT is the output vault root. export.set_output_vault_root - mem_storew.OUTPUT_VAULT_ROOT_PTR + mem_storew_be.OUTPUT_VAULT_ROOT_PTR end # GLOBAL INPUTS @@ -394,7 +394,7 @@ end #! Where: #! - BLOCK_COMMITMENT is the commitment of the transaction reference block. export.set_block_commitment - mem_storew.BLOCK_COMMITMENT_PTR + mem_storew_be.BLOCK_COMMITMENT_PTR end #! Returns the block commitment of the reference block. @@ -405,7 +405,7 @@ end #! Where: #! - BLOCK_COMMITMENT is the commitment of the transaction reference block. export.get_block_commitment - padw mem_loadw.BLOCK_COMMITMENT_PTR + padw mem_loadw_be.BLOCK_COMMITMENT_PTR end #! Sets the ID of the native account. @@ -418,7 +418,7 @@ end export.set_global_account_id push.0.0 # => [0, 0, account_id_prefix, account_id_suffix] - mem_storew.NATIVE_ACCT_ID_PTR + mem_storew_be.NATIVE_ACCT_ID_PTR dropw # => [] end @@ -431,7 +431,7 @@ end #! Where: #! - account_id_{prefix,suffix} are the prefix and suffix felts of the account ID. export.get_global_account_id - padw mem_loadw.NATIVE_ACCT_ID_PTR + padw mem_loadw_be.NATIVE_ACCT_ID_PTR # => [0, 0, account_id_prefix, account_id_suffix] drop drop end @@ -444,7 +444,7 @@ end #! Where: #! - INIT_ACCOUNT_COMMITMENT is the initial account commitment. export.set_init_account_commitment - mem_storew.INIT_ACCOUNT_COMMITMENT_PTR + mem_storew_be.INIT_ACCOUNT_COMMITMENT_PTR end #! Returns the native account commitment at the beginning of the transaction. @@ -455,7 +455,7 @@ end #! Where: #! - INIT_ACCOUNT_COMMITMENT is the initial account commitment. export.get_init_account_commitment - padw mem_loadw.INIT_ACCOUNT_COMMITMENT_PTR + padw mem_loadw_be.INIT_ACCOUNT_COMMITMENT_PTR end #! Sets the initial account nonce. @@ -488,7 +488,7 @@ end #! Where: #! - INIT_NATIVE_ACCOUNT_VAULT_ROOT is the initial vault root of the native account. export.set_init_native_account_vault_root - mem_storew.INIT_NATIVE_ACCOUNT_VAULT_ROOT_PTR + mem_storew_be.INIT_NATIVE_ACCOUNT_VAULT_ROOT_PTR end #! Returns the vault root of the native account at the beginning of the transaction. @@ -499,7 +499,7 @@ end #! Where: #! - INIT_NATIVE_ACCOUNT_VAULT_ROOT is the initial vault root of the native account. export.get_init_native_account_vault_root - padw mem_loadw.INIT_NATIVE_ACCOUNT_VAULT_ROOT_PTR + padw mem_loadw_be.INIT_NATIVE_ACCOUNT_VAULT_ROOT_PTR end #! Returns the memory address of the vault root of the native account at the beginning of the @@ -523,7 +523,7 @@ end #! Where: #! - INIT_NATIVE_ACCOUNT_STORAGE_COMMITMENT is the initial storage commitment of the native account. export.set_init_account_storage_commitment - mem_storew.INIT_NATIVE_ACCOUNT_STORAGE_COMMITMENT_PTR + mem_storew_be.INIT_NATIVE_ACCOUNT_STORAGE_COMMITMENT_PTR end #! Returns the storage commitment of the native account at the beginning of the transaction. @@ -534,7 +534,7 @@ end #! Where: #! - INIT_NATIVE_ACCOUNT_STORAGE_COMMITMENT is the initial storage commitment of the native account. export.get_init_account_storage_commitment - padw mem_loadw.INIT_NATIVE_ACCOUNT_STORAGE_COMMITMENT_PTR + padw mem_loadw_be.INIT_NATIVE_ACCOUNT_STORAGE_COMMITMENT_PTR end #! Returns the input notes commitment. @@ -547,7 +547,7 @@ end #! Where: #! - INPUT_NOTES_COMMITMENT is the input notes commitment. export.get_input_notes_commitment - padw mem_loadw.INPUT_NOTES_COMMITMENT_PTR + padw mem_loadw_be.INPUT_NOTES_COMMITMENT_PTR end #! Sets the input notes' commitment. @@ -558,7 +558,7 @@ end #! Where: #! - INPUT_NOTES_COMMITMENT is the notes' commitment. export.set_nullifier_commitment - mem_storew.INPUT_NOTES_COMMITMENT_PTR + mem_storew_be.INPUT_NOTES_COMMITMENT_PTR end #! Returns the memory address of the transaction script root. @@ -580,7 +580,7 @@ end #! Where: #! - TX_SCRIPT_ROOT is the transaction script root. export.set_tx_script_root - mem_storew.TX_SCRIPT_ROOT_PTR + mem_storew_be.TX_SCRIPT_ROOT_PTR end #! Returns the transaction script arguments. @@ -592,7 +592,7 @@ end #! - TX_SCRIPT_ARGS is the word of values which could be used directly or could be used to obtain #! some values associated with it from the advice map. export.get_tx_script_args - padw mem_loadw.TX_SCRIPT_ARGS_PTR + padw mem_loadw_be.TX_SCRIPT_ARGS_PTR end #! Sets the transaction script arguments. @@ -604,7 +604,7 @@ end #! - TX_SCRIPT_ARGS is the word of values which could be used directly or could be used to obtain #! some values associated with it from the advice map. export.set_tx_script_args - mem_storew.TX_SCRIPT_ARGS_PTR + mem_storew_be.TX_SCRIPT_ARGS_PTR end #! Returns the auth procedure arguments. @@ -615,7 +615,7 @@ end #! Where: #! - AUTH_ARGS is the argument passed to the auth procedure. export.get_auth_args - padw mem_loadw.AUTH_ARGS_PTR + padw mem_loadw_be.AUTH_ARGS_PTR end #! Sets the auth procedure arguments. @@ -626,7 +626,7 @@ end #! Where: #! - AUTH_ARGS is the argument passed to the auth procedure. export.set_auth_args - mem_storew.AUTH_ARGS_PTR + mem_storew_be.AUTH_ARGS_PTR end # BLOCK DATA @@ -651,7 +651,7 @@ end #! Where: #! - PREV_BLOCK_COMMITMENT_PTR is the block commitment of the transaction reference block. export.get_prev_block_commitment - padw mem_loadw.PREV_BLOCK_COMMITMENT_PTR + padw mem_loadw_be.PREV_BLOCK_COMMITMENT_PTR end #! Returns the block number of the transaction reference block. @@ -698,7 +698,7 @@ end #! - native_asset_id_{prefix,suffix} are the prefix and suffix felts of the faucet ID that defines #! the native asset. export.get_native_asset_id - padw mem_loadw.FEE_PARAMETERS_PTR drop drop + padw mem_loadw_be.FEE_PARAMETERS_PTR drop drop # => [native_asset_id_prefix, native_asset_id_suffix] end @@ -722,7 +722,7 @@ end #! Where: #! - CHAIN_COMMITMENT is the chain commitment of the transaction reference block. export.get_chain_commitment - padw mem_loadw.CHAIN_COMMITMENT_PTR + padw mem_loadw_be.CHAIN_COMMITMENT_PTR end #! Returns the account db root of the transaction reference block. @@ -733,7 +733,7 @@ end #! Where: #! - ACCT_DB_ROOT is the account database root of the transaction reference block. export.get_account_db_root - padw mem_loadw.ACCT_DB_ROOT_PTR + padw mem_loadw_be.ACCT_DB_ROOT_PTR end #! Returns the nullifier db root of the transaction reference block. @@ -744,7 +744,7 @@ end #! Where: #! - NULLIFIER_ROOT is the nullifier root of the transaction reference block. export.get_nullifier_db_root - padw mem_loadw.NULLIFIER_ROOT_PTR + padw mem_loadw_be.NULLIFIER_ROOT_PTR end #! Returns the tx commitment of the transaction reference block. @@ -755,7 +755,7 @@ end #! Where: #! - TX_COMMITMENT is the tx commitment of the transaction reference block. export.get_tx_commitment - padw mem_loadw.TX_COMMITMENT_PTR + padw mem_loadw_be.TX_COMMITMENT_PTR end #! Returns the transaction kernel commitment of the transaction reference block. @@ -766,7 +766,7 @@ end #! Where: #! - TX_KERNEL_COMMITMENT is the sequential hash of the kernel procedures. export.get_tx_kernel_commitment - padw mem_loadw.TX_KERNEL_COMMITMENT_PTR + padw mem_loadw_be.TX_KERNEL_COMMITMENT_PTR end #! Returns the proof commitment of the transaction reference block. @@ -777,7 +777,7 @@ end #! Where: #! - PROOF_COMMITMENT is the proof commitment of the transaction reference block. export.get_proof_commitment - padw mem_loadw.PROOF_COMMITMENT_PTR + padw mem_loadw_be.PROOF_COMMITMENT_PTR end #! Returns the note root of the transaction reference block. @@ -788,7 +788,7 @@ end #! Where: #! - NOTE_ROOT is the note root of the transaction reference block. export.get_note_root - padw mem_loadw.NOTE_ROOT_PTR + padw mem_loadw_be.NOTE_ROOT_PTR end #! Sets the note root of the transaction reference block. @@ -799,7 +799,7 @@ end #! Where: #! - NOTE_ROOT is the note root of the transaction reference block. export.set_note_root - mem_storew.NOTE_ROOT_PTR + mem_storew_be.NOTE_ROOT_PTR end # CHAIN DATA @@ -1008,7 +1008,7 @@ end #! Where: #! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the active account. export.get_account_id - padw exec.get_active_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET mem_loadw + padw exec.get_active_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET mem_loadw_be # => [nonce, 0, account_id_prefix, account_id_suffix] drop drop # => [account_id_prefix, account_id_suffix] @@ -1023,7 +1023,7 @@ end #! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the native account #! of the transaction. export.get_native_account_id - padw push.NATIVE_ACCOUNT_DATA_PTR add.ACCT_ID_AND_NONCE_OFFSET mem_loadw + padw push.NATIVE_ACCOUNT_DATA_PTR add.ACCT_ID_AND_NONCE_OFFSET mem_loadw_be # => [nonce, 0, account_id_prefix, account_id_suffix] drop drop # => [account_id_prefix, account_id_suffix] @@ -1039,7 +1039,7 @@ end #! - nonce is the nonce of the active account. export.set_account_id_and_nonce exec.get_active_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET - mem_storew + mem_storew_be end #! Returns the nonce of the active account. @@ -1076,9 +1076,9 @@ end export.set_account_nonce exec.get_active_account_data_ptr add.ACCT_ID_AND_NONCE_OFFSET padw # => [0, 0, 0, 0, account_id_and_nonce_ptr, new_nonce] - dup.4 mem_loadw + dup.4 mem_loadw_be # => [old_nonce, 0, old_id_prefix, old_id_suffix, account_id_and_nonce_ptr, new_nonce] - drop movup.4 movup.4 mem_storew dropw + drop movup.4 movup.4 mem_storew_be dropw # => [] end @@ -1105,7 +1105,7 @@ end export.get_account_vault_root padw exec.get_active_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET - mem_loadw + mem_loadw_be end #! Sets the account vault root. @@ -1117,7 +1117,7 @@ end #! - ACCT_VAULT_ROOT is the account vault root to be set. export.set_account_vault_root exec.get_active_account_data_ptr add.ACCT_VAULT_ROOT_OFFSET - mem_storew + mem_storew_be end #! Returns the memory pointer to the initial vault root of the active account. @@ -1162,7 +1162,7 @@ end export.get_account_code_commitment padw exec.get_active_account_data_ptr add.ACCT_CODE_COMMITMENT_OFFSET - mem_loadw + mem_loadw_be end #! Sets the code commitment of the account. @@ -1174,7 +1174,7 @@ end #! - CODE_COMMITMENT is the code commitment to be set. export.set_account_code_commitment exec.get_active_account_data_ptr add.ACCT_CODE_COMMITMENT_OFFSET - mem_storew + mem_storew_be end #! Sets the transaction expiration block number. @@ -1269,7 +1269,7 @@ end export.get_account_storage_commitment padw exec.get_active_account_data_ptr add.ACCT_STORAGE_COMMITMENT_OFFSET - mem_loadw + mem_loadw_be end #! Sets the account storage commitment. @@ -1281,7 +1281,7 @@ end #! - STORAGE_COMMITMENT is the account storage commitment. export.set_account_storage_commitment exec.get_active_account_data_ptr add.ACCT_STORAGE_COMMITMENT_OFFSET - mem_storew + mem_storew_be end #! Sets the dirty flag for the native account storage commitment. @@ -1524,7 +1524,7 @@ end #! - note_ptr is the input note's the memory address. #! - NOTE_ID is the note's id. export.set_input_note_id - mem_storew + mem_storew_be end #! Computes a pointer to the memory address at which the nullifier associated a note with `idx` is @@ -1549,7 +1549,7 @@ end #! - idx is the index of the input note. #! - nullifier is the nullifier of the input note. export.get_input_note_nullifier - mul.4 padw movup.4 add.INPUT_NOTE_NULLIFIER_SECTION_PTR mem_loadw + mul.4 padw movup.4 add.INPUT_NOTE_NULLIFIER_SECTION_PTR mem_loadw_be end #! Returns a pointer to the start of the input note core data segment for the note located at the @@ -1576,7 +1576,7 @@ end export.get_input_note_script_root padw movup.4 add.INPUT_NOTE_SCRIPT_ROOT_OFFSET - mem_loadw + mem_loadw_be end #! Returns the memory address of the script root of an input note. @@ -1602,7 +1602,7 @@ end export.get_input_note_inputs_commitment padw movup.4 add.INPUT_NOTE_INPUTS_COMMITMENT_OFFSET - mem_loadw + mem_loadw_be end #! Returns the metadata of an input note located at the specified memory address. @@ -1616,7 +1616,7 @@ end export.get_input_note_metadata padw movup.4 add.INPUT_NOTE_METADATA_OFFSET - mem_loadw + mem_loadw_be end #! Sets the metadata for an input note located at the specified memory address. @@ -1629,7 +1629,7 @@ end #! - NOTE_METADATA is the metadata of the input note. export.set_input_note_metadata add.INPUT_NOTE_METADATA_OFFSET - mem_storew + mem_storew_be end #! Returns the note's args. @@ -1643,7 +1643,7 @@ end export.get_input_note_args padw movup.4 add.INPUT_NOTE_ARGS_OFFSET - mem_loadw + mem_loadw_be end #! Sets the note args for an input note located at the specified memory address. @@ -1656,7 +1656,7 @@ end #! - NOTE_ARGS are optional note args of the input note. export.set_input_note_args add.INPUT_NOTE_ARGS_OFFSET - mem_storew + mem_storew_be end #! Returns the number of inputs of the note located at the specified memory address. @@ -1735,7 +1735,7 @@ end export.get_input_note_recipient padw movup.4 add.INPUT_NOTE_RECIPIENT_OFFSET - mem_loadw + mem_loadw_be end #! Sets the input note's recipient. @@ -1748,7 +1748,7 @@ end #! - RECIPIENT is the commitment to the note's script, inputs and the serial number. export.set_input_note_recipient add.INPUT_NOTE_RECIPIENT_OFFSET - mem_storew + mem_storew_be end #! Returns the assets commitment for the input note located at the specified memory address. @@ -1762,7 +1762,7 @@ end export.get_input_note_assets_commitment padw movup.4 add.INPUT_NOTE_ASSETS_COMMITMENT_OFFSET - mem_loadw + mem_loadw_be end #! Returns the serial number for the input note located at the specified memory address. @@ -1776,7 +1776,7 @@ end export.get_input_note_serial_num padw movup.4 add.INPUT_NOTE_SERIAL_NUM_OFFSET - mem_loadw + mem_loadw_be end #! Returns the sender for the input note located at the specified memory address. @@ -1790,7 +1790,7 @@ end export.get_input_note_sender padw movup.4 add.INPUT_NOTE_METADATA_OFFSET - mem_loadw + mem_loadw_be # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_id_prefix] drop drop @@ -1847,7 +1847,7 @@ end export.get_output_note_recipient padw movup.4 add.OUTPUT_NOTE_RECIPIENT_OFFSET - mem_loadw + mem_loadw_be end #! Sets the output note's recipient. @@ -1860,7 +1860,7 @@ end #! - RECIPIENT is the commitment to the note's script, inputs and the serial number. export.set_output_note_recipient add.OUTPUT_NOTE_RECIPIENT_OFFSET - mem_storew + mem_storew_be end #! Returns the output note's metadata. @@ -1876,7 +1876,7 @@ export.get_output_note_metadata # => [0, 0, 0, 0, note_ptr] movup.4 add.OUTPUT_NOTE_METADATA_OFFSET # => [(note_ptr + offset), 0, 0, 0, 0] - mem_loadw + mem_loadw_be # => [METADATA] end @@ -1890,7 +1890,7 @@ end #! - note_ptr is the memory address at which the output note data begins. export.set_output_note_metadata add.OUTPUT_NOTE_METADATA_OFFSET - mem_storew + mem_storew_be end #! Returns the number of assets in the output note. @@ -1979,7 +1979,7 @@ end export.get_output_note_assets_commitment padw movup.4 add.OUTPUT_NOTE_ASSETS_COMMITMENT_OFFSET - mem_loadw + mem_loadw_be end #! Sets the output note assets commitment for the output note located at the specified memory @@ -1993,7 +1993,7 @@ end #! - ASSETS_COMMITMENT is the sequential hash of the padded assets of an output note. export.set_output_note_assets_commitment add.OUTPUT_NOTE_ASSETS_COMMITMENT_OFFSET - mem_storew + mem_storew_be end # KERNEL DATA diff --git a/crates/miden-lib/asm/kernels/transaction/lib/note.masm b/crates/miden-lib/asm/kernels/transaction/lib/note.masm index 7737c18ec8..b884a3728f 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/note.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/note.masm @@ -194,7 +194,7 @@ proc.compute_output_note_id # => [NOTE_ID, note_data_ptr] # save the output note commitment (note ID) to memory - movup.4 mem_storew + movup.4 mem_storew_be # => [NOTE_ID] end diff --git a/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm b/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm index bc06593d67..902ea9768f 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/output_note.masm @@ -415,7 +415,7 @@ proc.add_fungible_asset # note_idx] while.true - mem_loadw + mem_loadw_be # => [STORED_ASSET, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] dup.4 eq @@ -465,7 +465,7 @@ proc.add_fungible_asset # => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] # Store the fungible asset, either the combined ASSET or the new ASSET - mem_storew dropw drop drop + mem_storew_be dropw drop drop # => [note_ptr, num_of_assets, note_idx] # increase the number of assets in the note @@ -506,7 +506,7 @@ proc.add_non_fungible_asset while.true # load the asset and compare - mem_loadw exec.word::test_eq + mem_loadw_be exec.word::test_eq assertz.err=ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS # => [ASSET', ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] @@ -525,7 +525,7 @@ proc.add_non_fungible_asset # => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx] # end of the loop reached, no error so we can store the non-fungible asset - mem_storew dropw drop drop + mem_storew_be dropw drop drop # => [note_ptr, num_of_assets, note_idx] # increase the number of assets in the note diff --git a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm index 05384bbe02..0f4848deb0 100644 --- a/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -555,7 +555,7 @@ proc.authenticate_note.8 # --------------------------------------------------------------------------------------------- # load the note root from memory - loc_loadw.4 swapw + loc_loadw_be.4 swapw # => [NOTE_COMMITMENT, NOTE_ROOT] # load the index of the note @@ -782,7 +782,7 @@ proc.add_input_note_assets_to_vault dup.2 # => [input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] - padw dup.5 mem_loadw + padw dup.5 mem_loadw_be # => [ASSET, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] # TODO: Because the input vault is a copy of the account vault, to mutate the input vault, @@ -897,7 +897,7 @@ proc.process_input_note # => [NULLIFIER, note_ptr, idx, HASHER_CAPACITY] # save NULLIFIER to memory - movup.5 exec.memory::get_input_note_nullifier_ptr mem_storew + movup.5 exec.memory::get_input_note_nullifier_ptr mem_storew_be # => [NULLIFIER, note_ptr, HASHER_CAPACITY] # note metadata & args diff --git a/crates/miden-lib/asm/kernels/transaction/main.masm b/crates/miden-lib/asm/kernels/transaction/main.masm index d6f2c779f8..959f882d83 100644 --- a/crates/miden-lib/asm/kernels/transaction/main.masm +++ b/crates/miden-lib/asm/kernels/transaction/main.masm @@ -129,7 +129,7 @@ proc.main.1 # get the memory address of the transaction script root and load it to the stack exec.memory::get_tx_script_root_ptr - padw dup.4 mem_loadw + padw dup.4 mem_loadw_be # => [TX_SCRIPT_ROOT, tx_script_root_ptr, pad(16)] exec.word::eqz not diff --git a/crates/miden-lib/asm/kernels/transaction/tx_script_main.masm b/crates/miden-lib/asm/kernels/transaction/tx_script_main.masm index c6abb37472..79c99f8549 100644 --- a/crates/miden-lib/asm/kernels/transaction/tx_script_main.masm +++ b/crates/miden-lib/asm/kernels/transaction/tx_script_main.masm @@ -46,7 +46,7 @@ proc.main # get the memory address of the transaction script root and load it to the stack exec.memory::get_tx_script_root_ptr - padw dup.4 mem_loadw + padw dup.4 mem_loadw_be # => [TX_SCRIPT_ROOT, tx_script_root_ptr] # return an error if the transaction script was not specified diff --git a/crates/miden-lib/asm/miden/active_note.masm b/crates/miden-lib/asm/miden/active_note.masm index cc1ee278a5..69d2e6753b 100644 --- a/crates/miden-lib/asm/miden/active_note.masm +++ b/crates/miden-lib/asm/miden/active_note.masm @@ -291,7 +291,7 @@ export.add_assets_to_account.1024 # => [ptr, EMPTY_WORD, ptr, end_ptr] # load the asset - mem_loadw + mem_loadw_be # => [ASSET, ptr, end_ptr] # pad the stack before call diff --git a/crates/miden-lib/asm/miden/auth/mod.masm b/crates/miden-lib/asm/miden/auth/mod.masm index 187ac3cf5f..a62b6141cc 100644 --- a/crates/miden-lib/asm/miden/auth/mod.masm +++ b/crates/miden-lib/asm/miden/auth/mod.masm @@ -5,13 +5,13 @@ use.std::crypto::hashes::rpo #! Inputs: [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT] #! Outputs: [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT] export.adv_insert_hqword.16 - loc_storew.0 + loc_storew_be.0 movdnw.3 - loc_storew.4 + loc_storew_be.4 movdnw.3 - loc_storew.8 + loc_storew_be.8 movdnw.3 - loc_storew.12 + loc_storew_be.12 movdnw.3 # => [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT] @@ -29,10 +29,10 @@ export.adv_insert_hqword.16 drop drop # => [<4 stack elements>] - loc_loadw.12 - padw loc_loadw.8 - padw loc_loadw.4 - padw loc_loadw.0 + loc_loadw_be.12 + padw loc_loadw_be.8 + padw loc_loadw_be.4 + padw loc_loadw_be.0 # => [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT] end diff --git a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm index 131780b538..81da11a100 100644 --- a/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm +++ b/crates/miden-lib/asm/miden/auth/rpo_falcon512.masm @@ -111,7 +111,7 @@ export.verify_signatures.16 exec.active_account::get_initial_map_item # => [OWNER_PUB_KEY, i-1, MSG] - loc_storew.CURRENT_PK_LOC + loc_storew_be.CURRENT_PK_LOC # => [OWNER_PUB_KEY, i-1, MSG] # Check if signature exists for this signer. @@ -142,7 +142,7 @@ export.verify_signatures.16 # Verify the signature against the public key and message. # ----------------------------------------------------------------------------------------- - loc_loadw.CURRENT_PK_LOC + loc_loadw_be.CURRENT_PK_LOC # => [PK, MSG, MSG, i-1] swapw diff --git a/crates/miden-lib/asm/miden/contracts/faucets/mod.masm b/crates/miden-lib/asm/miden/contracts/faucets/mod.masm index a7b45ac1a6..b16d3ad808 100644 --- a/crates/miden-lib/asm/miden/contracts/faucets/mod.masm +++ b/crates/miden-lib/asm/miden/contracts/faucets/mod.masm @@ -105,7 +105,7 @@ export.burn assert.err=ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS # => [dest_ptr, pad(16)] - mem_loadw + mem_loadw_be # => [ASSET, pad(16)] # burning the asset diff --git a/crates/miden-lib/asm/miden/tx.masm b/crates/miden-lib/asm/miden/tx.masm index 45516b26e3..a6890e1fe4 100644 --- a/crates/miden-lib/asm/miden/tx.masm +++ b/crates/miden-lib/asm/miden/tx.masm @@ -234,7 +234,7 @@ export.execute_foreign_procedure.4 # store the foreign procedure root to the first local memory slot and get its absolute memory # address - loc_storew.0 dropw locaddr.0 + loc_storew_be.0 dropw locaddr.0 # => [foreign_proc_root_ptr, , pad(n)] # execute the foreign procedure diff --git a/crates/miden-lib/asm/note_scripts/MINT.masm b/crates/miden-lib/asm/note_scripts/MINT.masm index ed5056a9fb..fec23f48a4 100644 --- a/crates/miden-lib/asm/note_scripts/MINT.masm +++ b/crates/miden-lib/asm/note_scripts/MINT.masm @@ -44,10 +44,10 @@ begin # => [pad(16)] # Load amount - mem_loadw.0 + mem_loadw_be.0 # => [RECIPIENT, pad(12)] - swapw mem_loadw.4 + swapw mem_loadw_be.4 # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] mem_load.8 diff --git a/crates/miden-lib/asm/note_scripts/P2ID.masm b/crates/miden-lib/asm/note_scripts/P2ID.masm index 884d20a1a4..f5b565d223 100644 --- a/crates/miden-lib/asm/note_scripts/P2ID.masm +++ b/crates/miden-lib/asm/note_scripts/P2ID.masm @@ -37,7 +37,7 @@ begin # => [inputs_ptr, EMPTY_WORD] # read the target account ID from the note inputs - mem_loadw drop drop + mem_loadw_be drop drop # => [target_account_id_prefix, target_account_id_suffix] exec.active_account::get_id diff --git a/crates/miden-lib/asm/note_scripts/P2IDE.masm b/crates/miden-lib/asm/note_scripts/P2IDE.masm index 87adda3cb8..8e885cc02d 100644 --- a/crates/miden-lib/asm/note_scripts/P2IDE.masm +++ b/crates/miden-lib/asm/note_scripts/P2IDE.masm @@ -109,7 +109,7 @@ begin # => [inputs_ptr] # read the reclaim block height, timelock_block_height, and target account ID from the note inputs - mem_loadw + mem_loadw_be # => [timelock_block_height, reclaim_block_height, target_account_id_prefix, target_account_id_suffix] # read the current block number diff --git a/crates/miden-lib/asm/note_scripts/SWAP.masm b/crates/miden-lib/asm/note_scripts/SWAP.masm index f64252166c..479e328593 100644 --- a/crates/miden-lib/asm/note_scripts/SWAP.masm +++ b/crates/miden-lib/asm/note_scripts/SWAP.masm @@ -54,15 +54,15 @@ begin # => [inputs_ptr] # load REQUESTED_ASSET - mem_loadw + mem_loadw_be # => [REQUESTED_ASSET] # load PAYBACK_NOTE_RECIPIENT - padw mem_loadw.4 + padw mem_loadw_be.4 # => [PAYBACK_NOTE_RECIPIENT, REQUESTED_ASSET] # load payback P2ID details - padw mem_loadw.8 + padw mem_loadw_be.8 # => [tag, aux, note_type, execution_hint, PAYBACK_NOTE_RECIPIENT, REQUESTED_ASSET] # create payback P2ID note @@ -97,7 +97,7 @@ begin # => [ptr, pad(12)] # load the ASSET - mem_loadw + mem_loadw_be # => [ASSET, pad(12)] # add the ASSET to the account diff --git a/crates/miden-lib/src/errors/transaction_errors.rs b/crates/miden-lib/src/errors/transaction_errors.rs index a94d719d21..8c8013c8e4 100644 --- a/crates/miden-lib/src/errors/transaction_errors.rs +++ b/crates/miden-lib/src/errors/transaction_errors.rs @@ -8,8 +8,6 @@ use crate::transaction::TransactionEvent; #[derive(Debug, Error)] pub enum TransactionEventError { - #[error("event id {0} is reserved for system events")] - ReservedSystemEvent(EventId), #[error("event id {0} is not a valid transaction event")] InvalidTransactionEvent(EventId, Option<&'static str>), #[error("event id {0} is not a transaction kernel event")] diff --git a/crates/miden-lib/src/transaction/events.rs b/crates/miden-lib/src/transaction/events.rs index 652830065b..2b3c5edd67 100644 --- a/crates/miden-lib/src/transaction/events.rs +++ b/crates/miden-lib/src/transaction/events.rs @@ -103,15 +103,11 @@ impl fmt::Display for TransactionEvent { impl TryFrom for TransactionEvent { type Error = TransactionEventError; - fn try_from(value: EventId) -> Result { - let raw = value.as_felt().as_int(); + fn try_from(event_id: EventId) -> Result { + let raw = event_id.as_felt().as_int(); let name = EVENT_NAME_LUT.get(&raw).copied(); - if value.is_reserved() { - return Err(TransactionEventError::ReservedSystemEvent(value)); - } - match raw { ACCOUNT_BEFORE_FOREIGN_LOAD => Ok(TransactionEvent::AccountBeforeForeignLoad), @@ -184,7 +180,7 @@ impl TryFrom for TransactionEvent { AUTH_UNAUTHORIZED => Ok(TransactionEvent::Unauthorized), - _ => Err(TransactionEventError::InvalidTransactionEvent(value, name)), + _ => Err(TransactionEventError::InvalidTransactionEvent(event_id, name)), } } } diff --git a/crates/miden-lib/src/transaction/kernel_procedures.rs b/crates/miden-lib/src/transaction/kernel_procedures.rs index e572b51e2d..59f5573b31 100644 --- a/crates/miden-lib/src/transaction/kernel_procedures.rs +++ b/crates/miden-lib/src/transaction/kernel_procedures.rs @@ -30,27 +30,27 @@ pub const KERNEL_PROCEDURES: [Word; 52] = [ // account_set_item word!("0x84b5206c5a0dccf56568bc0157b8322e8a506332bc212f1ad35bab4fe9f6bfed"), // account_get_map_item - word!("0xc310b9a4a08531061839abd3f575a681a6af61c36ca48491d0896d3badee87f1"), + word!("0xeb40115d6ca9bed0ac817b246e3d247bb3386e56ee17692d30a3be799f98f2f6"), // account_get_initial_map_item - word!("0xa23d7c4e671f60c36c43076fbeb9a3d4112bc18cf808eba28e153690b6833236"), + word!("0x054f48624a30f260269cc9d780e9d84b77091e29f1d44c6450ab73e0eb91cc39"), // account_set_map_item - word!("0x6ee1674cb94eaf4e23383abbfe918bff742ec13f69150eaf66cc9ea0243f4a7e"), + word!("0x33ab43462ceb87f00c5fbebd47cdf948246db0e82b8e85c98edc1b568b784ba3"), // account_get_initial_vault_root word!("0x46297d9ac95afd60c7ef1a065e024ad49aa4c019f6b3924191905449b244d4ec"), // account_get_vault_root word!("0x42a2bfb8eac4fce9bbf75ea15215b00729faeeaf7fff784692948d3f618a9bb7"), // account_add_asset - word!("0xcc3f09332303fc856a05fe2ec176a19e8e9e69213e380f7ac019fbd67ca42552"), + word!("0x11890564573ff2ad0916b1a8ca12a9d2546f4c3dcf09fa4bfb6d72d701a3d7ff"), // account_remove_asset - word!("0x7b965e458a962667a9cdc54a6677fd0c5a573bd7b265ccc33775a8a985aeead5"), + word!("0xcb3118521acfe552030b988439713717fdee54d9e7cb54116d4034cf89ce8a34"), // account_get_balance - word!("0x52233827fc91ef50a5dc09f74d3176011d244fd80e291ae751b64bdaa2c6cf75"), + word!("0x1ed792cc7775aa1ce2f32367a3d430561ec9bceb33f5bb222691c49a6bde8112"), // account_get_initial_balance - word!("0x9758302462328d6557153655562e6a419def7992997c0abbcec412cbb4f9351f"), + word!("0xdc1320d6f044c40d37e5e835a584b643d6e77e3fc1136f498815298e28c912b8"), // account_has_non_fungible_asset - word!("0xf975c799cffebf8565a8479475fe04c1832ef2a7484c4a2a42bbf7d8a340d649"), + word!("0xfaad11de0c026551df15231790c2364cc598e891444bf826da01b524b1a8ca8f"), // account_compute_delta_commitment - word!("0xf92256166420f15937d4e5b358cef0affae1d658140ae2bfa4f50a050eac5af3"), + word!("0x88a66fd1da5df01c98d02c7d76cd567de9a17c6641c045fcd9b05881e990826a"), // account_get_num_procedures word!("0x53b5ec38b7841948762c258010e6e07ad93963bcaac2d83813f8edb6710dc720"), // account_get_procedure_root @@ -60,13 +60,13 @@ pub const KERNEL_PROCEDURES: [Word; 52] = [ // account_has_procedure word!("0x667d5ce1b7a54c3b8965666ce90e59085c97775b82eba25dddfe218db5fe137d"), // faucet_mint_asset - word!("0x83cca30914ec2265bb79bf0eda0c4fc1dfa2a300b47511528a1c85f49967faa9"), + word!("0xfc1923fc651f8122a2f32fce96711203bd8309180ce6f484ea6fd4ceb175e3f0"), // faucet_burn_asset - word!("0x5a2b12bbd942e74187c1a5c1b8efe546e98f15b9915c3224d48f3147eb8dcc3e"), + word!("0x2633226e8831cfb1e9970ae2ee79b83678538f2c6656a755c707ba85cd462562"), // faucet_get_total_fungible_asset_issuance word!("0x7d32952d4dc0edd0311e3424b8128df2d48cf949f800c28218fbc851a8db42b5"), // faucet_is_non_fungible_asset_issued - word!("0xcf6969fde43c797d0bbf76685cb707ff46774fe606b075db0cd7a8c369e29e07"), + word!("0x0323d18fe6bd7bbada87d265d2ffb27f2a7828b1b6865d6d1a3b4a64924a9f7f"), // input_note_get_metadata word!("0x7ad3e94585e7a397ee27443c98b376ed8d4ba762122af6413fde9314c00a6219"), // input_note_get_assets_info @@ -84,7 +84,7 @@ pub const KERNEL_PROCEDURES: [Word; 52] = [ // output_note_get_metadata word!("0xde4a5b57f9d53692459383e6cf6302ef3602a348896ed6ab6fdf67e07fa483ff"), // output_note_get_assets_info - word!("0xc1af8e03d4672761fab05e742cc267a03d090d43f1348721eeb00881ebcfead8"), + word!("0x7e5d726b5f25f6cfd533bd0294853f3fceea62c41e5f2fd68919d8d53a48b3f8"), // output_note_get_recipient word!("0xc824115ed79a2e1670daed8c18fba1bc15f54c5ec0ec6699de69a00b21d9df92"), // output_note_add_asset @@ -104,7 +104,7 @@ pub const KERNEL_PROCEDURES: [Word; 52] = [ // tx_get_block_timestamp word!("0x7903185b847517debb6c2072364e3e757b99ee623e97c2bd0a4661316c5c5418"), // tx_start_foreign_context - word!("0x17bd522cf79f5cb2b09362d328c517c25efab6f1b778cfc566eb7c732622ccaf"), + word!("0x4bfde60ab4b1e42148ceea2845ecf9aae061a577972baf348379701760d476d7"), // tx_end_foreign_context word!("0xaa0018aa8da890b73511879487f65553753fb7df22de380dd84c11e6f77eec6f"), // tx_get_expiration_delta diff --git a/crates/miden-lib/src/transaction/mod.rs b/crates/miden-lib/src/transaction/mod.rs index 0d47e08bd7..54c55744cc 100644 --- a/crates/miden-lib/src/transaction/mod.rs +++ b/crates/miden-lib/src/transaction/mod.rs @@ -274,15 +274,15 @@ impl TransactionKernel { stack: &StackOutputs, // FIXME TODO add an extension trait for this one ) -> Result<(Word, Word, FungibleAsset, BlockNumber), TransactionOutputError> { let output_notes_commitment = stack - .get_stack_word(OUTPUT_NOTES_COMMITMENT_WORD_IDX * 4) + .get_stack_word_be(OUTPUT_NOTES_COMMITMENT_WORD_IDX * 4) .expect("output_notes_commitment (first word) missing"); let account_update_commitment = stack - .get_stack_word(ACCOUNT_UPDATE_COMMITMENT_WORD_IDX * 4) + .get_stack_word_be(ACCOUNT_UPDATE_COMMITMENT_WORD_IDX * 4) .expect("account_update_commitment (second word) missing"); let fee = stack - .get_stack_word(FEE_ASSET_WORD_IDX * 4) + .get_stack_word_be(FEE_ASSET_WORD_IDX * 4) .expect("fee_asset (third word) missing"); let expiration_block_num = stack @@ -299,7 +299,7 @@ impl TransactionKernel { // Make sure that indices 13, 14 and 15 are zeroes (i.e. the fourth word without the // expiration block number). - if stack.get_stack_word(12).expect("fourth word missing").as_elements()[..3] + if stack.get_stack_word_be(12).expect("fourth word missing").as_elements()[..3] != Word::empty().as_elements()[..3] { return Err(TransactionOutputError::OutputStackInvalid( diff --git a/crates/miden-objects/Cargo.toml b/crates/miden-objects/Cargo.toml index 6bd643c885..68ca9850f1 100644 --- a/crates/miden-objects/Cargo.toml +++ b/crates/miden-objects/Cargo.toml @@ -69,4 +69,4 @@ tempfile = { version = "3.19" } winter-air = { version = "0.13" } # for HashFunction/ExecutionProof::new_dummy color-eyre = { version = "0.5" } -miden-air = { features = ["std", "testing"], version = "0.18" } +miden-air = { features = ["std", "testing"], workspace = true } diff --git a/crates/miden-objects/src/account/storage/map/partial.rs b/crates/miden-objects/src/account/storage/map/partial.rs index 1559b8e173..92fd97e868 100644 --- a/crates/miden-objects/src/account/storage/map/partial.rs +++ b/crates/miden-objects/src/account/storage/map/partial.rs @@ -43,19 +43,26 @@ impl PartialStorageMap { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns a new instance of partial storage map with the specified partial SMT and stored - /// entries. - pub fn from_witnesses( + /// Constructs a [`PartialStorageMap`] from a [`StorageMap`] root. + /// + /// For conversion from a [`StorageMap`], prefer [`Self::new_minimal`] to be more explicit. + pub fn new(root: Word) -> Self { + PartialStorageMap { + partial_smt: PartialSmt::new(root), + entries: BTreeMap::new(), + } + } + + /// Returns a new instance of a [`PartialStorageMap`] with all provided witnesses added to it. + pub fn with_witnesses( witnesses: impl IntoIterator, ) -> Result { - let mut partial_smt = PartialSmt::default(); let mut map = BTreeMap::new(); - for witness in witnesses.into_iter() { + let partial_smt = PartialSmt::from_proofs(witnesses.into_iter().map(|witness| { map.extend(witness.entries()); - let smt_proof = SmtProof::from(witness); - partial_smt.add_proof(smt_proof)?; - } + SmtProof::from(witness) + }))?; Ok(PartialStorageMap { partial_smt, entries: map }) } @@ -73,21 +80,11 @@ impl PartialStorageMap { /// Converts a [`StorageMap`] into a partial storage representation. /// - /// The resulting [`PartialStorageMap`] will contain only a single, unspecified key-value pair - /// in order to have the same root as the original storage map. Is it otherwise the most - /// _minimal_ representation of the storage map. + /// The resulting [`PartialStorageMap`] will represent the root of the storage map, but not + /// track any key-value pairs, which means it is the most _minimal_ representation of the + /// storage map. pub fn new_minimal(storage_map: &StorageMap) -> Self { - let mut partial_map = PartialStorageMap::default(); - - // Construct a partial storage map that tracks the empty word, but none of the assets that - // are actually in the asset tree. That way, the partial map has the same root as the full - // map. This is the most minimal and correct partial map we can build. - // TODO: Workaround for https://github.com/0xMiden/miden-base/issues/1966. Fix when implemented. - partial_map - .add(storage_map.open(&Word::empty())) - .expect("adding the first proof should never fail"); - - partial_map + Self::new(storage_map.root()) } // ACCESSORS diff --git a/crates/miden-objects/src/account/storage/partial.rs b/crates/miden-objects/src/account/storage/partial.rs index 577aca0c2d..53c90828a7 100644 --- a/crates/miden-objects/src/account/storage/partial.rs +++ b/crates/miden-objects/src/account/storage/partial.rs @@ -182,7 +182,7 @@ mod tests { let witness = map_1.open(&map_key_present); let partial_storage = - PartialStorage::new(storage_header, [PartialStorageMap::from_witnesses([witness])?]) + PartialStorage::new(storage_header, [PartialStorageMap::with_witnesses([witness])?]) .context("creating partial storage")?; let retrieved_map = partial_storage.maps.get(&partial_storage.header.slot(0)?.1).unwrap(); diff --git a/crates/miden-objects/src/asset/vault/partial.rs b/crates/miden-objects/src/asset/vault/partial.rs index ee792f9443..b303842cff 100644 --- a/crates/miden-objects/src/asset/vault/partial.rs +++ b/crates/miden-objects/src/asset/vault/partial.rs @@ -23,18 +23,11 @@ impl PartialVault { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns a new instance of a partial vault from the provided partial SMT. - /// - /// # Errors + /// Constructs a [`PartialVault`] from an [`AssetVault`] root. /// - /// Returns an error if: - /// - the provided SMT does not track only valid [`Asset`]s. - /// - the vault key at which the asset is stored does not match the vault key derived from the - /// asset. - pub fn new(partial_smt: PartialSmt) -> Result { - Self::validate_entries(partial_smt.entries())?; - - Ok(PartialVault { partial_smt }) + /// For conversion from an [`AssetVault`], prefer [`Self::new_minimal`] to be more explicit. + pub fn new(root: Word) -> Self { + PartialVault { partial_smt: PartialSmt::new(root) } } /// Converts an [`AssetVault`] into a partial vault representation. @@ -49,21 +42,10 @@ impl PartialVault { /// Converts an [`AssetVault`] into a partial vault representation. /// - /// The resulting [`PartialVault`] will contain only a single, unspecified key-value pair - /// in order to have the same root as the original storage map. Is it otherwise the most - /// _minimal_ representation of the asset vault. + /// The resulting [`PartialVault`] will represent the root of the asset vault, but not track any + /// key-value pairs, which means it is the most _minimal_ representation of the asset vault. pub fn new_minimal(vault: &AssetVault) -> Self { - let mut partial_vault = PartialVault::default(); - - // Construct a partial vault that tracks the empty word, but none of the assets that are - // actually in the asset tree. That way, the partial vault has the same root as the full - // vault. This is the most minimal and correct partial vault we can build. - // TODO: Workaround for https://github.com/0xMiden/miden-base/issues/1966. Fix when implemented. - partial_vault - .add(vault.open(AssetVaultKey::new_unchecked(Word::empty()))) - .expect("adding the first proof should never fail"); - - partial_vault + PartialVault::new(vault.root()) } // ACCESSORS @@ -170,6 +152,24 @@ impl PartialVault { } } +impl TryFrom for PartialVault { + type Error = PartialAssetVaultError; + + /// Returns a new instance of a partial vault from the provided partial SMT. + /// + /// # Errors + /// + /// Returns an error if: + /// - the provided SMT does not track only valid [`Asset`]s. + /// - the vault key at which the asset is stored does not match the vault key derived from the + /// asset. + fn try_from(partial_smt: PartialSmt) -> Result { + Self::validate_entries(partial_smt.entries())?; + + Ok(PartialVault { partial_smt }) + } +} + impl Serializable for PartialVault { fn write_into(&self, target: &mut W) { target.write(&self.partial_smt) @@ -178,9 +178,9 @@ impl Serializable for PartialVault { impl Deserializable for PartialVault { fn read_from(source: &mut R) -> Result { - let vault_partial_smt = source.read()?; + let partial_smt: PartialSmt = source.read()?; - PartialVault::new(vault_partial_smt) + PartialVault::try_from(partial_smt) .map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } @@ -203,7 +203,7 @@ mod tests { let proof = smt.open(&invalid_asset); let partial_smt = PartialSmt::from_proofs([proof.clone()])?; - let err = PartialVault::new(partial_smt).unwrap_err(); + let err = PartialVault::try_from(partial_smt).unwrap_err(); assert_matches!(err, PartialAssetVaultError::InvalidAssetInSmt { entry, .. } => { assert_eq!(entry, invalid_asset); }); @@ -219,7 +219,7 @@ mod tests { let proof = smt.open(&invalid_vault_key); let partial_smt = PartialSmt::from_proofs([proof.clone()])?; - let err = PartialVault::new(partial_smt).unwrap_err(); + let err = PartialVault::try_from(partial_smt).unwrap_err(); assert_matches!(err, PartialAssetVaultError::AssetVaultKeyMismatch { expected, actual } => { assert_eq!(actual, invalid_vault_key); assert_eq!(expected, asset.vault_key()); diff --git a/crates/miden-objects/src/block/partial_account_tree.rs b/crates/miden-objects/src/block/partial_account_tree.rs index ae3e761f89..34cfd20c01 100644 --- a/crates/miden-objects/src/block/partial_account_tree.rs +++ b/crates/miden-objects/src/block/partial_account_tree.rs @@ -10,7 +10,7 @@ use crate::errors::AccountTreeError; /// The partial sparse merkle tree containing the state commitments of accounts in the chain. /// /// This is the partial version of [`AccountTree`](crate::block::account_tree::AccountTree). -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct PartialAccountTree { smt: PartialSmt, } @@ -19,9 +19,10 @@ impl PartialAccountTree { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Creates a new, empty partial account tree. - pub fn new() -> Self { - PartialAccountTree { smt: PartialSmt::new() } + /// Creates a new partial account tree with the provided root that does not track any account + /// IDs. + pub fn new(root: Word) -> Self { + PartialAccountTree { smt: PartialSmt::new(root) } } /// Returns a new [`PartialAccountTree`] instantiated with the provided entries. @@ -34,8 +35,21 @@ impl PartialAccountTree { pub fn with_witnesses( witnesses: impl IntoIterator, ) -> Result { - let mut tree = Self::new(); + let mut witnesses = witnesses.into_iter(); + let Some(first_witness) = witnesses.next() else { + return Ok(Self::default()); + }; + + // Construct a partial account tree with the root of the first witness. + // SAFETY: This is guaranteed to _not_ result in a tree with more than one entry because + // the account witness type guarantees that it tracks zero or one entries. + let partial_smt = PartialSmt::from_proofs([first_witness.into_proof()]) + .map_err(AccountTreeError::TreeRootConflict)?; + let mut tree = PartialAccountTree { smt: partial_smt }; + + // Add all remaining witnesses to the tree, which validates the invariants of the account + // tree. for witness in witnesses { tree.track_account(witness)?; } @@ -98,21 +112,22 @@ impl PartialAccountTree { pub fn track_account(&mut self, witness: AccountWitness) -> Result<(), AccountTreeError> { let id_prefix = witness.id().prefix(); let id_key = account_id_to_smt_key(witness.id()); - let (path, leaf) = witness.into_proof().into_parts(); // If a leaf with the same prefix is already tracked by this partial tree, consider it an // error. // // We return an error even for empty leaves, because tracking the same ID prefix twice - // indicates that different IDs are attempted to be tracked. It would technically - // not violate the invariant of the tree that it only tracks zero or one entries per leaf, - // but since tracking the same ID twice should practically never happen, we return an error, - // out of an abundance of caution. + // indicates that different IDs are attempted to be tracked. It would technically not + // violate the invariant of the tree that it only tracks zero or one entries per leaf, but + // since tracking the same ID twice should practically never happen, we return an error, out + // of an abundance of caution. if self.smt.get_leaf(&id_key).is_ok() { return Err(AccountTreeError::DuplicateIdPrefix { duplicate_prefix: id_prefix }); } - self.smt.add_path(leaf, path).map_err(AccountTreeError::TreeRootConflict)?; + self.smt + .add_proof(witness.into_proof()) + .map_err(AccountTreeError::TreeRootConflict)?; Ok(()) } @@ -174,12 +189,6 @@ impl PartialAccountTree { } } -impl Default for PartialAccountTree { - fn default() -> Self { - Self::new() - } -} - #[cfg(test)] mod tests { use assert_matches::assert_matches; @@ -190,16 +199,15 @@ mod tests { use crate::block::account_tree::tests::setup_duplicate_prefix_ids; #[test] - fn insert_fails_on_duplicate_prefix() { + fn insert_fails_on_duplicate_prefix() -> anyhow::Result<()> { let mut full_tree = AccountTree::::default(); - let mut partial_tree = PartialAccountTree::new(); let [(id0, commitment0), (id1, commitment1)] = setup_duplicate_prefix_ids(); full_tree.insert(id0, commitment0).unwrap(); let witness = full_tree.open(id0); - partial_tree.track_account(witness).unwrap(); + let mut partial_tree = PartialAccountTree::with_witnesses([witness])?; partial_tree.insert(id0, commitment0).unwrap(); assert_eq!(partial_tree.get(id0).unwrap(), commitment0); @@ -215,17 +223,20 @@ mod tests { assert_matches!(err, AccountTreeError::DuplicateIdPrefix { duplicate_prefix } if duplicate_prefix == id0.prefix()); + + Ok(()) } #[test] fn insert_succeeds_on_multiple_updates() { let mut full_tree = AccountTree::::default(); - let mut partial_tree = PartialAccountTree::new(); let [(id0, commitment0), (_, commitment1)] = setup_duplicate_prefix_ids(); full_tree.insert(id0, commitment0).unwrap(); let witness = full_tree.open(id0); + let mut partial_tree = PartialAccountTree::new(full_tree.root()); + partial_tree.track_account(witness.clone()).unwrap(); assert_eq!( partial_tree.open(id0).unwrap(), @@ -245,7 +256,7 @@ mod tests { #[test] fn upsert_state_commitments_fails_on_untracked_key() { - let mut partial_tree = PartialAccountTree::new(); + let mut partial_tree = PartialAccountTree::default(); let [update, _] = setup_duplicate_prefix_ids(); let err = partial_tree.upsert_state_commitments([update]).unwrap_err(); @@ -273,12 +284,11 @@ mod tests { assert_eq!(proof0.leaf(), proof1.leaf()); let witness0 = - AccountWitness::new_unchecked(id0, proof0.get(&key0).unwrap(), proof0.into_parts().0); + AccountWitness::new(id0, proof0.get(&key0).unwrap(), proof0.into_parts().0).unwrap(); let witness1 = - AccountWitness::new_unchecked(id1, proof1.get(&key1).unwrap(), proof1.into_parts().0); + AccountWitness::new(id1, proof1.get(&key1).unwrap(), proof1.into_parts().0).unwrap(); - let mut partial_tree = PartialAccountTree::new(); - partial_tree.track_account(witness0).unwrap(); + let mut partial_tree = PartialAccountTree::with_witnesses([witness0]).unwrap(); let err = partial_tree.track_account(witness1).unwrap_err(); assert_matches!(err, AccountTreeError::DuplicateIdPrefix { duplicate_prefix, .. } diff --git a/crates/miden-objects/src/block/partial_nullifier_tree.rs b/crates/miden-objects/src/block/partial_nullifier_tree.rs index 9a9c2ab04a..d32d425976 100644 --- a/crates/miden-objects/src/block/partial_nullifier_tree.rs +++ b/crates/miden-objects/src/block/partial_nullifier_tree.rs @@ -12,13 +12,14 @@ use crate::note::Nullifier; /// The tree guarantees that once a nullifier has been inserted into the tree, its block number does /// not change. Note that inserting the nullifier multiple times with the same block number is /// valid. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct PartialNullifierTree(PartialSmt); impl PartialNullifierTree { - /// Creates a new, empty partial nullifier tree. - pub fn new() -> Self { - PartialNullifierTree(PartialSmt::new()) + /// Creates a new partial nullifier tree with the provided root that does not track any + /// nullifiers. + pub fn new(root: Word) -> Self { + PartialNullifierTree(PartialSmt::new(root)) } /// Returns a new [`PartialNullifierTree`] instantiated with the provided entries. @@ -30,13 +31,9 @@ impl PartialNullifierTree { pub fn with_witnesses( witnesses: impl IntoIterator, ) -> Result { - let mut tree = Self::new(); - - for witness in witnesses { - tree.track_nullifier(witness)?; - } - - Ok(tree) + PartialSmt::from_proofs(witnesses.into_iter().map(NullifierWitness::into_proof)) + .map_err(NullifierTreeError::TreeRootConflict) + .map(Self) } /// Adds the given nullifier witness to the partial tree and tracks it. Once a nullifier has @@ -100,12 +97,6 @@ impl PartialNullifierTree { } } -impl Default for PartialNullifierTree { - fn default() -> Self { - Self::new() - } -} - #[cfg(test)] mod tests { use assert_matches::assert_matches; @@ -138,9 +129,9 @@ mod tests { assert_ne!(stale_proof0.compute_root(), proof2.compute_root()); - let mut partial = PartialNullifierTree::new(); + let mut partial = + PartialNullifierTree::with_witnesses([NullifierWitness::new(stale_proof0)]).unwrap(); - partial.track_nullifier(NullifierWitness::new(stale_proof0)).unwrap(); let error = partial.track_nullifier(NullifierWitness::new(proof2)).unwrap_err(); assert_matches!(error, NullifierTreeError::TreeRootConflict(_)); @@ -157,8 +148,7 @@ mod tests { let witness = tree.open(&nullifier1); - let mut partial_tree = PartialNullifierTree::new(); - partial_tree.track_nullifier(witness).unwrap(); + let mut partial_tree = PartialNullifierTree::with_witnesses([witness]).unwrap(); // Attempt to insert nullifier 1 again at a different block number. let err = partial_tree.mark_spent([nullifier1], block2).unwrap_err(); @@ -179,7 +169,7 @@ mod tests { let mut tree = NullifierTree::with_entries([(nullifier1, block1), (nullifier2, block2)]).unwrap(); - let mut partial_tree = PartialNullifierTree::new(); + let mut partial_tree = PartialNullifierTree::new(tree.root()); for nullifier in [nullifier1, nullifier2, nullifier3] { let witness = tree.open(&nullifier); diff --git a/crates/miden-objects/src/note/script.rs b/crates/miden-objects/src/note/script.rs index 39c5b12537..457d72cfb5 100644 --- a/crates/miden-objects/src/note/script.rs +++ b/crates/miden-objects/src/note/script.rs @@ -93,7 +93,7 @@ impl From<&NoteScript> for Vec { let mut result = Vec::with_capacity(final_size); // Push the length, this is used to remove the padding later - result.push(Felt::from(script.entrypoint.as_u32())); + result.push(Felt::from(u32::from(script.entrypoint))); result.push(Felt::new(len as u64)); // A Felt can not represent all u64 values, so the data is encoded using u32. @@ -164,7 +164,7 @@ impl TryFrom> for NoteScript { impl Serializable for NoteScript { fn write_into(&self, target: &mut W) { self.mast.write_into(target); - target.write_u32(self.entrypoint.as_u32()); + target.write_u32(u32::from(self.entrypoint)); } } diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index 246f03cf80..27739a1a2b 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -328,7 +328,7 @@ impl TransactionScript { impl Serializable for TransactionScript { fn write_into(&self, target: &mut W) { self.mast.write_into(target); - target.write_u32(self.entrypoint.as_u32()); + target.write_u32(u32::from(self.entrypoint)); } } diff --git a/crates/miden-testing/src/kernel_tests/tx/mod.rs b/crates/miden-testing/src/kernel_tests/tx/mod.rs index 95c2dc5aee..781bae46dc 100644 --- a/crates/miden-testing/src/kernel_tests/tx/mod.rs +++ b/crates/miden-testing/src/kernel_tests/tx/mod.rs @@ -73,8 +73,12 @@ pub trait ExecutionOutputExt { /// Reads an element from the stack. fn get_stack_element(&self, idx: usize) -> Felt; - /// Reads a [`Word`] from the stack. - fn get_stack_word(&self, index: usize) -> Word; + /// Reads a [`Word`] from the stack in big-endian (reversed) order. + fn get_stack_word_be(&self, index: usize) -> Word; + + /// Reads a [`Word`] from the stack in little-endian (memory) order. + #[allow(dead_code)] + fn get_stack_word_le(&self, index: usize) -> Word; /// Reads the [`Word`] of the input note's memory identified by the index at the provided /// `offset`. @@ -98,8 +102,12 @@ impl ExecutionOutputExt for ExecutionOutput { *self.stack.get(index).expect("index must be in bounds") } - fn get_stack_word(&self, index: usize) -> Word { - self.stack.get_stack_word(index).expect("index must be in bounds") + fn get_stack_word_be(&self, index: usize) -> Word { + self.stack.get_stack_word_be(index).expect("index must be in bounds") + } + + fn get_stack_word_le(&self, index: usize) -> Word { + self.stack.get_stack_word_le(index).expect("index must be in bounds") } } @@ -134,10 +142,10 @@ pub fn create_mock_notes_procedure(notes: &[Note]) -> String { " # populate note {idx} push.{metadata} - push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_METADATA_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_METADATA_OFFSET} add add mem_storew_be dropw push.{recipient} - push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_RECIPIENT_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_RECIPIENT_OFFSET} add add mem_storew_be dropw push.{num_assets} push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_NUM_ASSETS_OFFSET} add add mem_store @@ -146,7 +154,7 @@ pub fn create_mock_notes_procedure(notes: &[Note]) -> String { push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_DIRTY_FLAG_OFFSET} add add mem_store push.{first_asset} - push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_ASSETS_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET} push.{note_offset} push.{OUTPUT_NOTE_ASSETS_OFFSET} add add mem_storew_be dropw ", idx = idx, metadata = metadata, diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 10b8f23ffa..a4f5a4398a 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -437,22 +437,22 @@ async fn test_get_map_item() -> miette::Result<()> { let exec_output = &mut tx_context.execute_code(&code).await?; assert_eq!( - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), value, "get_map_item result doesn't match the expected value", ); assert_eq!( - exec_output.get_stack_word(4), + exec_output.get_stack_word_be(4), Word::empty(), "The rest of the stack must be cleared", ); assert_eq!( - exec_output.get_stack_word(8), + exec_output.get_stack_word_be(8), Word::empty(), "The rest of the stack must be cleared", ); assert_eq!( - exec_output.get_stack_word(12), + exec_output.get_stack_word_be(12), Word::empty(), "The rest of the stack must be cleared", ); @@ -499,9 +499,21 @@ async fn test_get_storage_slot_type() -> miette::Result<()> { assert_eq!(exec_output.get_stack_element(1), ZERO, "the rest of the stack is empty"); assert_eq!(exec_output.get_stack_element(2), ZERO, "the rest of the stack is empty"); assert_eq!(exec_output.get_stack_element(3), ZERO, "the rest of the stack is empty"); - assert_eq!(exec_output.get_stack_word(4), Word::empty(), "the rest of the stack is empty"); - assert_eq!(exec_output.get_stack_word(8), Word::empty(), "the rest of the stack is empty"); - assert_eq!(exec_output.get_stack_word(12), Word::empty(), "the rest of the stack is empty"); + assert_eq!( + exec_output.get_stack_word_be(4), + Word::empty(), + "the rest of the stack is empty" + ); + assert_eq!( + exec_output.get_stack_word_be(8), + Word::empty(), + "the rest of the stack is empty" + ); + assert_eq!( + exec_output.get_stack_word_be(12), + Word::empty(), + "the rest of the stack is empty" + ); } Ok(()) @@ -595,12 +607,12 @@ async fn test_set_map_item() -> miette::Result<()> { assert_eq!( new_storage_map.root(), - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), "get_item must return the new updated value", ); assert_eq!( storage_item.slot.value(), - exec_output.get_stack_word(4), + exec_output.get_stack_word_be(4), "The original value stored in the map doesn't match the expected value", ); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 3d2b98b10f..ae4122d326 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -200,7 +200,7 @@ async fn test_active_note_get_assets() -> anyhow::Result<()> { code += &format!( " # assert the asset is correct - dup padw movup.4 mem_loadw push.{asset} assert_eqw push.4 add + dup padw movup.4 mem_loadw_be push.{asset} assert_eqw push.4 add ", asset = Word::from(asset) ); @@ -326,7 +326,7 @@ async fn test_active_note_get_inputs() -> anyhow::Result<()> { r#" # assert the inputs are correct # => [dest_ptr] - dup padw movup.4 mem_loadw push.{inputs_word} assert_eqw.err="inputs are incorrect" + dup padw movup.4 mem_loadw_be push.{inputs_word} assert_eqw.err="inputs are incorrect" # => [dest_ptr] push.4 add @@ -499,7 +499,7 @@ async fn test_active_note_get_serial_number() -> anyhow::Result<()> { let exec_output = tx_context.execute_code(code).await?; let serial_number = tx_context.input_notes().get_note(0).note().serial_num(); - assert_eq!(exec_output.get_stack_word(0), serial_number); + assert_eq!(exec_output.get_stack_word_be(0), serial_number); Ok(()) } @@ -538,6 +538,6 @@ async fn test_active_note_get_script_root() -> anyhow::Result<()> { let exec_output = tx_context.execute_code(code).await?; let script_root = tx_context.input_notes().get_note(0).note().script().root(); - assert_eq!(exec_output.get_stack_word(0), script_root); + assert_eq!(exec_output.get_stack_word_be(0), script_root); Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs index f1a35db25d..092e2608ac 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset.rs @@ -41,7 +41,7 @@ async fn test_create_fungible_asset_succeeds() -> anyhow::Result<()> { let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); assert_eq!( - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), Word::from([ Felt::new(FUNGIBLE_ASSET_AMOUNT), Felt::new(0), @@ -80,7 +80,7 @@ async fn test_create_non_fungible_asset_succeeds() -> anyhow::Result<()> { ); let exec_output = &tx_context.execute_code(&code).await?; - assert_eq!(exec_output.get_stack_word(0), Word::from(non_fungible_asset)); + assert_eq!(exec_output.get_stack_word_be(0), Word::from(non_fungible_asset)); Ok(()) } @@ -109,6 +109,6 @@ async fn test_validate_non_fungible_asset() -> anyhow::Result<()> { let exec_output = &tx_context.execute_code(&code).await?; - assert_eq!(exec_output.get_stack_word(0), non_fungible_asset); + assert_eq!(exec_output.get_stack_word_be(0), non_fungible_asset); Ok(()) } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs index 83661b5f61..37b492d8cc 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs @@ -200,7 +200,7 @@ async fn test_add_fungible_asset_success() -> anyhow::Result<()> { let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), Word::from(account_vault.add_asset(add_fungible_asset).unwrap()) ); @@ -278,7 +278,7 @@ async fn test_add_non_fungible_asset_success() -> anyhow::Result<()> { let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), Word::from(account_vault.add_asset(add_non_fungible_asset)?) ); @@ -357,7 +357,7 @@ async fn test_remove_fungible_asset_success_no_balance_remaining() -> anyhow::Re let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), Word::from(account_vault.remove_asset(remove_fungible_asset).unwrap()) ); @@ -441,7 +441,7 @@ async fn test_remove_fungible_asset_success_balance_remaining() -> anyhow::Resul let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), Word::from(account_vault.remove_asset(remove_fungible_asset).unwrap()) ); @@ -526,7 +526,7 @@ async fn test_remove_non_fungible_asset_success() -> anyhow::Result<()> { let exec_output = &tx_context.execute_code(&code).await?; assert_eq!( - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), Word::from(account_vault.remove_asset(non_fungible_asset).unwrap()) ); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs index be8921c020..915c1dd01c 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_fpi.rs @@ -161,7 +161,7 @@ async fn test_fpi_memory_single_account() -> anyhow::Result<()> { let exec_output = tx_context.execute_code(&code).await?; assert_eq!( - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), storage_slots[0].value(), "Value at the top of the stack (value in the storage at index 0) should be equal [1, 2, 3, 4]", ); @@ -215,7 +215,7 @@ async fn test_fpi_memory_single_account() -> anyhow::Result<()> { let exec_output = tx_context.execute_code(&code).await.unwrap(); assert_eq!( - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), STORAGE_LEAVES_2[0].1, "Value at the top of the stack should be equal [1, 2, 3, 4]", ); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index 25595dd181..b903b3d6b0 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -231,7 +231,7 @@ async fn test_get_assets() -> anyhow::Result<()> { check_assets_code.push_str(&format!( r#" # load the asset stored in memory - padw dup.4 mem_loadw + padw dup.4 mem_loadw_be # => [STORED_ASSET, dest_ptr, note_index] # assert the asset diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index e41510d6b7..3e2e0a466a 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -72,7 +72,7 @@ async fn test_note_setup() -> anyhow::Result<()> { exec.prologue::prepare_transaction exec.note::prepare_note # => [note_script_root_ptr, NOTE_ARGS, pad(11), pad(16)] - padw movup.4 mem_loadw + padw movup.4 mem_loadw_be # => [SCRIPT_ROOT, NOTE_ARGS, pad(11), pad(16)] # truncate the stack @@ -160,8 +160,8 @@ async fn test_note_script_and_note_args() -> miette::Result<()> { tx_context.set_tx_args(tx_args); let exec_output = tx_context.execute_code(code).await.unwrap(); - assert_eq!(exec_output.get_stack_word(0), note_args[0]); - assert_eq!(exec_output.get_stack_word(4), note_args[1]); + assert_eq!(exec_output.get_stack_word_be(0), note_args[0]); + assert_eq!(exec_output.get_stack_word_be(4), note_args[1]); Ok(()) } @@ -209,10 +209,10 @@ async fn test_build_recipient() -> anyhow::Result<()> { begin # put the values that will be hashed into the memory - push.{word_1} push.{base_addr} mem_storew dropw - push.{word_2} push.{addr_1} mem_storew dropw - push.{word_3} push.{addr_2} mem_storew dropw - push.{word_4} push.{addr_3} mem_storew dropw + push.{word_1} push.{base_addr} mem_storew_be dropw + push.{word_2} push.{addr_1} mem_storew_be dropw + push.{word_3} push.{addr_2} mem_storew_be dropw + push.{word_4} push.{addr_3} mem_storew_be dropw # Test with 4 values push.{script_root} # SCRIPT_ROOT @@ -298,10 +298,10 @@ async fn test_compute_inputs_commitment() -> anyhow::Result<()> { begin # put the values that will be hashed into the memory - push.{word_1} push.{base_addr} mem_storew dropw - push.{word_2} push.{addr_1} mem_storew dropw - push.{word_3} push.{addr_2} mem_storew dropw - push.{word_4} push.{addr_3} mem_storew dropw + push.{word_1} push.{base_addr} mem_storew_be dropw + push.{word_2} push.{addr_1} mem_storew_be dropw + push.{word_3} push.{addr_2} mem_storew_be dropw + push.{word_4} push.{addr_3} mem_storew_be dropw # push the number of values and pointer to the inputs on the stack push.5.4000 @@ -418,7 +418,7 @@ async fn test_build_metadata() -> miette::Result<()> { let exec_output = tx_context.execute_code(&code).await.unwrap(); - let metadata_word = exec_output.get_stack_word(0); + let metadata_word = exec_output.get_stack_word_be(0); assert_eq!(Word::from(test_metadata), metadata_word, "failed in iteration {iteration}"); } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 341e77d6d1..b004a03ff7 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -381,7 +381,7 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { "Validate the output note 1 metadata", ); - assert_eq!(exec_output.get_stack_word(0), expected_output_notes_commitment); + assert_eq!(exec_output.get_stack_word_be(0), expected_output_notes_commitment); Ok(()) } @@ -958,7 +958,7 @@ async fn test_get_assets() -> anyhow::Result<()> { check_assets_code.push_str(&format!( r#" # load the asset stored in memory - padw dup.4 mem_loadw + padw dup.4 mem_loadw_be # => [STORED_ASSET, dest_ptr, note_index] # assert the asset diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index b619550626..909492c73c 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -151,7 +151,7 @@ async fn test_block_procedures() -> anyhow::Result<()> { let exec_output = &tx_context.execute_code(code).await?; assert_eq!( - exec_output.get_stack_word(0), + exec_output.get_stack_word_be(0), tx_context.tx_inputs().block_header().commitment(), "top word on the stack should be equal to the block header commitment" ); diff --git a/crates/miden-testing/src/mock_host.rs b/crates/miden-testing/src/mock_host.rs index 0787061bf7..ded9a6e663 100644 --- a/crates/miden-testing/src/mock_host.rs +++ b/crates/miden-testing/src/mock_host.rs @@ -54,7 +54,7 @@ impl<'store> MockHost<'store> { let stdlib_handlers = StdLibrary::default() .handlers() .into_iter() - .map(|(handler_event_id, _)| handler_event_id); + .map(|(handler_event_name, _)| handler_event_name.to_event_id()); let mut handled_events = BTreeSet::from_iter(stdlib_handlers); // The default set of transaction events that are always handled. diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index bb84084352..f6b5b1c7d3 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -104,7 +104,7 @@ pub fn create_p2any_note( " # add first asset - padw dup.4 mem_loadw + padw dup.4 mem_loadw_be padw swapw padw padw swapdw call.wallet::receive_asset dropw movup.12 @@ -117,7 +117,7 @@ pub fn create_p2any_note( # add next asset add.4 dup movdn.13 - padw movup.4 mem_loadw + padw movup.4 mem_loadw_be call.wallet::receive_asset dropw movup.12 # => [dest_ptr, pad(12)]", diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 86e8d73fec..6a12481a59 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -369,12 +369,12 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul # Store note inputs in memory at address 0 # First word: inputs[0..4] push.{input0}.{input1}.{input2}.{input3} - mem_storew.0 dropw + mem_storew_be.0 dropw # Memory[0] = [input0, input1, input2, input3] # Second word: inputs[4..8] push.{input4}.{input5}.{input6}.{input7} - mem_storew.4 dropw + mem_storew_be.4 dropw # Memory[1] = [input4, input5, input6, input7] push.8 push.0 diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index 7c066ad254..c85b8037da 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -192,7 +192,7 @@ where let advice_provider = output.advice; // The stack is not necessary since it is being reconstructed when re-executing. - let (_stack, advice_map, merkle_store) = advice_provider.into_parts(); + let (_stack, advice_map, merkle_store, _pc_requests) = advice_provider.into_parts(); let advice_inputs = AdviceInputs { map: advice_map, store: merkle_store, diff --git a/crates/miden-tx/src/executor/notes_checker.rs b/crates/miden-tx/src/executor/notes_checker.rs index eaebc8048f..e6a033cb60 100644 --- a/crates/miden-tx/src/executor/notes_checker.rs +++ b/crates/miden-tx/src/executor/notes_checker.rs @@ -342,7 +342,7 @@ where // Set the advice inputs from the successful execution as advice inputs for // reexecution. This avoids calls to the data store (to load data lazily) that have // already been done as part of this execution. - let (_, advice_map, merkle_store) = execution_output.advice.into_parts(); + let (_, advice_map, merkle_store, _) = execution_output.advice.into_parts(); let advice_inputs = AdviceInputs { map: advice_map, store: merkle_store, diff --git a/crates/miden-tx/src/host/account_procedures.rs b/crates/miden-tx/src/host/account_procedures.rs index e7fbbd0300..c4c664977f 100644 --- a/crates/miden-tx/src/host/account_procedures.rs +++ b/crates/miden-tx/src/host/account_procedures.rs @@ -82,7 +82,7 @@ impl AccountProcedureIndexMap { .expect("active account code commitment was not initialized") }; - let proc_root = process.get_stack_word(1); + let proc_root = process.get_stack_word_be(1); self.0 .get(&code_commitment) diff --git a/crates/miden-tx/src/host/link_map.rs b/crates/miden-tx/src/host/link_map.rs index 52347109d4..de96421edf 100644 --- a/crates/miden-tx/src/host/link_map.rs +++ b/crates/miden-tx/src/host/link_map.rs @@ -44,7 +44,7 @@ impl<'process> LinkMap<'process> { /// Advice stack state after: [set_operation, entry_ptr] pub fn handle_set_event(process: &ProcessState<'_>) -> Result, EventError> { let map_ptr = process.get_stack_item(1); - let map_key = process.get_stack_word(2); + let map_key = process.get_stack_word_be(2); let mem_viewer = MemoryViewer::ProcessState(process); let link_map = LinkMap::new(map_ptr, &mem_viewer); @@ -63,7 +63,7 @@ impl<'process> LinkMap<'process> { /// Advice stack state after: [get_operation, entry_ptr] pub fn handle_get_event(process: &ProcessState<'_>) -> Result, EventError> { let map_ptr = process.get_stack_item(1); - let map_key = process.get_stack_word(2); + let map_key = process.get_stack_word_be(2); let mem_viewer = MemoryViewer::ProcessState(process); let link_map = LinkMap::new(map_ptr, &mem_viewer); diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index f25e78c252..c62091420c 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -410,7 +410,7 @@ where &self, process: &ProcessState, ) -> Result { - let account_id_word = process.get_stack_word(1); + let account_id_word = process.get_stack_word_be(1); let account_id = AccountId::try_from([account_id_word[3], account_id_word[2]]).map_err(|err| { TransactionKernelError::other_with_source( @@ -436,8 +436,8 @@ where &self, process: &ProcessState, ) -> Result { - let message = process.get_stack_word(1); - let pub_key_hash = process.get_stack_word(5); + let message = process.get_stack_word_be(1); + let pub_key_hash = process.get_stack_word_be(5); let signature_key = Hasher::merge(&[pub_key_hash, message]); let tx_summary = self.build_tx_summary(process, message)?; @@ -482,7 +482,7 @@ where /// MESSAGE -> [SALT, OUTPUT_NOTES_COMMITMENT, INPUT_NOTES_COMMITMENT, ACCOUNT_DELTA_COMMITMENT] /// ``` fn on_unauthorized(&self, process: &ProcessState) -> TransactionKernelError { - let msg = process.get_stack_word(1); + let msg = process.get_stack_word_be(1); let tx_summary = match self.build_tx_summary(process, msg) { Ok(s) => s, @@ -510,7 +510,7 @@ where &self, process: &ProcessState, ) -> Result { - let fee_asset = process.get_stack_word(1); + let fee_asset = process.get_stack_word_be(1); let fee_asset = FungibleAsset::try_from(fee_asset) .map_err(TransactionKernelError::FailedToConvertFeeAsset)?; @@ -531,11 +531,11 @@ where &mut self, process: &ProcessState, ) -> Result { - let metadata_word = process.get_stack_word(1); + let metadata_word = process.get_stack_word_be(1); let metadata = NoteMetadata::try_from(metadata_word) .map_err(TransactionKernelError::MalformedNoteMetadata)?; - let recipient_digest = process.get_stack_word(6); + let recipient_digest = process.get_stack_word_be(6); let note_idx = process.get_stack_item(10).as_int() as usize; // try to read the full recipient from the advice provider @@ -588,7 +588,7 @@ where assert!(note_idx < self.output_notes.len() as u64); let node_idx = note_idx as usize; - let asset_word = process.get_stack_word(1); + let asset_word = process.get_stack_word_be(1); let asset = Asset::try_from(asset_word).map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_note_before_add_asset", @@ -652,10 +652,10 @@ where } // get the value to which the slot is being updated - let new_slot_value = process.get_stack_word(2); + let new_slot_value = process.get_stack_word_be(2); // get the current value for the slot - let current_slot_value = process.get_stack_word(6); + let current_slot_value = process.get_stack_word_be(6); self.account_delta.storage().set_item( slot_index.as_int() as u8, @@ -674,8 +674,8 @@ where &self, process: &ProcessState, ) -> Result { - let map_key = process.get_stack_word(1); - let current_map_root = process.get_stack_word(5); + let map_key = process.get_stack_word_be(1); + let current_map_root = process.get_stack_word_be(5); let slot_index = process.get_stack_item(9); self.on_account_storage_before_get_or_set_map_item( @@ -695,8 +695,8 @@ where process: &ProcessState, ) -> Result { let slot_index = process.get_stack_item(1); - let map_key = process.get_stack_word(2); - let current_map_root = process.get_stack_word(10); + let map_key = process.get_stack_word_be(2); + let current_map_root = process.get_stack_word_be(10); self.on_account_storage_before_get_or_set_map_item( slot_index, @@ -786,13 +786,13 @@ where } // get the KEY to which the slot is being updated - let key = process.get_stack_word(2); + let key = process.get_stack_word_be(2); // get the previous VALUE of the slot - let prev_map_value = process.get_stack_word(6); + let prev_map_value = process.get_stack_word_be(6); // get the VALUE to which the slot is being updated - let new_map_value = process.get_stack_word(10); + let new_map_value = process.get_stack_word_be(10); self.account_delta.storage().set_map_item( slot_index.as_int() as u8, @@ -815,7 +815,7 @@ where &mut self, process: &ProcessState, ) -> Result<(), TransactionKernelError> { - let asset: Asset = process.get_stack_word(1).try_into().map_err(|source| { + let asset: Asset = process.get_stack_word_be(1).try_into().map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_after_add_asset", source, @@ -837,7 +837,7 @@ where &self, process: &ProcessState, ) -> Result { - let asset_word = process.get_stack_word(1); + let asset_word = process.get_stack_word_be(1); let asset = Asset::try_from(asset_word).map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_before_add_or_remove_asset", @@ -875,7 +875,7 @@ where &mut self, process: &ProcessState, ) -> Result<(), TransactionKernelError> { - let asset: Asset = process.get_stack_word(1).try_into().map_err(|source| { + let asset: Asset = process.get_stack_word_be(1).try_into().map_err(|source| { TransactionKernelError::MalformedAssetInEventHandler { handler: "on_account_vault_after_remove_asset", source, @@ -897,7 +897,7 @@ where &self, process: &ProcessState, ) -> Result { - let stack_top = process.get_stack_word(1); + let stack_top = process.get_stack_word_be(1); let faucet_id = AccountId::try_from([stack_top[3], stack_top[2]]).map_err(|err| { TransactionKernelError::other_with_source( "failed to convert faucet ID word into faucet ID", @@ -923,7 +923,7 @@ where &self, process: &ProcessState, ) -> Result { - let asset_word = process.get_stack_word(1); + let asset_word = process.get_stack_word_be(1); let asset = Asset::try_from(asset_word).map_err(|err| { TransactionKernelError::other_with_source("provided asset is not a valid asset", err) })?; @@ -1132,7 +1132,7 @@ fn advice_provider_has_merkle_path( ) -> Result { match process .advice_provider() - .get_merkle_path(root, &Felt::from(TREE_DEPTH), &leaf_index) + .get_merkle_path(root, Felt::from(TREE_DEPTH), leaf_index) { // Merkle path is already in the store; consider the event handled. Ok(_) => Ok(true), From 8f11d0686786fda6598416f9aebfc51cec1a1266 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 4 Nov 2025 16:28:43 +0800 Subject: [PATCH 125/133] feat: Rework `Address` docs (#2047) * feat: Rework address docs after refactor * chore: add changelog * chore: Remove address summary description --- CHANGELOG.md | 1 + docs/src/account/address.md | 100 ++++++++++++++++++++---------------- docs/src/account/id.md | 4 ++ 3 files changed, 60 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7192c2ce7c..b0a06ad944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ - [BREAKING] Added `get_note_script()` method to `DataStore` trait to enable lazy loading of note scripts during transaction execution ([#1995](https://github.com/0xMiden/miden-base/pull/1995)). - [BREAKING] Separate account APIs in `miden::account` into `active_account` and `native_account` ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). - [BREAKING] Remove `miden::account::get_native_nonce` procedure ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). +- [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032), [#2047](https://github.com/0xMiden/miden-base/pull/2047)). - [BREAKING] Refactor `PartialVault`, `PartialStorageMap`, `PartialAccountTree` and `PartialNullifierTree` to allow construction from a root ([#2042](https://github.com/0xMiden/miden-base/pull/2042)). - [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032)). diff --git a/docs/src/account/address.md b/docs/src/account/address.md index 08ecb059ad..79b457c3c3 100644 --- a/docs/src/account/address.md +++ b/docs/src/account/address.md @@ -4,14 +4,9 @@ sidebar_position: 3 # Address -:::note -A human-readable identifier for `Account`s or public keys. -::: - - ## Purpose -An address is an identifier that facilitates sending and receiving of [notes](../note). It serves three main purposes explained in this section. +An address is an extension to account IDs and other identifiers that facilitates sending and receiving of [notes](../note). It serves three main purposes explained in this section. ### Communicating receiver information @@ -21,14 +16,14 @@ The receiver can choose to disclose various pieces of information that control h Consider a few examples that use different address mechanisms: -- The [Pay-to-ID note](../note#p2id-pay-to-id): the note itself can only be consumed if the account ID encoded in the note details matches the ID of the account that tries to consume it. To receive a P2ID note, the receiver should communicate an `Address::AccountId` type to the sender. -- A "Pay-to-PoW" note that can only be consumed if the receiver can provide a valid seed such that the hash of the seed results in a value with n leading zero bits. The receiver communicates an `Address::PoW` type to the sender, which encodes the target number of leading zero bits (and a salt to avoid re-use of the same seed).* -- A "Pay-to-Public-Key" note that stores a public (signature) key and checks if the receiver can provide a valid cryptographic signature for that key. The `Address::PublicKey` type must encode the public key.* +- The [Pay-to-ID note](../note#p2id-pay-to-id): the note itself can only be consumed if the account ID encoded in the note details matches the ID of the account that tries to consume it. To receive a P2ID note, the receiver should communicate an `AddressId::AccountId` type to the sender. +- A "Pay-to-PoW" note that can only be consumed if the receiver can provide a valid seed such that the hash of the seed results in a value with n leading zero bits. The receiver communicates an `AddressId::PoW` type to the sender, which encodes the target number of leading zero bits (and a salt to avoid re-use of the same seed).* +- A "Pay-to-Public-Key" note that stores a public (signature) key and checks if the receiver can provide a valid cryptographic signature for that key. The `AddressId::PublicKey` type must encode the public key.* These different address mechanisms provide different levels of privacy and security: -- `Address::AccountId`: the receiver is uniquely identifiable, but they are the only ones who can consume the note. -- `Address::PoW`: the receiver is not revealed publicly, but potentially many entities can consume the note. The receiver has an advantage by specifying the salt. -- `Address::PublicKey`: the receiver `AccountId` is not revealed publicly, only their public key. A fresh `Address::PublicKey` can be used for receiving each note, resulting in increased privacy. +- `AddressId::AccountId`: the receiver is uniquely identifiable, but they are the only ones who can consume the note. +- `AddressId::PoW`: the receiver is not revealed publicly, but potentially many entities can consume the note. The receiver has an advantage by specifying the salt. +- `AddressId::PublicKey`: the receiver `AccountId` is not revealed publicly, only their public key. A fresh `AddressId::PublicKey` can be used for receiving each note, resulting in increased privacy. :::note The "Pay-to-PoW" and "Pay-to-Public-Key" notes and the corresponding address types are for illustration purposes only. They are not part of the Miden library. @@ -40,7 +35,7 @@ For notes which are sent privately, the sender needs to communicate the full not Instead, our Miden client connects to a _Note Transport Layer_, which stores encrypted note details together with the associated public metadata for each note. The receiver can query the Note Transport Layer for `NoteTag`s they are interested in. Typically, a `NoteTag` encodes a few leading bits (14 by default) of the receiver's `AccountId`. Querying the Note Transport Layer for 14-bit `NoteTag`s reduces the receiver's privacy, but at the same time allows them to perform less work downloading and trial-decrypting the notes than if fewer bits were encoded. -With an `Address`, e.g. the [`Address::AccountId`](./address#addressaccountid) variant, the receiver could specify how many bits of their `AccountId` they want to disclose to the sender and thus choose their level of privacy. +With an `Address`, e.g. the [`AddressId::AccountId`](./address#addressaccountid) variant, the receiver could specify how many bits of their `AccountId` they want to disclose to the sender and thus choose their level of privacy. ### Account interface discovery @@ -48,49 +43,64 @@ An address allows the sender of the note to easily discover the interface of the If a sender wants to create a note, it is up to them to check whether the receiver account has an interface that it compatible with that note. The notion of an address doesn't exist at protocol level and so it is up to wallets or clients to implement this interface compatibility check. -## Relationship to Identifiers +## Structure + +An address consists of two parts: +- An identifier that determines what the address fundamentally points to, e.g. an account ID or, in the future, a public key. +- Routing parameters, that customize how a sender creates notes for the receiver, or in other words, how they are routed. + +The separation between these two parts is represented by an underscore (`_`) in the encoded address: + +```text +mm1arp0azyk9jugtgpnnhle8daav58nczzr_qpgqqwcfx0p + | | + account ID routing parameters +``` + +### Relationship to Identifiers -An address can encode exactly one account interface, which is a deliberate limitation to keep address sizes small. Users can generate multiple addresses for the same identifier like account ID or public key, in order to communicate different interfaces to senders. In other words, there could be multiple different addresses that point to the same account, each encoding a different interface. So, the relationship from addresses to their underlying identifiers is n-to-1. +The routing parameters in an address can encode exactly one account interface, which is a deliberate limitation to keep the size of addresses small. Users can generate multiple addresses for the same identifier like account ID or public key, in order to communicate different interfaces to senders. In other words, there could be multiple different addresses that point to the same account, each encoding a different interface. So, the relationship from addresses to their underlying identifiers is n-to-1. -## Types & Interfaces +As an example, these two addresses contain the same account ID but different routing parameters: -An address encodes an address type and an address interface: -- The type determines what the address fundamentally points to, e.g. an account ID or, in the future, a public key. -- The interface informs the sender of the capabilities of the receiver's account. +```text +mm1arp0azyk9jugtgpnnhle8daav58nczzr_qpgqqwcfx0p +mm1arp0azyk9jugtgpnnhle8daav58nczzr_qzsqqd4avz7 +``` + +### Address Types + +The supported **address types** are: +- `AddressId::AccountId` (type `232`): An address pointing to an account ID. + - Choosing `232` as the type byte means that all addresses that encode an account ID start with `mm1a`, where `a` conveniently indicates "account". :::note Adding a public key-based address type is planned. ::: -The currently supported **address types** are: -- `Address::AccountId` (type `0`): An address pointing to an account ID. +### Routing Parameters + +The supported routing parameters are detailed in this section. + +:::note +Adding an encryption key routing parameter is planned. +::: + +#### Address Interface + +The address interface informs the sender of the capabilities of the [receiver account's interface](./code#interface). -The currently supported **address interfaces** are: -- `Unspecified` (type `0`): No interface is specified. Used for addresses where the interface is unknown. -- `BasicWallet` (type `1`): The standard basic wallet interface. See the [account code](./code#interface) docs for details. +The supported **address interfaces** are: +- `BasicWallet` (type `0`): The standard basic wallet interface. See the [account code](./code#interface) docs for details. -### `Address::AccountId` +#### Note Tag Length -The account ID address points to an account ID and also allows specifying the [note tag](../note#note-discovery) length. This tag length preference determines how many bits of the account ID are encoded into note tags of notes targeted to this address. This lets the owner of the account choose their level of privacy. A higher tag length makes the account more uniquely identifiable and reduces privacy, while a shorter length increases privacy at the cost of matching more notes published onchain. +The note tag length routing parameter allows specifying the length of the [note tag](../note#note-discovery) that the sender should create. This parameter determines how many bits of the account ID are encoded into note tags of notes targeted to this address. This lets the owner of the account choose their level of privacy. A higher tag length makes the address ID more uniquely identifiable and reduces privacy, while a shorter length increases privacy at the cost of matching more notes published onchain. ## Encoding -An address is encoded in [**bech32 format**](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki), which has the following benefits: -- Built-in error detection via checksum algorithm -- Human-readable prefix indicates network type -- Less prone to errors when typed or spoken compared to hex format - -Examples of bech32-encoded addresses that encode the same `Address::AccountId` are: -- `mm1qqttmuqgxur0jup8j8luck774rcqq58se2m`, with the `Unspecified` interface. -- `mm1qqttmuqgxur0jup8j8luck774rcqz8z36ek`, with the `BasicWallet` interface. - -The structure of a bech32-encoded address is: -- [Human-readable prefix](https://github.com/satoshilabs/slips/blob/master/slip-0173.md) that -determines the network: - - `mm` (indicates **M**iden **M**ainnet) - - `mtst` (indicates Miden Testnet) - - `mdev` (indicates Miden Devnet) -- Separator: `1` -- Data part with integrated checksum - -The data part is where the underlying address type is encoded (e.g. `Address::AccountId` with `BasicWallet`). +The two parts of an address are encoded as follows: +- The identifier is encoded in [**bech32**](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki). See the [account ID encoding](id.md#encoding) section for details. +- The routing parameters are encoded in bech32 as well, but without the HRP or `1` separator. + - This means the routing parameter string's alphabet is consistent with that of the address ID. + - It also means the routing parameters have their own checksum, which is important so address ID and routing parameters can be separated at any time without causing validation issues. diff --git a/docs/src/account/id.md b/docs/src/account/id.md index 92cc68db4d..ee8362b429 100644 --- a/docs/src/account/id.md +++ b/docs/src/account/id.md @@ -45,6 +45,10 @@ Users can choose whether their accounts are stored publicly or privately. The pr ## Encoding +:::info +Bech32 is the preferred encoding format and should be used for user-facing applications like wallets or websites. +::: + An `Account` ID can be encoded in different formats: 1. [**Bech32**](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) (user-facing): From 68401e16ae199cfcaaea12de124ab7be55ef1a9b Mon Sep 17 00:00:00 2001 From: Marti Date: Tue, 4 Nov 2025 10:47:23 +0100 Subject: [PATCH 126/133] chore: expose "get_procedures" as public (#2048) --- crates/miden-objects/src/account/component/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/miden-objects/src/account/component/mod.rs b/crates/miden-objects/src/account/component/mod.rs index a2bae654b8..22874bc06e 100644 --- a/crates/miden-objects/src/account/component/mod.rs +++ b/crates/miden-objects/src/account/component/mod.rs @@ -209,7 +209,7 @@ impl AccountComponent { } /// Returns a vector of tuples (digest, is_auth) for all procedures in this component. - pub(crate) fn get_procedures(&self) -> Vec<(Word, bool)> { + pub fn get_procedures(&self) -> Vec<(Word, bool)> { let mut procedures = Vec::new(); for module in self.library.module_infos() { for (_, procedure_info) in module.procedures() { From 543194e0a03446c99828dfb708b28540bcc7a7a9 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:26:52 -0800 Subject: [PATCH 127/133] refactor: account auth struct improvements (#1994) --- CHANGELOG.md | 23 +- .../src/account/auth/rpo_falcon_512.rs | 3 +- .../src/account/auth/rpo_falcon_512_acl.rs | 9 +- .../account/auth/rpo_falcon_512_multisig.rs | 3 +- .../src/account/faucets/basic_fungible.rs | 3 +- .../src/account/interface/component.rs | 8 +- .../miden-lib/src/account/interface/test.rs | 9 +- crates/miden-lib/src/account/wallets/mod.rs | 2 +- crates/miden-lib/src/auth.rs | 2 +- crates/miden-objects/Cargo.toml | 4 +- crates/miden-objects/src/account/auth.rs | 217 +++++++++++++++--- crates/miden-objects/src/account/file.rs | 11 +- crates/miden-objects/src/account/mod.rs | 2 - crates/miden-objects/src/errors.rs | 9 + crates/miden-objects/src/lib.rs | 1 + .../miden-objects/src/transaction/tx_args.rs | 2 +- .../src/kernel_tests/tx/test_note.rs | 3 +- crates/miden-testing/src/mock_chain/auth.rs | 28 +-- crates/miden-testing/src/mock_chain/chain.rs | 20 +- .../miden-testing/src/tx_context/builder.rs | 10 +- .../miden-testing/src/tx_context/context.rs | 6 +- crates/miden-testing/tests/auth/multisig.rs | 141 +++++------- crates/miden-testing/tests/wallet/mod.rs | 6 +- crates/miden-tx/src/auth/tx_authenticator.rs | 73 ++---- crates/miden-tx/src/errors/mod.rs | 7 +- crates/miden-tx/src/executor/exec_host.rs | 9 +- 26 files changed, 346 insertions(+), 265 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a06ad944..854405c12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,17 +32,17 @@ - Added `account::get_initial_balance` procedure to `miden` lib ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). - [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963)). - Added `MastArtifact`, `PackageExport`, `PackageManifest`, `AttributeSet`, `QualifiedProcedureName`, `Section` and `SectionId` to re-export section ([#1984](https://github.com/0xMiden/miden-base/pull/1984) and [#2015](https://github.com/0xMiden/miden-base/pull/2015)). -- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). +- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973](https://github.com/0xMiden/miden-base/pull/1973). - [BREAKING] Introduce `AssetVaultKey` newtype wrapper for asset vault keys ([#1978](https://github.com/0xMiden/miden-base/pull/1978), [#2024](https://github.com/0xMiden/miden-base/pull/2024)). -- [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963]https://github.com/0xMiden/miden-base/pull/1963). +- [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963). - Added `network_fungible_faucet` and `MINT` & `BURN` notes ([#1925](https://github.com/0xMiden/miden-base/pull/1925)) +- [BREAKING] Introduced `AuthScheme` and `PublicKey` enums in `miden-objects::account::auth` module ([#1994](https://github.com/0xMiden/miden-base/pull/1994)). +- [BREAKING] Added `get_note_script()` method to `DataStore` trait to enable lazy loading of note scripts during transaction execution ([#1995](https://github.com/0xMiden/miden-base/pull/1995)). - Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). +- [BREAKING] Change `AccountTree` to be generic over `trait AccountTreeBackend` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). - Added `Display` trait for `AddressInterface` ([#2016](https://github.com/0xMiden/miden-base/pull/2016)). - Added `has_procedure` procedure to the `miden::account` module ([#2017](https://github.com/0xMiden/miden-base/pull/2017)). - Re-add bech32 encoding for `AccountId` ([#2018](https://github.com/0xMiden/miden-base/pull/2018)). -- [BREAKING] Change `AccountTree` to be generic over `Smt` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). -- [BREAKING] Change `AccountTree` to be generic over `trait AccountTreeBackend` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). -- [BREAKING] Added `get_note_script()` method to `DataStore` trait to enable lazy loading of note scripts during transaction execution ([#1995](https://github.com/0xMiden/miden-base/pull/1995)). - [BREAKING] Separate account APIs in `miden::account` into `active_account` and `native_account` ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). - [BREAKING] Remove `miden::account::get_native_nonce` procedure ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). - [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032), [#2047](https://github.com/0xMiden/miden-base/pull/2047)). @@ -77,16 +77,17 @@ - [BREAKING] Remove `MockChain::add_pending_note` to simplify mock chain internals ([#1903](https://github.com/0xMiden/miden-base/pull/1903)). - [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). - [BREAKING] Remove account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). -- Simplify `MockChain` internals and rework its documentation ([#1942]https://github.com/0xMiden/miden-base/pull/1942). +- [BREAKING] Rename `TransactionInputs` to `TransactionExecutionInputs` and make a new `TransactionInputs` struct which does not contain `InputNotes` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). +- [BREAKING] Refactor `TransactionInputs` and remove `TransactionWitness` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). +- Simplify `MockChain` internals and rework its documentation ([#1942](https://github.com/0xMiden/miden-base/pull/1942). - [BREAKING] Change the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). -- Update the type signature syntax in the `account_components` module ([#1971](https://github.com/0xMiden/miden-base/pull/1971)). -- [BREAKING] Return `ExecutionOutput` from `TransactionContext::execute_code` ([#1955](https://github.com/0xMiden/miden-base/pull/1955)). +- [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). - Dynamically lookup all masm `EventId`s from source ([#1954](https://github.com/0xMiden/miden-base/pull/1954)). +- [BREAKING] Return `ExecutionOutput` from `TransactionContext::execute_code` ([#1955](https://github.com/0xMiden/miden-base/pull/1955)). - [BREAKING] Rename `get_item_init` and `get_map_item_init` to `get_initial_item` and `get_initial_map_item` respectively ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). -- [BREAKING] Rename `TransactionInputs` to `TransactionExecutionInputs` and make a new `TransactionInputs` struct which does not contain `InputNotes` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). -- [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). -- [BREAKING] Refactor `TransactionInputs` and remove `TransactionWitness` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). +- Update the type signature syntax in the `account_components` module ([#1971](https://github.com/0xMiden/miden-base/pull/1971)). - [BREAKING] Assert nonce is non-zero after the auth procedure ([#1982](https://github.com/0xMiden/miden-base/pull/1982)). +- [BREAKING] Removed `Rng` from `BasicAuthenticator` ([#1994](https://github.com/0xMiden/miden-base/pull/1994)). - [BREAKING] Change the outputs of the `output_note::add_asset` procedure: now the values that are the same as the passed parameters are dropped ([#2031](https://github.com/0xMiden/miden-base/pull/2031)). - [BREAKING] Upgrade VM to 0.19 ([#2042](https://github.com/0xMiden/miden-base/pull/2042)). diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512.rs index e35b95b4c4..a8e3e2ada8 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512.rs @@ -1,4 +1,5 @@ -use miden_objects::account::{AccountComponent, PublicKeyCommitment, StorageSlot}; +use miden_objects::account::auth::PublicKeyCommitment; +use miden_objects::account::{AccountComponent, StorageSlot}; use crate::account::components::rpo_falcon_512_library; diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs index 699935d001..f9982d5456 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_acl.rs @@ -1,12 +1,7 @@ use alloc::vec::Vec; -use miden_objects::account::{ - AccountCode, - AccountComponent, - PublicKeyCommitment, - StorageMap, - StorageSlot, -}; +use miden_objects::account::auth::PublicKeyCommitment; +use miden_objects::account::{AccountCode, AccountComponent, StorageMap, StorageSlot}; use miden_objects::{AccountError, Word}; use crate::account::components::rpo_falcon_512_acl_library; diff --git a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs index 692aad523c..f364abc298 100644 --- a/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs +++ b/crates/miden-lib/src/account/auth/rpo_falcon_512_multisig.rs @@ -1,6 +1,7 @@ use alloc::vec::Vec; -use miden_objects::account::{AccountComponent, PublicKeyCommitment, StorageMap, StorageSlot}; +use miden_objects::account::auth::PublicKeyCommitment; +use miden_objects::account::{AccountComponent, StorageMap, StorageSlot}; use miden_objects::{AccountError, Word}; use crate::account::components::rpo_falcon_512_multisig_library; diff --git a/crates/miden-lib/src/account/faucets/basic_fungible.rs b/crates/miden-lib/src/account/faucets/basic_fungible.rs index c222925c90..dd1dadb397 100644 --- a/crates/miden-lib/src/account/faucets/basic_fungible.rs +++ b/crates/miden-lib/src/account/faucets/basic_fungible.rs @@ -279,6 +279,7 @@ pub fn create_basic_fungible_faucet( #[cfg(test)] mod tests { use assert_matches::assert_matches; + use miden_objects::account::auth::PublicKeyCommitment; use miden_objects::{FieldElement, ONE, Word}; use super::{ @@ -370,7 +371,7 @@ mod tests { fn faucet_create_from_account() { // prepare the test data let mock_word = Word::from([0, 1, 2, 3u32]); - let mock_public_key = miden_objects::account::PublicKeyCommitment::from(mock_word); + let mock_public_key = PublicKeyCommitment::from(mock_word); let mock_seed = mock_word.as_bytes(); // valid account diff --git a/crates/miden-lib/src/account/interface/component.rs b/crates/miden-lib/src/account/interface/component.rs index 27f47d771f..db6136db52 100644 --- a/crates/miden-lib/src/account/interface/component.rs +++ b/crates/miden-lib/src/account/interface/component.rs @@ -2,12 +2,8 @@ use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use miden_objects::account::{ - AccountId, - AccountProcedureInfo, - AccountStorage, - PublicKeyCommitment, -}; +use miden_objects::account::auth::PublicKeyCommitment; +use miden_objects::account::{AccountId, AccountProcedureInfo, AccountStorage}; use miden_objects::note::PartialNote; use miden_objects::{Felt, FieldElement, Word}; diff --git a/crates/miden-lib/src/account/interface/test.rs b/crates/miden-lib/src/account/interface/test.rs index f8a44141e8..9ee92f06c4 100644 --- a/crates/miden-lib/src/account/interface/test.rs +++ b/crates/miden-lib/src/account/interface/test.rs @@ -3,13 +3,8 @@ use alloc::sync::Arc; use alloc::vec::Vec; use assert_matches::assert_matches; -use miden_objects::account::{ - AccountBuilder, - AccountComponent, - AccountType, - PublicKeyCommitment, - StorageSlot, -}; +use miden_objects::account::auth::PublicKeyCommitment; +use miden_objects::account::{AccountBuilder, AccountComponent, AccountType, StorageSlot}; use miden_objects::assembly::diagnostics::NamedSource; use miden_objects::assembly::{Assembler, DefaultSourceManager}; use miden_objects::asset::{FungibleAsset, NonFungibleAsset, TokenSymbol}; diff --git a/crates/miden-lib/src/account/wallets/mod.rs b/crates/miden-lib/src/account/wallets/mod.rs index fd59463367..7dd73650e8 100644 --- a/crates/miden-lib/src/account/wallets/mod.rs +++ b/crates/miden-lib/src/account/wallets/mod.rs @@ -156,7 +156,7 @@ pub fn create_basic_wallet( #[cfg(test)] mod tests { - use miden_objects::account::PublicKeyCommitment; + use miden_objects::account::auth::PublicKeyCommitment; use miden_objects::{ONE, Word}; use miden_processor::utils::{Deserializable, Serializable}; diff --git a/crates/miden-lib/src/auth.rs b/crates/miden-lib/src/auth.rs index c143fa9f9d..53644d8784 100644 --- a/crates/miden-lib/src/auth.rs +++ b/crates/miden-lib/src/auth.rs @@ -1,6 +1,6 @@ use alloc::vec::Vec; -use miden_objects::account::PublicKeyCommitment; +use miden_objects::account::auth::PublicKeyCommitment; /// Defines authentication schemes available to standard and faucet accounts. pub enum AuthScheme { diff --git a/crates/miden-objects/Cargo.toml b/crates/miden-objects/Cargo.toml index 68ca9850f1..445dfea655 100644 --- a/crates/miden-objects/Cargo.toml +++ b/crates/miden-objects/Cargo.toml @@ -31,7 +31,7 @@ std = [ "miden-processor/std", "miden-verifier/std", ] -testing = ["dep:rand", "dep:rand_xoshiro", "dep:winter-rand-utils", "miden-air/testing"] +testing = ["dep:rand_xoshiro", "dep:winter-rand-utils", "miden-air/testing"] [dependencies] # Miden dependencies @@ -48,7 +48,7 @@ winter-rand-utils = { optional = true, version = "0.13" } # External dependencies bech32 = { default-features = false, features = ["alloc"], version = "0.11" } log = { optional = true, version = "0.4" } -rand = { optional = true, workspace = true } +rand = { workspace = true } rand_xoshiro = { default-features = false, optional = true, version = "0.7" } semver = { features = ["serde"], version = "1.0" } serde = { features = ["derive"], optional = true, version = "1.0" } diff --git a/crates/miden-objects/src/account/auth.rs b/crates/miden-objects/src/account/auth.rs index c66a43db25..242784840b 100644 --- a/crates/miden-objects/src/account/auth.rs +++ b/crates/miden-objects/src/account/auth.rs @@ -1,8 +1,8 @@ use alloc::vec::Vec; -use miden_crypto::dsa::rpo_falcon512::PublicKey as RpoFalconPublicKey; +use rand::Rng; -use crate::crypto::dsa::rpo_falcon512::{self, Polynomial, SecretKey}; +use crate::crypto::dsa::rpo_falcon512; use crate::utils::serde::{ ByteReader, ByteWriter, @@ -10,30 +10,119 @@ use crate::utils::serde::{ DeserializationError, Serializable, }; -use crate::{Felt, Hasher, Word}; +use crate::{AuthSchemeError, Felt, Hasher, Word}; + +// AUTH SCHEME +// ================================================================================================ + +/// Identifier of the RpoFalcon512 signature scheme. +const RPO_FALCON_512: u8 = 0; + +/// Defines standard authentication schemes (i.e., signature schemes) available in the Miden +/// protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +#[repr(u8)] +pub enum AuthScheme { + RpoFalcon512 = RPO_FALCON_512, +} + +impl AuthScheme { + /// Returns a numerical value of this auth scheme. + pub fn as_u8(&self) -> u8 { + *self as u8 + } +} + +impl core::fmt::Display for AuthScheme { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::RpoFalcon512 => f.write_str("RpoFalcon512"), + } + } +} + +impl TryFrom for AuthScheme { + type Error = AuthSchemeError; + + fn try_from(value: u8) -> Result { + match value { + RPO_FALCON_512 => Ok(Self::RpoFalcon512), + value => Err(AuthSchemeError::InvalidAuthSchemeIdentifier(value)), + } + } +} + +impl Serializable for AuthScheme { + fn write_into(&self, target: &mut W) { + target.write_u8(*self as u8); + } + + fn get_size_hint(&self) -> usize { + // auth scheme is encoded as a single byte + size_of::() + } +} + +impl Deserializable for AuthScheme { + fn read_from(source: &mut R) -> Result { + match source.read_u8()? { + RPO_FALCON_512 => Ok(Self::RpoFalcon512), + value => Err(DeserializationError::InvalidValue(format!( + "auth scheme identifier `{value}` is not valid" + ))), + } + } +} // AUTH SECRET KEY // ================================================================================================ -/// Types of secret keys used for signing messages -#[derive(Clone, Debug)] +/// Secret keys of the standard [`AuthScheme`]s available in the Miden protocol. +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] #[repr(u8)] pub enum AuthSecretKey { - RpoFalcon512(rpo_falcon512::SecretKey) = 0, + RpoFalcon512(rpo_falcon512::SecretKey) = RPO_FALCON_512, } impl AuthSecretKey { - /// Identifier for the type of authentication key - pub fn auth_scheme_id(&self) -> u8 { + /// Generates an RpoFalcon512 secret key from the OS-provided randomness. + #[cfg(feature = "std")] + pub fn new_rpo_falcon512() -> Self { + Self::RpoFalcon512(rpo_falcon512::SecretKey::new()) + } + + /// Generates an RpoFalcon512 secrete key using the provided random number generator. + pub fn new_rpo_falcon512_with_rng(rng: &mut R) -> Self { + Self::RpoFalcon512(rpo_falcon512::SecretKey::with_rng(rng)) + } + + /// Returns the authentication scheme of this secret key. + pub fn auth_scheme(&self) -> AuthScheme { + match self { + AuthSecretKey::RpoFalcon512(_) => AuthScheme::RpoFalcon512, + } + } + + /// Returns a public key associated with this secret key. + pub fn public_key(&self) -> PublicKey { + match self { + AuthSecretKey::RpoFalcon512(key) => PublicKey::RpoFalcon512(key.public_key()), + } + } + + /// Signs the provided message with this secret key. + pub fn sign(&self, message: Word) -> Signature { match self { - AuthSecretKey::RpoFalcon512(_) => 0u8, + AuthSecretKey::RpoFalcon512(key) => Signature::RpoFalcon512(key.sign(message)), } } } impl Serializable for AuthSecretKey { fn write_into(&self, target: &mut W) { - target.write_u8(self.auth_scheme_id()); + self.auth_scheme().write_into(target); match self { AuthSecretKey::RpoFalcon512(secret_key) => { secret_key.write_into(target); @@ -44,14 +133,11 @@ impl Serializable for AuthSecretKey { impl Deserializable for AuthSecretKey { fn read_from(source: &mut R) -> Result { - let auth_key_id: u8 = source.read_u8()?; - match auth_key_id { - // RpoFalcon512 - 0u8 => { - let secret_key = SecretKey::read_from(source)?; + match source.read::()? { + AuthScheme::RpoFalcon512 => { + let secret_key = rpo_falcon512::SecretKey::read_from(source)?; Ok(AuthSecretKey::RpoFalcon512(secret_key)) }, - val => Err(DeserializationError::InvalidValue(format!("Invalid auth scheme ID {val}"))), } } } @@ -60,11 +146,17 @@ impl Deserializable for AuthSecretKey { // ================================================================================================ /// Commitment to a public key. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct PublicKeyCommitment(Word); -impl From for PublicKeyCommitment { - fn from(value: RpoFalconPublicKey) -> Self { +impl core::fmt::Display for PublicKeyCommitment { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for PublicKeyCommitment { + fn from(value: rpo_falcon512::PublicKey) -> Self { Self(value.to_commitment()) } } @@ -81,6 +173,60 @@ impl From for PublicKeyCommitment { } } +/// Public keys of the standard authentication schemes available in the Miden protocol. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum PublicKey { + RpoFalcon512(rpo_falcon512::PublicKey), +} + +impl PublicKey { + /// Returns the authentication scheme of this public key. + pub fn auth_scheme(&self) -> AuthScheme { + match self { + PublicKey::RpoFalcon512(_) => AuthScheme::RpoFalcon512, + } + } + + /// Returns a commitment to this public key. + pub fn to_commitment(&self) -> PublicKeyCommitment { + match self { + PublicKey::RpoFalcon512(key) => key.to_commitment().into(), + } + } + + /// Verifies the provided signature against the provided message and this public key. + pub fn verify(&self, message: Word, signature: Signature) -> bool { + match (self, signature) { + (PublicKey::RpoFalcon512(key), Signature::RpoFalcon512(signature)) => { + key.verify(message, &signature) + }, + } + } +} + +impl Serializable for PublicKey { + fn write_into(&self, target: &mut W) { + self.auth_scheme().write_into(target); + match self { + PublicKey::RpoFalcon512(pub_key) => { + pub_key.write_into(target); + }, + } + } +} + +impl Deserializable for PublicKey { + fn read_from(source: &mut R) -> Result { + match source.read::()? { + AuthScheme::RpoFalcon512 => { + let pub_key = rpo_falcon512::PublicKey::read_from(source)?; + Ok(PublicKey::RpoFalcon512(pub_key)) + }, + } + } +} + // SIGNATURE // ================================================================================================ @@ -102,10 +248,19 @@ impl From for PublicKeyCommitment { #[derive(Clone, Debug)] #[repr(u8)] pub enum Signature { - RpoFalcon512(rpo_falcon512::Signature) = 0, + RpoFalcon512(rpo_falcon512::Signature) = RPO_FALCON_512, } impl Signature { + /// Returns the authentication scheme of this signature. + pub fn auth_scheme(&self) -> AuthScheme { + match self { + Signature::RpoFalcon512(_) => AuthScheme::RpoFalcon512, + } + } + + /// Converts this signature to a sequence of field elements in the format expected by the + /// native verification procedure in the VM. pub fn to_prepared_signature(&self) -> Vec { match self { Signature::RpoFalcon512(signature) => prepare_rpo_falcon512_signature(signature), @@ -119,18 +274,9 @@ impl From for Signature { } } -impl Signature { - /// Identifier for the type of signature scheme - pub fn signature_scheme_id(&self) -> u8 { - match self { - Signature::RpoFalcon512(_) => 0u8, - } - } -} - impl Serializable for Signature { fn write_into(&self, target: &mut W) { - target.write_u8(self.signature_scheme_id()); + self.auth_scheme().write_into(target); match self { Signature::RpoFalcon512(signature) => { signature.write_into(target); @@ -141,16 +287,11 @@ impl Serializable for Signature { impl Deserializable for Signature { fn read_from(source: &mut R) -> Result { - let signature_scheme_id: u8 = source.read_u8()?; - match signature_scheme_id { - // RpoFalcon512 - 0u8 => { + match source.read::()? { + AuthScheme::RpoFalcon512 => { let signature = rpo_falcon512::Signature::read_from(source)?; Ok(Signature::RpoFalcon512(signature)) }, - val => Err(DeserializationError::InvalidValue(format!( - "Invalid signature scheme ID {val}" - ))), } } } @@ -170,6 +311,8 @@ impl Deserializable for Signature { /// the Miden field. /// 5. The nonce represented as 8 field elements. fn prepare_rpo_falcon512_signature(sig: &rpo_falcon512::Signature) -> Vec { + use rpo_falcon512::Polynomial; + // The signature is composed of a nonce and a polynomial s2 // The nonce is represented as 8 field elements. let nonce = sig.nonce(); diff --git a/crates/miden-objects/src/account/file.rs b/crates/miden-objects/src/account/file.rs index 1d57f35b0e..f882f261e6 100644 --- a/crates/miden-objects/src/account/file.rs +++ b/crates/miden-objects/src/account/file.rs @@ -15,7 +15,8 @@ use super::super::utils::serde::{ DeserializationError, Serializable, }; -use super::{Account, AuthSecretKey}; +use super::Account; +use super::auth::AuthSecretKey; const MAGIC: &str = "acct"; @@ -98,14 +99,14 @@ impl Deserializable for AccountFile { #[cfg(test)] mod tests { - use miden_crypto::dsa::rpo_falcon512::SecretKey; use miden_crypto::utils::{Deserializable, Serializable}; use storage::AccountStorage; #[cfg(feature = "std")] use tempfile::tempdir; use super::AccountFile; - use crate::account::{Account, AccountCode, AccountId, AuthSecretKey, Felt, storage}; + use crate::account::auth::AuthSecretKey; + use crate::account::{Account, AccountCode, AccountId, Felt, storage}; use crate::asset::AssetVault; use crate::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE; @@ -118,8 +119,8 @@ mod tests { let storage = AccountStorage::new(vec![]).unwrap(); let nonce = Felt::new(1); let account = Account::new_existing(id, vault, storage, code, nonce); - let auth_secret_key = AuthSecretKey::RpoFalcon512(SecretKey::new()); - let auth_secret_key_2 = AuthSecretKey::RpoFalcon512(SecretKey::new()); + let auth_secret_key = AuthSecretKey::new_rpo_falcon512(); + let auth_secret_key_2 = AuthSecretKey::new_rpo_falcon512(); AccountFile::new(account, vec![auth_secret_key, auth_secret_key_2]) } diff --git a/crates/miden-objects/src/account/mod.rs b/crates/miden-objects/src/account/mod.rs index bc52c73fdb..0918e43ce8 100644 --- a/crates/miden-objects/src/account/mod.rs +++ b/crates/miden-objects/src/account/mod.rs @@ -26,8 +26,6 @@ pub use account_id::{ pub mod auth; -pub use auth::{AuthSecretKey, PublicKeyCommitment, Signature}; - mod builder; pub use builder::AccountBuilder; diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index f536dfe6cf..3a806326a7 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -1061,3 +1061,12 @@ pub enum NullifierTreeError { #[error("failed to compute nulifier tree mutations")] ComputeMutations(#[source] MerkleError), } + +// AUTH SCHEME ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum AuthSchemeError { + #[error("auth scheme identifier `{0}` is not valid")] + InvalidAuthSchemeIdentifier(u8), +} diff --git a/crates/miden-objects/src/lib.rs b/crates/miden-objects/src/lib.rs index 7e35798d81..ae78569bb4 100644 --- a/crates/miden-objects/src/lib.rs +++ b/crates/miden-objects/src/lib.rs @@ -32,6 +32,7 @@ pub use errors::{ AddressError, AssetError, AssetVaultError, + AuthSchemeError, BatchAccountUpdateError, FeeError, NetworkIdError, diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index 27739a1a2b..0088ffdc1c 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -6,7 +6,7 @@ use miden_crypto::merkle::InnerNodeInfo; use miden_processor::MastNodeExt; use super::{Felt, Hasher, Word}; -use crate::account::{PublicKeyCommitment, Signature}; +use crate::account::auth::{PublicKeyCommitment, Signature}; use crate::note::{NoteId, NoteRecipient}; use crate::utils::serde::{ ByteReader, diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 3e2e0a466a..1c37c1591e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -8,7 +8,8 @@ use miden_lib::testing::note::NoteBuilder; use miden_lib::transaction::TransactionKernel; use miden_lib::transaction::memory::ACTIVE_INPUT_NOTE_PTR; use miden_lib::utils::ScriptBuilder; -use miden_objects::account::{AccountBuilder, AccountId, PublicKeyCommitment}; +use miden_objects::account::auth::PublicKeyCommitment; +use miden_objects::account::{AccountBuilder, AccountId}; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::diagnostics::miette::{self, miette}; use miden_objects::asset::FungibleAsset; diff --git a/crates/miden-testing/src/mock_chain/auth.rs b/crates/miden-testing/src/mock_chain/auth.rs index 6e379bad37..29d8270b3c 100644 --- a/crates/miden-testing/src/mock_chain/auth.rs +++ b/crates/miden-testing/src/mock_chain/auth.rs @@ -11,8 +11,8 @@ use miden_lib::account::auth::{ }; use miden_lib::testing::account_component::{ConditionalAuthComponent, IncrNonceAuthComponent}; use miden_objects::Word; -use miden_objects::account::{AccountComponent, AuthSecretKey, PublicKeyCommitment}; -use miden_objects::crypto::dsa::rpo_falcon512::SecretKey; +use miden_objects::account::AccountComponent; +use miden_objects::account::auth::{AuthSecretKey, PublicKeyCommitment}; use miden_objects::testing::noop_auth_component::NoopAuthComponent; use miden_tx::auth::BasicAuthenticator; use rand::SeedableRng; @@ -21,7 +21,7 @@ use rand_chacha::ChaCha20Rng; /// Specifies which authentication mechanism is desired for accounts #[derive(Debug, Clone)] pub enum Auth { - /// Creates a [SecretKey] for the account and creates a [BasicAuthenticator] used to + /// Creates a secret key for the account and creates a [BasicAuthenticator] used to /// authenticate the account with [AuthRpoFalcon512]. BasicAuth, @@ -32,7 +32,7 @@ pub enum Auth { proc_threshold_map: Vec<(Word, u32)>, }, - /// Creates a [SecretKey] for the account, and creates a [BasicAuthenticator] used to + /// Creates a secret key for the account, and creates a [BasicAuthenticator] used to /// authenticate the account with [AuthRpoFalcon512Acl]. Authentication will only be /// triggered if any of the procedures specified in the list are called during execution. Acl { @@ -59,18 +59,15 @@ impl Auth { /// Converts `self` into its corresponding authentication [`AccountComponent`] and an optional /// [`BasicAuthenticator`]. The component is always returned, but the authenticator is only /// `Some` when [`Auth::BasicAuth`] is passed." - pub fn build_component(&self) -> (AccountComponent, Option>) { + pub fn build_component(&self) -> (AccountComponent, Option) { match self { Auth::BasicAuth => { let mut rng = ChaCha20Rng::from_seed(Default::default()); - let sec_key = SecretKey::with_rng(&mut rng); - let pub_key = PublicKeyCommitment::from(sec_key.public_key()); + let sec_key = AuthSecretKey::new_rpo_falcon512_with_rng(&mut rng); + let pub_key = sec_key.public_key().to_commitment(); let component = AuthRpoFalcon512::new(pub_key).into(); - let authenticator = BasicAuthenticator::::new_with_rng( - &[(pub_key.into(), AuthSecretKey::RpoFalcon512(sec_key))], - rng, - ); + let authenticator = BasicAuthenticator::new(&[sec_key]); (component, Some(authenticator)) }, @@ -93,8 +90,8 @@ impl Auth { allow_unauthorized_input_notes, } => { let mut rng = ChaCha20Rng::from_seed(Default::default()); - let sec_key = SecretKey::with_rng(&mut rng); - let pub_key = PublicKeyCommitment::from(sec_key.public_key()); + let sec_key = AuthSecretKey::new_rpo_falcon512_with_rng(&mut rng); + let pub_key = sec_key.public_key().to_commitment(); let component = AuthRpoFalcon512Acl::new( pub_key, @@ -105,10 +102,7 @@ impl Auth { ) .expect("component creation failed") .into(); - let authenticator = BasicAuthenticator::::new_with_rng( - &[(pub_key.into(), AuthSecretKey::RpoFalcon512(sec_key))], - rng, - ); + let authenticator = BasicAuthenticator::new(&[sec_key]); (component, Some(authenticator)) }, diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 643a9ce2a3..39b5a7d688 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -3,8 +3,9 @@ use alloc::vec::Vec; use anyhow::Context; use miden_block_prover::{LocalBlockProver, ProvenBlockError}; +use miden_objects::account::auth::AuthSecretKey; use miden_objects::account::delta::AccountUpdateDetails; -use miden_objects::account::{Account, AccountId, AuthSecretKey, PartialAccount}; +use miden_objects::account::{Account, AccountId, PartialAccount}; use miden_objects::batch::{ProposedBatch, ProvenBatch}; use miden_objects::block::account_tree::AccountTree; use miden_objects::block::{ @@ -28,13 +29,11 @@ use miden_objects::transaction::{ ProvenTransaction, TransactionInputs, }; -use miden_processor::{DeserializationError, Word}; +use miden_processor::DeserializationError; use miden_tx::LocalTransactionProver; use miden_tx::auth::BasicAuthenticator; use miden_tx::utils::{ByteReader, Deserializable, Serializable}; use miden_tx_batch_prover::LocalBatchProver; -use rand::SeedableRng; -use rand_chacha::ChaCha20Rng; use winterfell::ByteWriter; use super::note::MockChainNote; @@ -1046,15 +1045,15 @@ pub enum AccountState { /// A wrapper around the authenticator of an account. #[derive(Debug, Clone)] pub(super) struct AccountAuthenticator { - authenticator: Option>, + authenticator: Option, } impl AccountAuthenticator { - pub fn new(authenticator: Option>) -> Self { + pub fn new(authenticator: Option) -> Self { Self { authenticator } } - pub fn authenticator(&self) -> Option<&BasicAuthenticator> { + pub fn authenticator(&self) -> Option<&BasicAuthenticator> { self.authenticator.as_ref() } } @@ -1078,17 +1077,16 @@ impl Serializable for AccountAuthenticator { fn write_into(&self, target: &mut W) { self.authenticator .as_ref() - .map(|auth| auth.keys().iter().collect::>()) + .map(|auth| auth.keys().values().collect::>()) .write_into(target); } } impl Deserializable for AccountAuthenticator { fn read_from(source: &mut R) -> Result { - let authenticator = Option::>::read_from(source)?; + let authenticator = Option::>::read_from(source)?; - let authenticator = authenticator - .map(|keys| BasicAuthenticator::new_with_rng(&keys, ChaCha20Rng::from_os_rng())); + let authenticator = authenticator.map(|keys| BasicAuthenticator::new(&keys)); Ok(Self { authenticator }) } diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index e0148ca026..8e524171f9 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -9,7 +9,8 @@ use anyhow::Context; use miden_lib::testing::account_component::IncrNonceAuthComponent; use miden_lib::testing::mock_account::MockAccountExt; use miden_objects::EMPTY_WORD; -use miden_objects::account::{Account, AccountHeader, AccountId, PublicKeyCommitment, Signature}; +use miden_objects::account::auth::{PublicKeyCommitment, Signature}; +use miden_objects::account::{Account, AccountHeader, AccountId}; use miden_objects::assembly::DefaultSourceManager; use miden_objects::assembly::debuginfo::SourceManagerSync; use miden_objects::block::AccountWitness; @@ -25,13 +26,10 @@ use miden_objects::transaction::{ use miden_processor::{AdviceInputs, Felt, Word}; use miden_tx::TransactionMastStore; use miden_tx::auth::BasicAuthenticator; -use rand_chacha::ChaCha20Rng; use super::TransactionContext; use crate::{MockChain, MockChainNote}; -pub type MockAuthenticator = BasicAuthenticator; - // TRANSACTION CONTEXT BUILDER // ================================================================================================ @@ -72,7 +70,7 @@ pub struct TransactionContextBuilder { source_manager: Arc, account: Account, advice_inputs: AdviceInputs, - authenticator: Option, + authenticator: Option, expected_output_notes: Vec, foreign_account_inputs: BTreeMap, input_notes: Vec, @@ -159,7 +157,7 @@ impl TransactionContextBuilder { } /// Set the authenticator for the transaction (if needed) - pub fn authenticator(mut self, authenticator: Option) -> Self { + pub fn authenticator(mut self, authenticator: Option) -> Self { self.authenticator = authenticator; self } diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index c2d5a973f3..d09ffc069c 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -32,11 +32,9 @@ use miden_tx::{ TransactionExecutorHost, TransactionMastStore, }; -use rand_chacha::ChaCha20Rng; use crate::executor::CodeExecutor; use crate::mock_host::MockHost; -use crate::tx_context::builder::MockAuthenticator; // TRANSACTION CONTEXT // ================================================================================================ @@ -51,7 +49,7 @@ pub struct TransactionContext { pub(super) foreign_account_inputs: BTreeMap, pub(super) tx_inputs: TransactionInputs, pub(super) mast_store: TransactionMastStore, - pub(super) authenticator: Option, + pub(super) authenticator: Option, pub(super) source_manager: Arc, pub(super) is_lazy_loading_enabled: bool, pub(super) note_scripts: BTreeMap, @@ -178,7 +176,7 @@ impl TransactionContext { &self.tx_inputs } - pub fn authenticator(&self) -> Option<&BasicAuthenticator> { + pub fn authenticator(&self) -> Option<&BasicAuthenticator> { self.authenticator.as_ref() } diff --git a/crates/miden-testing/tests/auth/multisig.rs b/crates/miden-testing/tests/auth/multisig.rs index 0b8dab35d3..4d20657bc5 100644 --- a/crates/miden-testing/tests/auth/multisig.rs +++ b/crates/miden-testing/tests/auth/multisig.rs @@ -5,16 +5,9 @@ use miden_lib::errors::tx_kernel_errors::ERR_TX_ALREADY_EXECUTED; use miden_lib::note::create_p2id_note; use miden_lib::testing::account_interface::get_public_keys_from_account; use miden_lib::utils::ScriptBuilder; -use miden_objects::account::{ - Account, - AccountBuilder, - AccountId, - AccountStorageMode, - AccountType, - AuthSecretKey, -}; +use miden_objects::account::auth::{AuthSecretKey, PublicKey}; +use miden_objects::account::{Account, AccountBuilder, AccountId, AccountStorageMode, AccountType}; use miden_objects::asset::FungibleAsset; -use miden_objects::crypto::dsa::rpo_falcon512::{PublicKey, SecretKey}; use miden_objects::note::NoteType; use miden_objects::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, @@ -36,7 +29,7 @@ use rand_chacha::ChaCha20Rng; // HELPER FUNCTIONS // ================================================================================================ -type MultisigTestSetup = (Vec, Vec, Vec>); +type MultisigTestSetup = (Vec, Vec, Vec); /// Sets up secret keys, public keys, and authenticators for multisig testing fn setup_keys_and_authenticators( @@ -51,7 +44,7 @@ fn setup_keys_and_authenticators( let mut authenticators = Vec::new(); for _ in 0..num_approvers { - let sec_key = SecretKey::with_rng(&mut rng); + let sec_key = AuthSecretKey::new_rpo_falcon512_with_rng(&mut rng); let pub_key = sec_key.public_key(); secret_keys.push(sec_key); @@ -59,14 +52,8 @@ fn setup_keys_and_authenticators( } // Create authenticators for required signers - for i in 0..threshold { - let authenticator = BasicAuthenticator::::new_with_rng( - &[( - public_keys[i].to_commitment(), - AuthSecretKey::RpoFalcon512(secret_keys[i].clone()), - )], - rng.clone(), - ); + for secret_key in secret_keys.iter().take(threshold) { + let authenticator = BasicAuthenticator::new(core::slice::from_ref(secret_key)); authenticators.push(authenticator); } @@ -80,7 +67,7 @@ fn create_multisig_account( asset_amount: u64, proc_threshold_map: Vec<(Word, u32)>, ) -> anyhow::Result { - let approvers: Vec<_> = public_keys.iter().map(|pk| pk.to_commitment()).collect(); + let approvers: Vec<_> = public_keys.iter().map(|pk| pk.to_commitment().into()).collect(); let multisig_account = AccountBuilder::new([0; 32]) .with_auth_component(Auth::Multisig { threshold, approvers, proc_threshold_map }) @@ -153,18 +140,18 @@ async fn test_multisig_2_of_2_with_note_creation() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment().into(), &tx_summary) + .get_signature(public_keys[0].to_commitment(), &tx_summary) .await?; let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment().into(), &tx_summary) + .get_signature(public_keys[1].to_commitment(), &tx_summary) .await?; // Execute transaction with signatures - should succeed let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[input_note.id()], &[])? .extend_expected_output_notes(vec![OutputNote::Full(output_note)]) - .add_signature(public_keys[0].clone().into(), msg, sig_1) - .add_signature(public_keys[1].clone().into(), msg, sig_2) + .add_signature(public_keys[0].to_commitment(), msg, sig_1) + .add_signature(public_keys[1].to_commitment(), msg, sig_2) .auth_args(salt) .build()? .execute() @@ -235,18 +222,18 @@ async fn test_multisig_2_of_4_all_signer_combinations() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[*signer1_idx] - .get_signature(public_keys[*signer1_idx].to_commitment().into(), &tx_summary) + .get_signature(public_keys[*signer1_idx].to_commitment(), &tx_summary) .await?; let sig_2 = authenticators[*signer2_idx] - .get_signature(public_keys[*signer2_idx].to_commitment().into(), &tx_summary) + .get_signature(public_keys[*signer2_idx].to_commitment(), &tx_summary) .await?; // Execute transaction with signatures - should succeed for any combination let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? .auth_args(salt) - .add_signature(public_keys[*signer1_idx].clone().into(), msg, sig_1) - .add_signature(public_keys[*signer2_idx].clone().into(), msg, sig_2) + .add_signature(public_keys[*signer1_idx].to_commitment(), msg, sig_1) + .add_signature(public_keys[*signer2_idx].to_commitment(), msg, sig_2) .build()?; let executed_tx = tx_context_execute.execute().await.unwrap_or_else(|_| { @@ -302,17 +289,17 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment().into(), &tx_summary) + .get_signature(public_keys[0].to_commitment(), &tx_summary) .await?; let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment().into(), &tx_summary) + .get_signature(public_keys[1].to_commitment(), &tx_summary) .await?; // Execute transaction with signatures - should succeed (first execution) let tx_context_execute = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .add_signature(public_keys[0].clone().into(), msg, sig_1.clone()) - .add_signature(public_keys[1].clone().into(), msg, sig_2.clone()) + .add_signature(public_keys[0].to_commitment(), msg, sig_1.clone()) + .add_signature(public_keys[1].to_commitment(), msg, sig_2.clone()) .auth_args(salt) .build()?; @@ -325,8 +312,8 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> { // Attempt to execute the same transaction again - should fail due to replay protection let tx_context_replay = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? - .add_signature(public_keys[0].clone().into(), msg, sig_1) - .add_signature(public_keys[1].clone().into(), msg, sig_2) + .add_signature(public_keys[0].to_commitment(), msg, sig_1) + .add_signature(public_keys[1].to_commitment(), msg, sig_2) .auth_args(salt) .build()?; @@ -394,7 +381,7 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { // Add each public key to the vector for public_key in new_public_keys.iter().rev() { - let key_word: Word = public_key.to_commitment(); + let key_word: Word = public_key.to_commitment().into(); config_and_pubkeys_vector.extend_from_slice(key_word.as_elements()); } @@ -442,10 +429,10 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment().into(), &tx_summary) + .get_signature(public_keys[0].to_commitment(), &tx_summary) .await?; let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment().into(), &tx_summary) + .get_signature(public_keys[1].to_commitment(), &tx_summary) .await?; // Execute transaction with signatures - should succeed @@ -453,8 +440,8 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { .build_tx_context(multisig_account.id(), &[], &[])? .tx_script(tx_script) .tx_script_args(multisig_config_hash) - .add_signature(public_keys[0].clone().into(), msg, sig_1) - .add_signature(public_keys[1].clone().into(), msg, sig_2) + .add_signature(public_keys[0].to_commitment(), msg, sig_1) + .add_signature(public_keys[1].to_commitment(), msg, sig_2) .auth_args(salt) .extend_advice_inputs(advice_inputs) .build()? @@ -477,7 +464,7 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { let storage_key = [Felt::new(i as u64), Felt::new(0), Felt::new(0), Felt::new(0)].into(); let storage_item = updated_multisig_account.storage().get_map_item(1, storage_key).unwrap(); - let expected_word: Word = expected_key.to_commitment(); + let expected_word: Word = expected_key.to_commitment().into(); assert_eq!(storage_item, expected_word, "Public key {} doesn't match expected value", i); } @@ -508,7 +495,7 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { // Verify that the extracted public keys match the new ones we set for (i, expected_key) in new_public_keys.iter().enumerate() { - let expected_word: Word = expected_key.to_commitment(); + let expected_word: Word = expected_key.to_commitment().into(); // Find the matching key in extracted keys (order might be different) let found_key = extracted_pub_keys.iter().find(|&key| *key == expected_word); @@ -528,14 +515,8 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { // Now test creating a note with the new signers // Setup authenticators for the new signers (we need 3 out of 4 for threshold 3) let mut new_authenticators = Vec::new(); - for i in 0..3 { - let authenticator = BasicAuthenticator::::new_with_rng( - &[( - new_public_keys[i].to_commitment(), - AuthSecretKey::RpoFalcon512(_new_secret_keys[i].clone()), - )], - ChaCha20Rng::from_seed([0u8; 32]), - ); + for secret_key in _new_secret_keys.iter().take(3) { + let authenticator = BasicAuthenticator::new(core::slice::from_ref(secret_key)); new_authenticators.push(authenticator); } @@ -577,13 +558,13 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { let tx_summary_new = SigningInputs::TransactionSummary(tx_summary_new); let sig_1_new = new_authenticators[0] - .get_signature(new_public_keys[0].to_commitment().into(), &tx_summary_new) + .get_signature(new_public_keys[0].to_commitment(), &tx_summary_new) .await?; let sig_2_new = new_authenticators[1] - .get_signature(new_public_keys[1].to_commitment().into(), &tx_summary_new) + .get_signature(new_public_keys[1].to_commitment(), &tx_summary_new) .await?; let sig_3_new = new_authenticators[2] - .get_signature(new_public_keys[2].to_commitment().into(), &tx_summary_new) + .get_signature(new_public_keys[2].to_commitment(), &tx_summary_new) .await?; // SECTION 3: Properly handle multisig authentication with the updated signers @@ -593,9 +574,9 @@ async fn test_multisig_update_signers() -> anyhow::Result<()> { let tx_context_execute_new = new_mock_chain .build_tx_context(updated_multisig_account.id(), &[input_note_new.id()], &[])? .extend_expected_output_notes(vec![OutputNote::Full(output_note_new)]) - .add_signature(new_public_keys[0].clone().into(), msg_new, sig_1_new) - .add_signature(new_public_keys[1].clone().into(), msg_new, sig_2_new) - .add_signature(new_public_keys[2].clone().into(), msg_new, sig_3_new) + .add_signature(new_public_keys[0].to_commitment(), msg_new, sig_1_new) + .add_signature(new_public_keys[1].to_commitment(), msg_new, sig_2_new) + .add_signature(new_public_keys[2].to_commitment(), msg_new, sig_3_new) .auth_args(salt_new) .build()? .execute() @@ -640,7 +621,7 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { // Add public keys in reverse order for public_key in new_public_keys.iter().rev() { - let key_word: Word = public_key.to_commitment(); + let key_word: Word = public_key.to_commitment().into(); config_and_pubkeys_vector.extend_from_slice(key_word.as_elements()); } @@ -677,16 +658,16 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { let tx_summary = SigningInputs::TransactionSummary(tx_summary); let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment().into(), &tx_summary) + .get_signature(public_keys[0].to_commitment(), &tx_summary) .await?; let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment().into(), &tx_summary) + .get_signature(public_keys[1].to_commitment(), &tx_summary) .await?; let sig_3 = authenticators[2] - .get_signature(public_keys[2].to_commitment().into(), &tx_summary) + .get_signature(public_keys[2].to_commitment(), &tx_summary) .await?; let sig_4 = authenticators[3] - .get_signature(public_keys[3].to_commitment().into(), &tx_summary) + .get_signature(public_keys[3].to_commitment(), &tx_summary) .await?; // Execute with signatures @@ -694,10 +675,10 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { .build_tx_context(multisig_account.id(), &[], &[])? .tx_script(tx_script) .tx_script_args(multisig_config_hash) - .add_signature(public_keys[0].clone().into(), msg, sig_1) - .add_signature(public_keys[1].clone().into(), msg, sig_2) - .add_signature(public_keys[2].clone().into(), msg, sig_3) - .add_signature(public_keys[3].clone().into(), msg, sig_4) + .add_signature(public_keys[0].to_commitment(), msg, sig_1) + .add_signature(public_keys[1].to_commitment(), msg, sig_2) + .add_signature(public_keys[2].to_commitment(), msg, sig_3) + .add_signature(public_keys[3].to_commitment(), msg, sig_4) .auth_args(salt) .extend_advice_inputs(advice_inputs) .build()? @@ -719,7 +700,7 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { for (i, expected_key) in new_public_keys.iter().enumerate() { let storage_key = [Felt::new(i as u64), Felt::new(0), Felt::new(0), Felt::new(0)].into(); let storage_item = updated_multisig_account.storage().get_map_item(1, storage_key).unwrap(); - let expected_word: Word = expected_key.to_commitment(); + let expected_word: Word = expected_key.to_commitment().into(); assert_eq!(storage_item, expected_word, "Public key {} doesn't match", i); } @@ -733,7 +714,7 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { assert_eq!(extracted_pub_keys.len(), 2, "Should have 2 public keys after update"); for expected_key in new_public_keys.iter() { - let expected_word: Word = expected_key.to_commitment(); + let expected_word: Word = expected_key.to_commitment().into(); assert!( extracted_pub_keys.contains(&expected_word), "Public key not found in extracted keys" @@ -764,7 +745,7 @@ async fn test_multisig_update_signers_remove_owner() -> anyhow::Result<()> { non_empty_count += 1; assert!(i < 2, "Found non-empty key at index {} which should be removed", i); - let expected_word: Word = new_public_keys.get(i).unwrap().to_commitment(); + let expected_word: Word = new_public_keys.get(i).unwrap().to_commitment().into(); assert_eq!(storage_item, expected_word, "Key at index {} doesn't match", i); } } @@ -827,7 +808,7 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu // Add each public key to the vector for public_key in new_public_keys.iter().rev() { - let key_word: Word = public_key.to_commitment(); + let key_word: Word = public_key.to_commitment().into(); config_and_pubkeys_vector.extend_from_slice(key_word.as_elements()); } @@ -878,10 +859,10 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu let tx_summary_signing = SigningInputs::TransactionSummary(tx_summary.clone()); let new_sig_1 = new_authenticators[0] - .get_signature(new_public_keys[0].to_commitment().into(), &tx_summary_signing) + .get_signature(new_public_keys[0].to_commitment(), &tx_summary_signing) .await?; let new_sig_2 = new_authenticators[1] - .get_signature(new_public_keys[1].to_commitment().into(), &tx_summary_signing) + .get_signature(new_public_keys[1].to_commitment(), &tx_summary_signing) .await?; // Try to execute transaction with NEW signatures - should FAIL @@ -889,8 +870,8 @@ async fn test_multisig_new_approvers_cannot_sign_before_update() -> anyhow::Resu .build_tx_context(multisig_account.id(), &[], &[])? .tx_script(tx_script.clone()) .tx_script_args(multisig_config_hash) - .add_signature(new_public_keys[0].clone().into(), msg, new_sig_1) - .add_signature(new_public_keys[1].clone().into(), msg, new_sig_2) + .add_signature(new_public_keys[0].to_commitment(), msg, new_sig_1) + .add_signature(new_public_keys[1].to_commitment(), msg, new_sig_2) .auth_args(salt) .extend_advice_inputs(advice_inputs.clone()) .build()?; @@ -960,13 +941,13 @@ async fn test_multisig_proc_threshold_overrides() -> anyhow::Result<()> { let msg = tx_summary.as_ref().to_commitment(); let tx_summary_signing = SigningInputs::TransactionSummary(tx_summary.clone()); let sig = authenticators[0] - .get_signature(public_keys[0].to_commitment().into(), &tx_summary_signing) + .get_signature(public_keys[0].to_commitment(), &tx_summary_signing) .await?; // 4. execute with signature let tx_result = mock_chain .build_tx_context(multisig_account.id(), &[note.id()], &[])? - .add_signature(public_keys[0].clone().into(), msg, sig) + .add_signature(public_keys[0].to_commitment(), msg, sig) .auth_args(salt) .build()? .execute() @@ -1017,14 +998,14 @@ async fn test_multisig_proc_threshold_overrides() -> anyhow::Result<()> { let tx_summary2_signing = SigningInputs::TransactionSummary(tx_summary2.clone()); let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment().into(), &tx_summary2_signing) + .get_signature(public_keys[0].to_commitment(), &tx_summary2_signing) .await?; // Try to execute with only 1 signature - should FAIL let tx_context_one_sig = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? .extend_expected_output_notes(vec![OutputNote::Full(output_note.clone())]) - .add_signature(public_keys[0].clone().into(), msg2, sig_1) + .add_signature(public_keys[0].to_commitment(), msg2, sig_1) .tx_script(send_note_transaction_script.clone()) .auth_args(salt2) .build()?; @@ -1041,18 +1022,18 @@ async fn test_multisig_proc_threshold_overrides() -> anyhow::Result<()> { // Now get signatures from BOTH approvers let sig_1 = authenticators[0] - .get_signature(public_keys[0].to_commitment().into(), &tx_summary2_signing) + .get_signature(public_keys[0].to_commitment(), &tx_summary2_signing) .await?; let sig_2 = authenticators[1] - .get_signature(public_keys[1].to_commitment().into(), &tx_summary2_signing) + .get_signature(public_keys[1].to_commitment(), &tx_summary2_signing) .await?; // Execute with 2 signatures - should SUCCEED let result = mock_chain .build_tx_context(multisig_account.id(), &[], &[])? .extend_expected_output_notes(vec![OutputNote::Full(output_note)]) - .add_signature(public_keys[0].clone().into(), msg2, sig_1) - .add_signature(public_keys[1].clone().into(), msg2, sig_2) + .add_signature(public_keys[0].to_commitment(), msg2, sig_1) + .add_signature(public_keys[1].to_commitment(), msg2, sig_2) .auth_args(salt2) .tx_script(send_note_transaction_script) .build()? diff --git a/crates/miden-testing/tests/wallet/mod.rs b/crates/miden-testing/tests/wallet/mod.rs index 2dad45c036..c23dc0ae40 100644 --- a/crates/miden-testing/tests/wallet/mod.rs +++ b/crates/miden-testing/tests/wallet/mod.rs @@ -1,7 +1,7 @@ use miden_lib::AuthScheme; use miden_lib::account::wallets::create_basic_wallet; use miden_objects::Word; -use miden_objects::crypto::dsa::rpo_falcon512::SecretKey; +use miden_objects::account::auth::AuthSecretKey; use rand_chacha::ChaCha20Rng; use rand_chacha::rand_core::SeedableRng; @@ -16,8 +16,8 @@ fn wallet_creation() { let seed = [0_u8; 32]; let mut rng = ChaCha20Rng::from_seed(seed); - let sec_key = SecretKey::with_rng(&mut rng); - let pub_key = miden_objects::account::PublicKeyCommitment::from(sec_key.public_key()); + let sec_key = AuthSecretKey::new_rpo_falcon512_with_rng(&mut rng); + let pub_key = sec_key.public_key().to_commitment(); let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key }; // we need to use an initial seed to create the wallet account diff --git a/crates/miden-tx/src/auth/tx_authenticator.rs b/crates/miden-tx/src/auth/tx_authenticator.rs index 10e96c9cb9..e163497b81 100644 --- a/crates/miden-tx/src/auth/tx_authenticator.rs +++ b/crates/miden-tx/src/auth/tx_authenticator.rs @@ -1,16 +1,13 @@ use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::string::ToString; -use alloc::sync::Arc; use alloc::vec::Vec; -use miden_objects::account::{AuthSecretKey, PublicKeyCommitment, Signature}; +use miden_objects::account::auth::{AuthSecretKey, PublicKeyCommitment, Signature}; use miden_objects::crypto::SequentialCommit; use miden_objects::transaction::TransactionSummary; use miden_objects::{Felt, Hasher, Word}; use miden_processor::FutureMaybeSend; -use rand::Rng; -use tokio::sync::RwLock; use crate::errors::AuthenticationError; use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; @@ -170,43 +167,34 @@ impl TransactionAuthenticator for UnreachableAuth { /// Represents a signer for [AuthSecretKey] keys. #[derive(Clone, Debug)] -pub struct BasicAuthenticator { +pub struct BasicAuthenticator { /// pub_key |-> secret_key mapping - keys: BTreeMap, - rng: Arc>, + keys: BTreeMap, } -impl BasicAuthenticator { - #[cfg(feature = "std")] - pub fn new(keys: &[(Word, AuthSecretKey)]) -> BasicAuthenticator { - use rand::SeedableRng; - use rand::rngs::StdRng; - - let rng = StdRng::from_os_rng(); - BasicAuthenticator::::new_with_rng(keys, rng) - } - - pub fn new_with_rng(keys: &[(Word, AuthSecretKey)], rng: R) -> Self { +impl BasicAuthenticator { + pub fn new(keys: &[AuthSecretKey]) -> Self { let mut key_map = BTreeMap::new(); - for (word, secret_key) in keys { - key_map.insert(*word, secret_key.clone()); + for secret_key in keys { + let pub_key = secret_key.public_key().to_commitment(); + key_map.insert(pub_key, secret_key.clone()); } - BasicAuthenticator { - keys: key_map, - rng: Arc::new(RwLock::new(rng)), - } + BasicAuthenticator { keys: key_map } } - /// Returns a reference to the keys map. Map keys represent the public keys, and values - /// represent the secret keys that the authenticator would use to sign messages. - pub fn keys(&self) -> &BTreeMap { + /// Returns a reference to the keys map. + /// + /// Map keys represent the public key commitments, and values represent the secret keys that + /// the authenticator would use to sign messages. + pub fn keys(&self) -> &BTreeMap { &self.keys } } -impl TransactionAuthenticator for BasicAuthenticator { - /// Gets a signature over a message, given a public key. +impl TransactionAuthenticator for BasicAuthenticator { + /// Gets a signature over a message, given a public key commitment. + /// /// The key should be included in the `keys` map and should be a variant of [AuthSecretKey]. /// /// Supported signature schemes: @@ -223,20 +211,9 @@ impl TransactionAuthenticator for BasicAuthenticator { let message = signing_inputs.to_commitment(); async move { - let mut rng = self.rng.write().await; - let pub_key: Word = pub_key_commitment.into(); - match self.keys.get(&pub_key) { - Some(key) => { - let signature: Signature = match key { - AuthSecretKey::RpoFalcon512(falcon_key) => { - falcon_key.sign_with_rng(message, &mut *rng).into() - }, - }; - Ok(signature) - }, - None => Err(AuthenticationError::UnknownPublicKey(format!( - "public key {pub_key} is not contained in the authenticator's keys", - ))), + match self.keys.get(&pub_key_commitment) { + Some(key) => Ok(key.sign(message)), + None => Err(AuthenticationError::UnknownPublicKey(pub_key_commitment)), } } } @@ -263,22 +240,18 @@ impl TransactionAuthenticator for () { #[cfg(test)] mod test { use miden_lib::utils::{Deserializable, Serializable}; - use miden_objects::account::AuthSecretKey; - use miden_objects::crypto::dsa::rpo_falcon512::SecretKey; + use miden_objects::account::auth::AuthSecretKey; use miden_objects::{Felt, Word}; use super::SigningInputs; #[test] fn serialize_auth_key() { - let secret_key = SecretKey::new(); - let auth_key = AuthSecretKey::RpoFalcon512(secret_key.clone()); + let auth_key = AuthSecretKey::new_rpo_falcon512(); let serialized = auth_key.to_bytes(); let deserialized = AuthSecretKey::read_from_bytes(&serialized).unwrap(); - match deserialized { - AuthSecretKey::RpoFalcon512(key) => assert_eq!(secret_key.to_bytes(), key.to_bytes()), - } + assert_eq!(auth_key, deserialized); } #[test] diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index 00272bf6a6..7bb66698bd 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -5,6 +5,7 @@ use core::error::Error; use miden_lib::transaction::TransactionAdviceMapMismatch; use miden_objects::account::AccountId; +use miden_objects::account::auth::PublicKeyCommitment; use miden_objects::assembly::diagnostics::reporting::PrintDiagnostic; use miden_objects::asset::AssetVaultKey; use miden_objects::block::BlockNumber; @@ -393,10 +394,10 @@ impl DataStoreError { pub enum AuthenticationError { #[error("signature rejected: {0}")] RejectedSignature(String), - #[error("unknown public key: {0}")] - UnknownPublicKey(String), + #[error("public key `{0}` is not contained in the authenticator's keys")] + UnknownPublicKey(PublicKeyCommitment), /// Custom error variant for implementors of the - /// [`TransactionAuthenticatior`](crate::auth::TransactionAuthenticator) trait. + /// [`TransactionAuthenticator`](crate::auth::TransactionAuthenticator) trait. #[error("{error_msg}")] Other { error_msg: Box, diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index f87b929095..0c80bbb83b 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -3,13 +3,8 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_lib::transaction::{EventId, TransactionAdviceInputs}; -use miden_objects::account::{ - AccountCode, - AccountDelta, - AccountId, - PartialAccount, - PublicKeyCommitment, -}; +use miden_objects::account::auth::PublicKeyCommitment; +use miden_objects::account::{AccountCode, AccountDelta, AccountId, PartialAccount}; use miden_objects::assembly::debuginfo::Location; use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan}; use miden_objects::asset::{Asset, AssetVaultKey, AssetWitness, FungibleAsset}; From 1b9676864beefd994802ea5079ca7295a8bece99 Mon Sep 17 00:00:00 2001 From: Marti Date: Tue, 4 Nov 2025 23:28:07 +0100 Subject: [PATCH 128/133] fix: avoid overflow of `tag_len` encoding (#2051) --- .../src/address/routing_parameters.rs | 77 +++++++++++++++---- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/crates/miden-objects/src/address/routing_parameters.rs b/crates/miden-objects/src/address/routing_parameters.rs index f92ff8c755..5b69d71af3 100644 --- a/crates/miden-objects/src/address/routing_parameters.rs +++ b/crates/miden-objects/src/address/routing_parameters.rs @@ -32,9 +32,9 @@ const BECH32_SEPARATOR: &str = "1"; /// The value to encode the absence of a note tag routing parameter (i.e. `None`). /// -/// Note tag length is ensured to be <= [`NoteTag::MAX_LOCAL_TAG_LENGTH`] and so 1 << 5 = 32 is used -/// to encode `None`. -const ABSENT_NOTE_TAG_LEN: u8 = 1 << 5; +/// The note tag length occupies 5 bits (values 0..=31). Valid tag lengths are 0..=30, +/// so we reserve the maximum 5-bit value (31) to represent `None`. +const ABSENT_NOTE_TAG_LEN: u8 = (1 << 5) - 1; // 31 /// The routing parameter key for the receiver profile. const RECEIVER_PROFILE_KEY: u8 = 0; @@ -276,25 +276,76 @@ mod tests { Ok(()) } + /// Tests bech32 encoding and decoding roundtrip with various tag lengths. #[test] fn routing_parameters_bech32_encode_decode_roundtrip() -> anyhow::Result<()> { - let routing_params = - RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(8)?; - assert_eq!(routing_params, RoutingParameters::decode(routing_params.encode_to_string())?); + // Test case 1: No explicit tag length + let params_no_tag = RoutingParameters::new(AddressInterface::BasicWallet); + let encoded = params_no_tag.encode_to_string(); + let decoded = RoutingParameters::decode(encoded)?; + assert_eq!(params_no_tag, decoded); + assert_eq!(decoded.note_tag_len(), None); + + // Test case 2: Explicit tag length 0 + let params_tag_0 = + RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(0)?; + let encoded = params_tag_0.encode_to_string(); + let decoded = RoutingParameters::decode(encoded)?; + assert_eq!(params_tag_0, decoded); + assert_eq!(decoded.note_tag_len(), Some(0)); + + // Test case 3: Explicit tag length 6 + let params_tag_6 = + RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(6)?; + let encoded = params_tag_6.encode_to_string(); + let decoded = RoutingParameters::decode(encoded)?; + assert_eq!(params_tag_6, decoded); + assert_eq!(decoded.note_tag_len(), Some(6)); + + // Test case 4: Explicit tag length set to max + let params_tag_max = RoutingParameters::new(AddressInterface::BasicWallet) + .with_note_tag_len(NoteTag::MAX_LOCAL_TAG_LENGTH)?; + let encoded = params_tag_max.encode_to_string(); + let decoded = RoutingParameters::decode(encoded)?; + assert_eq!(params_tag_max, decoded); + assert_eq!(decoded.note_tag_len(), Some(NoteTag::MAX_LOCAL_TAG_LENGTH)); Ok(()) } - /// Tests that routing parameters can be serialized and deserialized. + /// Tests serialization and deserialization roundtrip with various tag lengths. #[test] fn routing_parameters_serialization() -> anyhow::Result<()> { - let routing_params = + // Test case 1: No explicit tag length + let params_no_tag = RoutingParameters::new(AddressInterface::BasicWallet); + let serialized = params_no_tag.to_bytes(); + let deserialized = RoutingParameters::read_from_bytes(&serialized)?; + assert_eq!(params_no_tag, deserialized); + assert_eq!(deserialized.note_tag_len(), None); + + // Test case 2: Explicit tag length 0 + let params_tag_0 = + RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(0)?; + let serialized = params_tag_0.to_bytes(); + let deserialized = RoutingParameters::read_from_bytes(&serialized)?; + assert_eq!(params_tag_0, deserialized); + assert_eq!(deserialized.note_tag_len(), Some(0)); + + // Test case 3: Explicit tag length 6 + let params_tag_6 = RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(6)?; - - assert_eq!( - routing_params, - RoutingParameters::read_from_bytes(&routing_params.to_bytes()).unwrap() - ); + let serialized = params_tag_6.to_bytes(); + let deserialized = RoutingParameters::read_from_bytes(&serialized)?; + assert_eq!(params_tag_6, deserialized); + assert_eq!(deserialized.note_tag_len(), Some(6)); + + // Test case 4: Explicit tag length set to max + let params_tag_max = RoutingParameters::new(AddressInterface::BasicWallet) + .with_note_tag_len(NoteTag::MAX_LOCAL_TAG_LENGTH)?; + let serialized = params_tag_max.to_bytes(); + let deserialized = RoutingParameters::read_from_bytes(&serialized)?; + assert_eq!(params_tag_max, deserialized); + assert_eq!(deserialized.note_tag_len(), Some(NoteTag::MAX_LOCAL_TAG_LENGTH)); Ok(()) } From 0f042087a16609bee957cb8e36a90503280872ee Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 4 Nov 2025 14:32:24 -0800 Subject: [PATCH 129/133] docs: clarify address tag len comment --- crates/miden-objects/src/address/routing_parameters.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/miden-objects/src/address/routing_parameters.rs b/crates/miden-objects/src/address/routing_parameters.rs index 5b69d71af3..d0c9798465 100644 --- a/crates/miden-objects/src/address/routing_parameters.rs +++ b/crates/miden-objects/src/address/routing_parameters.rs @@ -34,10 +34,13 @@ const BECH32_SEPARATOR: &str = "1"; /// /// The note tag length occupies 5 bits (values 0..=31). Valid tag lengths are 0..=30, /// so we reserve the maximum 5-bit value (31) to represent `None`. +/// +/// If the note tag length is absent from routing parameters, the note tag length for the address +/// will be set to the default default tag length of the address' ID component. const ABSENT_NOTE_TAG_LEN: u8 = (1 << 5) - 1; // 31 /// The routing parameter key for the receiver profile. -const RECEIVER_PROFILE_KEY: u8 = 0; +const RECEIVER_PROFILE_PARAM_KEY: u8 = 0; /// Parameters that define how a sender should route a note to the [`AddressId`](super::AddressId) /// in an [`Address`](super::Address). @@ -118,7 +121,7 @@ impl RoutingParameters { let receiver_profile: [u8; 2] = receiver_profile.to_be_bytes(); // Append the receiver profile key and the encoded value to the vector. - encoded.push(RECEIVER_PROFILE_KEY); + encoded.push(RECEIVER_PROFILE_PARAM_KEY); encoded.extend(receiver_profile); encoded @@ -173,7 +176,7 @@ impl RoutingParameters { while let Some(key) = byte_iter.next() { match key { - RECEIVER_PROFILE_KEY => { + RECEIVER_PROFILE_PARAM_KEY => { if byte_iter.len() < 2 { return Err(AddressError::decode_error( "expected two bytes to decode receiver profile", From 31906508e736687b1ec0f6d5df41d9c14ee8ae29 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com> Date: Tue, 4 Nov 2025 22:46:32 -0800 Subject: [PATCH 130/133] feat: add ECDSA signature variants to auth enums (#2052) --- CHANGELOG.md | 1 + Cargo.lock | 8 +- crates/miden-objects/src/account/auth.rs | 94 ++++++++++++++++++++---- 3 files changed, 84 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 854405c12f..e8699d7f58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032), [#2047](https://github.com/0xMiden/miden-base/pull/2047)). - [BREAKING] Refactor `PartialVault`, `PartialStorageMap`, `PartialAccountTree` and `PartialNullifierTree` to allow construction from a root ([#2042](https://github.com/0xMiden/miden-base/pull/2042)). - [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032)). +- [BREAKING] Added `EcdsaK256Keccak` variant to auth enums ([#2052](https://github.com/0xMiden/miden-base/pull/2052)). ### Changes diff --git a/Cargo.lock b/Cargo.lock index c766403552..4b4cc93993 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1484,9 +1484,9 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffedb6b42cbc44634c3f9c58ee912b0d585fabdf3a19af56d8f185eb50125ef" +checksum = "ffd4233803234f287596d9a60c67c4e5762f792eb0dd20969a41c8db093d6126" dependencies = [ "blake3", "cc", @@ -1516,9 +1516,9 @@ dependencies = [ [[package]] name = "miden-crypto-derive" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d3b266500c6fe3584dacc773285a6e8c70b367b7ec0316fb17632a47f98b397" +checksum = "2f70dccd82a2b2787b9bfc64687cd224dfe0adc0d21ae9b241b0c6edc4a23335" dependencies = [ "quote", "syn", diff --git a/crates/miden-objects/src/account/auth.rs b/crates/miden-objects/src/account/auth.rs index 242784840b..65d1ba9169 100644 --- a/crates/miden-objects/src/account/auth.rs +++ b/crates/miden-objects/src/account/auth.rs @@ -1,8 +1,8 @@ use alloc::vec::Vec; -use rand::Rng; +use rand::{CryptoRng, Rng}; -use crate::crypto::dsa::rpo_falcon512; +use crate::crypto::dsa::{ecdsa_k256_keccak, rpo_falcon512}; use crate::utils::serde::{ ByteReader, ByteWriter, @@ -15,8 +15,9 @@ use crate::{AuthSchemeError, Felt, Hasher, Word}; // AUTH SCHEME // ================================================================================================ -/// Identifier of the RpoFalcon512 signature scheme. +/// Identifier of signature schemes use for transaction authentication const RPO_FALCON_512: u8 = 0; +const ECDSA_K256_KECCAK: u8 = 1; /// Defines standard authentication schemes (i.e., signature schemes) available in the Miden /// protocol. @@ -24,7 +25,15 @@ const RPO_FALCON_512: u8 = 0; #[non_exhaustive] #[repr(u8)] pub enum AuthScheme { + /// A deterministic RPO Falcon512 signature scheme. + /// + /// This version differs from the reference Falcon512 implementation in its use of the RPO + /// algebraic hash function in its hash-to-point algorithm to make signatures very efficient + /// to verify inside Miden VM. RpoFalcon512 = RPO_FALCON_512, + + /// ECDSA signature scheme over secp256k1 curve using Keccak to hash the messages when signing. + EcdsaK256Keccak = ECDSA_K256_KECCAK, } impl AuthScheme { @@ -38,6 +47,7 @@ impl core::fmt::Display for AuthScheme { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::RpoFalcon512 => f.write_str("RpoFalcon512"), + Self::EcdsaK256Keccak => f.write_str("EcdsaK256Keccak"), } } } @@ -48,6 +58,7 @@ impl TryFrom for AuthScheme { fn try_from(value: u8) -> Result { match value { RPO_FALCON_512 => Ok(Self::RpoFalcon512), + ECDSA_K256_KECCAK => Ok(Self::EcdsaK256Keccak), value => Err(AuthSchemeError::InvalidAuthSchemeIdentifier(value)), } } @@ -68,6 +79,7 @@ impl Deserializable for AuthScheme { fn read_from(source: &mut R) -> Result { match source.read_u8()? { RPO_FALCON_512 => Ok(Self::RpoFalcon512), + ECDSA_K256_KECCAK => Ok(Self::EcdsaK256Keccak), value => Err(DeserializationError::InvalidValue(format!( "auth scheme identifier `{value}` is not valid" ))), @@ -84,6 +96,7 @@ impl Deserializable for AuthScheme { #[repr(u8)] pub enum AuthSecretKey { RpoFalcon512(rpo_falcon512::SecretKey) = RPO_FALCON_512, + EcdsaK256Keccak(ecdsa_k256_keccak::SecretKey) = ECDSA_K256_KECCAK, } impl AuthSecretKey { @@ -98,10 +111,22 @@ impl AuthSecretKey { Self::RpoFalcon512(rpo_falcon512::SecretKey::with_rng(rng)) } + /// Generates an EcdsaK256Keccak secret key from the OS-provided randomness. + #[cfg(feature = "std")] + pub fn new_ecdsa_k256_keccak() -> Self { + Self::EcdsaK256Keccak(ecdsa_k256_keccak::SecretKey::new()) + } + + /// Generates an EcdsaK256Keccak secret key using the provided random number generator. + pub fn new_ecdsa_k256_keccak_with_rng(rng: &mut R) -> Self { + Self::EcdsaK256Keccak(ecdsa_k256_keccak::SecretKey::with_rng(rng)) + } + /// Returns the authentication scheme of this secret key. pub fn auth_scheme(&self) -> AuthScheme { match self { AuthSecretKey::RpoFalcon512(_) => AuthScheme::RpoFalcon512, + AuthSecretKey::EcdsaK256Keccak(_) => AuthScheme::EcdsaK256Keccak, } } @@ -109,6 +134,7 @@ impl AuthSecretKey { pub fn public_key(&self) -> PublicKey { match self { AuthSecretKey::RpoFalcon512(key) => PublicKey::RpoFalcon512(key.public_key()), + AuthSecretKey::EcdsaK256Keccak(key) => PublicKey::EcdsaK256Keccak(key.public_key()), } } @@ -116,6 +142,7 @@ impl AuthSecretKey { pub fn sign(&self, message: Word) -> Signature { match self { AuthSecretKey::RpoFalcon512(key) => Signature::RpoFalcon512(key.sign(message)), + AuthSecretKey::EcdsaK256Keccak(key) => Signature::EcdsaK256Keccak(key.sign(message)), } } } @@ -124,9 +151,8 @@ impl Serializable for AuthSecretKey { fn write_into(&self, target: &mut W) { self.auth_scheme().write_into(target); match self { - AuthSecretKey::RpoFalcon512(secret_key) => { - secret_key.write_into(target); - }, + AuthSecretKey::RpoFalcon512(key) => key.write_into(target), + AuthSecretKey::EcdsaK256Keccak(key) => key.write_into(target), } } } @@ -138,6 +164,10 @@ impl Deserializable for AuthSecretKey { let secret_key = rpo_falcon512::SecretKey::read_from(source)?; Ok(AuthSecretKey::RpoFalcon512(secret_key)) }, + AuthScheme::EcdsaK256Keccak => { + let secret_key = ecdsa_k256_keccak::SecretKey::read_from(source)?; + Ok(AuthSecretKey::EcdsaK256Keccak(secret_key)) + }, } } } @@ -178,6 +208,7 @@ impl From for PublicKeyCommitment { #[non_exhaustive] pub enum PublicKey { RpoFalcon512(rpo_falcon512::PublicKey), + EcdsaK256Keccak(ecdsa_k256_keccak::PublicKey), } impl PublicKey { @@ -185,6 +216,7 @@ impl PublicKey { pub fn auth_scheme(&self) -> AuthScheme { match self { PublicKey::RpoFalcon512(_) => AuthScheme::RpoFalcon512, + PublicKey::EcdsaK256Keccak(_) => AuthScheme::EcdsaK256Keccak, } } @@ -192,15 +224,20 @@ impl PublicKey { pub fn to_commitment(&self) -> PublicKeyCommitment { match self { PublicKey::RpoFalcon512(key) => key.to_commitment().into(), + PublicKey::EcdsaK256Keccak(key) => key.to_commitment().into(), } } /// Verifies the provided signature against the provided message and this public key. pub fn verify(&self, message: Word, signature: Signature) -> bool { match (self, signature) { - (PublicKey::RpoFalcon512(key), Signature::RpoFalcon512(signature)) => { - key.verify(message, &signature) + (PublicKey::RpoFalcon512(key), Signature::RpoFalcon512(sig)) => { + key.verify(message, &sig) }, + (PublicKey::EcdsaK256Keccak(key), Signature::EcdsaK256Keccak(sig)) => { + key.verify(message, &sig) + }, + _ => false, } } } @@ -209,9 +246,8 @@ impl Serializable for PublicKey { fn write_into(&self, target: &mut W) { self.auth_scheme().write_into(target); match self { - PublicKey::RpoFalcon512(pub_key) => { - pub_key.write_into(target); - }, + PublicKey::RpoFalcon512(pub_key) => pub_key.write_into(target), + PublicKey::EcdsaK256Keccak(pub_key) => pub_key.write_into(target), } } } @@ -223,6 +259,10 @@ impl Deserializable for PublicKey { let pub_key = rpo_falcon512::PublicKey::read_from(source)?; Ok(PublicKey::RpoFalcon512(pub_key)) }, + AuthScheme::EcdsaK256Keccak => { + let pub_key = ecdsa_k256_keccak::PublicKey::read_from(source)?; + Ok(PublicKey::EcdsaK256Keccak(pub_key)) + }, } } } @@ -249,6 +289,7 @@ impl Deserializable for PublicKey { #[repr(u8)] pub enum Signature { RpoFalcon512(rpo_falcon512::Signature) = RPO_FALCON_512, + EcdsaK256Keccak(ecdsa_k256_keccak::Signature) = ECDSA_K256_KECCAK, } impl Signature { @@ -256,6 +297,7 @@ impl Signature { pub fn auth_scheme(&self) -> AuthScheme { match self { Signature::RpoFalcon512(_) => AuthScheme::RpoFalcon512, + Signature::EcdsaK256Keccak(_) => AuthScheme::EcdsaK256Keccak, } } @@ -263,7 +305,8 @@ impl Signature { /// native verification procedure in the VM. pub fn to_prepared_signature(&self) -> Vec { match self { - Signature::RpoFalcon512(signature) => prepare_rpo_falcon512_signature(signature), + Signature::RpoFalcon512(sig) => prepare_rpo_falcon512_signature(sig), + Signature::EcdsaK256Keccak(sig) => prepare_ecdsa_k256_keccak_signature(sig), } } } @@ -278,9 +321,8 @@ impl Serializable for Signature { fn write_into(&self, target: &mut W) { self.auth_scheme().write_into(target); match self { - Signature::RpoFalcon512(signature) => { - signature.write_into(target); - }, + Signature::RpoFalcon512(signature) => signature.write_into(target), + Signature::EcdsaK256Keccak(signature) => signature.write_into(target), } } } @@ -292,6 +334,10 @@ impl Deserializable for Signature { let signature = rpo_falcon512::Signature::read_from(source)?; Ok(Signature::RpoFalcon512(signature)) }, + AuthScheme::EcdsaK256Keccak => { + let signature = ecdsa_k256_keccak::Signature::read_from(source)?; + Ok(Signature::EcdsaK256Keccak(signature)) + }, } } } @@ -346,3 +392,21 @@ fn prepare_rpo_falcon512_signature(sig: &rpo_falcon512::Signature) -> Vec result.reverse(); result } + +/// Converts a ECDSA [ecdsa_k256_keccak::Signature] to a vector of values to be pushed to be +/// written to memory. The values are the ones required for a ECDSA signature precompile inside +/// the Miden VM. +fn prepare_ecdsa_k256_keccak_signature(sig: &ecdsa_k256_keccak::Signature) -> Vec { + const BYTES_PER_U32: usize = size_of::(); + + let bytes = sig.to_bytes(); + bytes + .chunks(BYTES_PER_U32) + .map(|chunk| { + // Pack up to 4 bytes into a u32 in little-endian format + let mut packed = [0u8; BYTES_PER_U32]; + packed[..chunk.len()].copy_from_slice(chunk); + Felt::from(u32::from_le_bytes(packed)) + }) + .collect() +} From bdbc57257b5c4bd4dea5246e67c684fb1f259d45 Mon Sep 17 00:00:00 2001 From: Marti Date: Wed, 5 Nov 2025 08:18:10 +0100 Subject: [PATCH 131/133] feat: add encryption field to address `RoutingParameters` (#2050) --- CHANGELOG.md | 1 + crates/miden-objects/src/address/mod.rs | 99 +++++- .../src/address/routing_parameters.rs | 306 +++++++++++++++--- crates/miden-objects/src/lib.rs | 2 +- 4 files changed, 361 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8699d7f58..bd8532d32b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032), [#2047](https://github.com/0xMiden/miden-base/pull/2047)). - [BREAKING] Refactor `PartialVault`, `PartialStorageMap`, `PartialAccountTree` and `PartialNullifierTree` to allow construction from a root ([#2042](https://github.com/0xMiden/miden-base/pull/2042)). - [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032)). +- Added `encryption_key` to `RoutingParameters` ([#2050](https://github.com/0xMiden/miden-base/pull/2050)). - [BREAKING] Added `EcdsaK256Keccak` variant to auth enums ([#2052](https://github.com/0xMiden/miden-base/pull/2052)). ### Changes diff --git a/crates/miden-objects/src/address/mod.rs b/crates/miden-objects/src/address/mod.rs index dd39555b6a..142dd9e8b8 100644 --- a/crates/miden-objects/src/address/mod.rs +++ b/crates/miden-objects/src/address/mod.rs @@ -18,6 +18,7 @@ pub use network_id::{CustomNetworkId, NetworkId}; use crate::AddressError; use crate::account::AccountStorageMode; +use crate::crypto::ies::SealingKey; use crate::note::NoteTag; use crate::utils::serde::{ByteWriter, Deserializable, Serializable}; @@ -34,6 +35,7 @@ pub use address_id::AddressId; /// about various aspects like: /// - what kind of note the receiver's account can consume. /// - how the receiver discovers the note. +/// - how to encrypt the note for the receiver. /// /// It can be encoded to a string using [`Self::encode`] and decoded using [`Self::decode`]. /// If routing parameters are present, the ID and parameters are separated by @@ -43,16 +45,18 @@ pub use address_id::AddressId; /// /// ```text /// # account ID -/// mm1apk5f8jqxnadegr46xtklmm78qhdgkwc -/// # account ID + routing parameters -/// mm1apk5f8jqxnadegr46xtklmm78qhdgkwc_qrcqzlvsfdp +/// mm1apt3l475qemeqqp57xjycfdwcvw0sfhq +/// # account ID + routing parameters (interface & note tag length) +/// mm1apt3l475qemeqqp57xjycfdwcvw0sfhq_qruqqypuyph +/// # account ID + routing parameters (interface, note tag length, encryption key) +/// mm1apt3l475qemeqqp57xjycfdwcvw0sfhq_qruqqqgqjmsgjsh3687mt2w0qtqunxt3th442j48qwdnezl0fv6qm3x9c8zqsv7pku /// ``` /// /// The encoding of an address without routing parameters matches the encoding of the underlying /// identifier exactly (e.g. an account ID). This provides compatibility between identifiers and /// addresses and gives end-users a hint that an address is only an extension of the identifier /// (e.g. their account's ID) that they are likely to recognize. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Address { id: AddressId, routing_params: Option, @@ -147,6 +151,13 @@ impl Address { } } + /// Returns the optional public encryption key from routing parameters. + /// + /// This key can be used for sealed box encryption when sending notes to this address. + pub fn encryption_key(&self) -> Option<&SealingKey> { + self.routing_params.as_ref().and_then(RoutingParameters::encryption_key) + } + /// Encodes the [`Address`] into a string. /// /// ## Encoding @@ -430,4 +441,84 @@ mod tests { Ok(()) } + + /// Tests that an address with encryption key can be created and used. + #[test] + fn address_with_encryption_key() -> anyhow::Result<()> { + use crate::crypto::dsa::eddsa_25519::SecretKey; + use crate::crypto::ies::{SealingKey, UnsealingKey}; + + let rng = &mut rand::rng(); + let account_id = AccountIdBuilder::new() + .account_type(AccountType::FungibleFaucet) + .build_with_rng(rng); + + // Create keypair using rand::rng() + let secret_key = SecretKey::with_rng(rng); + let public_key = secret_key.public_key(); + let sealing_key = SealingKey::X25519XChaCha20Poly1305(public_key.clone()); + let unsealing_key = UnsealingKey::X25519XChaCha20Poly1305(secret_key.clone()); + + // Create address with encryption key + let address = Address::new(account_id).with_routing_parameters( + RoutingParameters::new(AddressInterface::BasicWallet) + .with_encryption_key(sealing_key.clone()), + )?; + + // Verify encryption key is present + let retrieved_key = + address.encryption_key().expect("encryption key should be present").clone(); + assert_eq!(retrieved_key, sealing_key); + + // Test seal/unseal round-trip + let plaintext = b"hello world"; + let sealed_message = + retrieved_key.seal_bytes(rng, plaintext).expect("sealing should succeed"); + let decrypted = + unsealing_key.unseal_bytes(sealed_message).expect("unsealing should succeed"); + assert_eq!(decrypted.as_slice(), plaintext); + + Ok(()) + } + + /// Tests that an address with encryption key can be encoded/decoded. + #[test] + fn address_encryption_key_encode_decode() -> anyhow::Result<()> { + use crate::crypto::dsa::eddsa_25519::SecretKey; + + let rng = &mut rand::rng(); + // Use a local account type (RegularAccountImmutableCode) instead of network + // (FungibleFaucet) + let account_id = AccountIdBuilder::new() + .account_type(AccountType::RegularAccountImmutableCode) + .storage_mode(AccountStorageMode::Public) + .build_with_rng(rng); + + // Create keypair + let secret_key = SecretKey::with_rng(rng); + let public_key = secret_key.public_key(); + let sealing_key = SealingKey::X25519XChaCha20Poly1305(public_key); + + // Create address with encryption key + let address = Address::new(account_id).with_routing_parameters( + RoutingParameters::new(AddressInterface::BasicWallet) + .with_encryption_key(sealing_key.clone()), + )?; + + // Encode and decode + let encoded = address.encode(NetworkId::Mainnet); + let (decoded_network, decoded_address) = Address::decode(&encoded)?; + + assert_eq!(decoded_network, NetworkId::Mainnet); + assert_eq!(address, decoded_address); + + // Verify encryption key is preserved + let decoded_key = decoded_address + .encryption_key() + .expect("encryption key should be present") + .clone(); + assert_eq!(decoded_key, sealing_key); + + Ok(()) + } } diff --git a/crates/miden-objects/src/address/routing_parameters.rs b/crates/miden-objects/src/address/routing_parameters.rs index d0c9798465..5d173f9ee6 100644 --- a/crates/miden-objects/src/address/routing_parameters.rs +++ b/crates/miden-objects/src/address/routing_parameters.rs @@ -7,6 +7,8 @@ use bech32::{Bech32m, Hrp}; use crate::AddressError; use crate::address::AddressInterface; +use crate::crypto::dsa::{ecdsa_k256_keccak, eddsa_25519}; +use crate::crypto::ies::SealingKey; use crate::errors::Bech32Error; use crate::note::NoteTag; use crate::utils::serde::{ @@ -42,12 +44,28 @@ const ABSENT_NOTE_TAG_LEN: u8 = (1 << 5) - 1; // 31 /// The routing parameter key for the receiver profile. const RECEIVER_PROFILE_PARAM_KEY: u8 = 0; +/// The routing parameter key for the encryption key. +const ENCRYPTION_KEY_PARAM_KEY: u8 = 1; + +/// The expected length of Ed25519/X25519 public keys in bytes. +const X25519_PUBLIC_KEY_LENGTH: usize = 32; + +/// The expected length of K256 (secp256k1) public keys in bytes (compressed format). +const K256_PUBLIC_KEY_LENGTH: usize = 33; + +/// Discriminants for encryption key variants. +const ENCRYPTION_KEY_X25519_XCHACHA20POLY1305: u8 = 0; +const ENCRYPTION_KEY_K256_XCHACHA20POLY1305: u8 = 1; +const ENCRYPTION_KEY_X25519_AEAD_RPO: u8 = 2; +const ENCRYPTION_KEY_K256_AEAD_RPO: u8 = 3; + /// Parameters that define how a sender should route a note to the [`AddressId`](super::AddressId) /// in an [`Address`](super::Address). -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct RoutingParameters { interface: AddressInterface, note_tag_len: Option, + encryption_key: Option, } impl RoutingParameters { @@ -57,7 +75,11 @@ impl RoutingParameters { /// Creates new [`RoutingParameters`] from an [`AddressInterface`] and all other parameters /// initialized to `None`. pub fn new(interface: AddressInterface) -> Self { - Self { interface, note_tag_len: None } + Self { + interface, + note_tag_len: None, + encryption_key: None, + } } /// Sets the note tag length routing parameter. @@ -98,6 +120,20 @@ impl RoutingParameters { self.interface } + /// Returns the public encryption key. + pub fn encryption_key(&self) -> Option<&SealingKey> { + self.encryption_key.as_ref() + } + + /// Sets the encryption key routing parameter. + /// + /// This allows senders to encrypt note payloads using sealed box encryption + /// for the recipient of this address. + pub fn with_encryption_key(mut self, key: SealingKey) -> Self { + self.encryption_key = Some(key); + self + } + // HELPERS // -------------------------------------------------------------------------------------------- @@ -105,24 +141,15 @@ impl RoutingParameters { pub(crate) fn encode_to_bytes(&self) -> Vec { let mut encoded = Vec::new(); - let note_tag_len = self.note_tag_len.unwrap_or(ABSENT_NOTE_TAG_LEN); - - let interface = self.interface as u16; - debug_assert_eq!( - interface >> 11, - 0, - "address interface should have its upper 5 bits unset" - ); - - // The interface takes up 11 bits and the tag length 5 bits, so we can merge them - // together. - let tag_len = (note_tag_len as u16) << 11; - let receiver_profile: u16 = tag_len | interface; - let receiver_profile: [u8; 2] = receiver_profile.to_be_bytes(); - // Append the receiver profile key and the encoded value to the vector. encoded.push(RECEIVER_PROFILE_PARAM_KEY); - encoded.extend(receiver_profile); + encoded.extend(encode_receiver_profile(self.interface, self.note_tag_len)); + + // Append the encryption key if present. + if let Some(encryption_key) = &self.encryption_key { + encoded.push(ENCRYPTION_KEY_PARAM_KEY); + encode_encryption_key(encryption_key, &mut encoded); + } encoded } @@ -173,36 +200,27 @@ impl RoutingParameters { ) -> Result { let mut interface = None; let mut note_tag_len = None; + let mut encryption_key = None; while let Some(key) = byte_iter.next() { match key { RECEIVER_PROFILE_PARAM_KEY => { - if byte_iter.len() < 2 { + if interface.is_some() { + return Err(AddressError::decode_error( + "duplicate receiver profile routing parameter", + )); + } + let receiver_profile = decode_receiver_profile(&mut byte_iter)?; + interface = Some(receiver_profile.0); + note_tag_len = receiver_profile.1; + }, + ENCRYPTION_KEY_PARAM_KEY => { + if encryption_key.is_some() { return Err(AddressError::decode_error( - "expected two bytes to decode receiver profile", + "duplicate encryption key routing parameter", )); - }; - - let byte0 = byte_iter.next().expect("byte0 should exist"); - let byte1 = byte_iter.next().expect("byte1 should exist"); - let receiver_profile = u16::from_be_bytes([byte0, byte1]); - - let tag_len = (receiver_profile >> 11) as u8; - note_tag_len = if tag_len == ABSENT_NOTE_TAG_LEN { - None - } else { - Some(tag_len) - }; - - let addr_interface = receiver_profile & 0b0000_0111_1111_1111; - let addr_interface = - AddressInterface::try_from(addr_interface).map_err(|err| { - AddressError::decode_error_with_source( - "failed to decode address interface", - err, - ) - })?; - interface = Some(addr_interface); + } + encryption_key = Some(decode_encryption_key(&mut byte_iter)?); }, other => { return Err(AddressError::UnknownRoutingParameterKey(other)); @@ -216,6 +234,7 @@ impl RoutingParameters { let mut routing_parameters = RoutingParameters::new(interface); routing_parameters.note_tag_len = note_tag_len; + routing_parameters.encryption_key = encryption_key; Ok(routing_parameters) } @@ -242,6 +261,147 @@ impl Deserializable for RoutingParameters { } } +// ENCODING / DECODING HELPERS +// ================================================================================================ + +/// Returns receiver profile bytes constructed from the provided interface and note tag length. +fn encode_receiver_profile(interface: AddressInterface, note_tag_len: Option) -> [u8; 2] { + let note_tag_len = note_tag_len.unwrap_or(ABSENT_NOTE_TAG_LEN); + + let interface = interface as u16; + debug_assert_eq!(interface >> 11, 0, "address interface should have its upper 5 bits unset"); + + // The interface takes up 11 bits and the tag length 5 bits, so we can merge them + // together. + let tag_len = (note_tag_len as u16) << 11; + let receiver_profile: u16 = tag_len | interface; + receiver_profile.to_be_bytes() +} + +/// Reads the receiver profile from the provided bytes. +fn decode_receiver_profile( + byte_iter: &mut impl ExactSizeIterator, +) -> Result<(AddressInterface, Option), AddressError> { + if byte_iter.len() < 2 { + return Err(AddressError::decode_error("expected two bytes to decode receiver profile")); + }; + + let byte0 = byte_iter.next().expect("byte0 should exist"); + let byte1 = byte_iter.next().expect("byte1 should exist"); + let receiver_profile = u16::from_be_bytes([byte0, byte1]); + + let tag_len = (receiver_profile >> 11) as u8; + let note_tag_len = if tag_len == ABSENT_NOTE_TAG_LEN { + None + } else { + Some(tag_len) + }; + + let addr_interface = receiver_profile & 0b0000_0111_1111_1111; + let addr_interface = AddressInterface::try_from(addr_interface).map_err(|err| { + AddressError::decode_error_with_source("failed to decode address interface", err) + })?; + + Ok((addr_interface, note_tag_len)) +} + +/// Append encryption key variant discriminant and key to the provided vector of bytes. +fn encode_encryption_key(key: &SealingKey, encoded: &mut Vec) { + match key { + SealingKey::X25519XChaCha20Poly1305(pk) => { + encoded.push(ENCRYPTION_KEY_X25519_XCHACHA20POLY1305); + encoded.extend(&pk.to_bytes()); + }, + SealingKey::K256XChaCha20Poly1305(pk) => { + encoded.push(ENCRYPTION_KEY_K256_XCHACHA20POLY1305); + encoded.extend(&pk.to_bytes()); + }, + SealingKey::X25519AeadRpo(pk) => { + encoded.push(ENCRYPTION_KEY_X25519_AEAD_RPO); + encoded.extend(&pk.to_bytes()); + }, + SealingKey::K256AeadRpo(pk) => { + encoded.push(ENCRYPTION_KEY_K256_AEAD_RPO); + encoded.extend(&pk.to_bytes()); + }, + } +} + +/// Reads the encryption key from the provided bytes. +fn decode_encryption_key( + byte_iter: &mut impl ExactSizeIterator, +) -> Result { + // Read variant discriminant + let Some(variant) = byte_iter.next() else { + return Err(AddressError::decode_error( + "expected at least 1 byte for encryption key variant", + )); + }; + + // Reconstruct the appropriate PublicEncryptionKey variant + let public_encryption_key = match variant { + ENCRYPTION_KEY_X25519_XCHACHA20POLY1305 => { + SealingKey::X25519XChaCha20Poly1305(read_x25519_pub_key(byte_iter)?) + }, + ENCRYPTION_KEY_K256_XCHACHA20POLY1305 => { + SealingKey::K256XChaCha20Poly1305(read_k256_pub_key(byte_iter)?) + }, + ENCRYPTION_KEY_X25519_AEAD_RPO => { + SealingKey::X25519AeadRpo(read_x25519_pub_key(byte_iter)?) + }, + ENCRYPTION_KEY_K256_AEAD_RPO => SealingKey::K256AeadRpo(read_k256_pub_key(byte_iter)?), + other => { + return Err(AddressError::decode_error(format!( + "unknown encryption key variant: {}", + other + ))); + }, + }; + + Ok(public_encryption_key) +} + +fn read_x25519_pub_key( + byte_iter: &mut impl ExactSizeIterator, +) -> Result { + if byte_iter.len() < X25519_PUBLIC_KEY_LENGTH { + return Err(AddressError::decode_error(format!( + "expected {} bytes to decode X25519 public key", + X25519_PUBLIC_KEY_LENGTH + ))); + } + let key_bytes: [u8; X25519_PUBLIC_KEY_LENGTH] = read_byte_array(byte_iter); + eddsa_25519::PublicKey::read_from_bytes(&key_bytes).map_err(|err| { + AddressError::decode_error_with_source("failed to decode X25519 public key", err) + }) +} + +fn read_k256_pub_key( + byte_iter: &mut impl ExactSizeIterator, +) -> Result { + if byte_iter.len() < K256_PUBLIC_KEY_LENGTH { + return Err(AddressError::decode_error(format!( + "expected {} bytes to decode K256 public key", + K256_PUBLIC_KEY_LENGTH + ))); + } + let key_bytes: [u8; K256_PUBLIC_KEY_LENGTH] = read_byte_array(byte_iter); + ecdsa_k256_keccak::PublicKey::read_from_bytes(&key_bytes).map_err(|err| { + AddressError::decode_error_with_source("failed to decode K256 public key", err) + }) +} + +/// Reads bytes from the provided iterator into an array of length N and returns this array. +/// +/// Assumes that there are at least N bytes in the iterator. +fn read_byte_array(byte_iter: &mut impl ExactSizeIterator) -> [u8; N] { + let mut array = [0u8; N]; + for byte in array.iter_mut() { + *byte = byte_iter.next().expect("iterator should have enough bytes"); + } + array +} + // TESTS // ================================================================================================ @@ -352,4 +512,66 @@ mod tests { Ok(()) } + + /// Tests encoding/decoding and serialization for all encryption key variants. + #[test] + fn routing_parameters_all_encryption_key_variants() -> anyhow::Result<()> { + // Helper function to test both encoding/decoding and serialization + fn test_encryption_key_roundtrip(encryption_key: SealingKey) -> anyhow::Result<()> { + let routing_params = RoutingParameters::new(AddressInterface::BasicWallet) + .with_encryption_key(encryption_key.clone()); + + // Test bech32 encoding/decoding + let encoded = routing_params.encode_to_string(); + let decoded = RoutingParameters::decode(encoded)?; + assert_eq!(routing_params, decoded); + assert_eq!(decoded.encryption_key(), Some(&encryption_key)); + + // Test serialization/deserialization + let serialized = routing_params.to_bytes(); + let deserialized = RoutingParameters::read_from_bytes(&serialized)?; + assert_eq!(routing_params, deserialized); + assert_eq!(deserialized.encryption_key(), Some(&encryption_key)); + + Ok(()) + } + + // Test X25519XChaCha20Poly1305 + { + use crate::crypto::dsa::eddsa_25519::SecretKey; + let secret_key = SecretKey::with_rng(&mut rand::rng()); + let public_key = secret_key.public_key(); + let encryption_key = SealingKey::X25519XChaCha20Poly1305(public_key); + test_encryption_key_roundtrip(encryption_key)?; + } + + // Test K256XChaCha20Poly1305 + { + use crate::crypto::dsa::ecdsa_k256_keccak::SecretKey; + let secret_key = SecretKey::with_rng(&mut rand::rng()); + let public_key = secret_key.public_key(); + let encryption_key = SealingKey::K256XChaCha20Poly1305(public_key); + test_encryption_key_roundtrip(encryption_key)?; + } + + // Test X25519AeadRpo + { + use crate::crypto::dsa::eddsa_25519::SecretKey; + let secret_key = SecretKey::with_rng(&mut rand::rng()); + let public_key = secret_key.public_key(); + let encryption_key = SealingKey::X25519AeadRpo(public_key); + test_encryption_key_roundtrip(encryption_key)?; + } + + // Test K256AeadRpo + { + use crate::crypto::dsa::ecdsa_k256_keccak::SecretKey; + let secret_key = SecretKey::with_rng(&mut rand::rng()); + let public_key = secret_key.public_key(); + let encryption_key = SealingKey::K256AeadRpo(public_key); + test_encryption_key_roundtrip(encryption_key)?; + } + + Ok(()) + } } diff --git a/crates/miden-objects/src/lib.rs b/crates/miden-objects/src/lib.rs index ae78569bb4..34f9219efb 100644 --- a/crates/miden-objects/src/lib.rs +++ b/crates/miden-objects/src/lib.rs @@ -80,7 +80,7 @@ pub mod assembly { } pub mod crypto { - pub use miden_crypto::{SequentialCommit, dsa, hash, merkle, rand, utils}; + pub use miden_crypto::{SequentialCommit, dsa, hash, ies, merkle, rand, utils}; } pub mod utils { From 5b9f2a727a555b0eed07ed0277b9ab38cd3e3969 Mon Sep 17 00:00:00 2001 From: igamigo Date: Wed, 5 Nov 2025 06:59:05 -0300 Subject: [PATCH 132/133] feat: support full map templates (#2053) --- CHANGELOG.md | 1 + .../src/account/component/template/mod.rs | 31 +- .../template/storage/entry_content.rs | 222 +++++++++----- .../template/storage/init_storage_data.rs | 34 ++- .../account/component/template/storage/mod.rs | 288 ++++++++++++++++-- .../component/template/storage/placeholder.rs | 5 + .../component/template/storage/toml.rs | 149 +++++++-- docs/src/account/components.md | 53 +++- 8 files changed, 636 insertions(+), 147 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd8532d32b..0edc734060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032)). - Added `encryption_key` to `RoutingParameters` ([#2050](https://github.com/0xMiden/miden-base/pull/2050)). - [BREAKING] Added `EcdsaK256Keccak` variant to auth enums ([#2052](https://github.com/0xMiden/miden-base/pull/2052)). +- Implemented storage map templates, which can be initialized through key/value lists provided via `InitStorageData` TOML ([#2053](https://github.com/0xMiden/miden-base/pull/2053)). ### Changes diff --git a/crates/miden-objects/src/account/component/template/mod.rs b/crates/miden-objects/src/account/component/template/mod.rs index 01c32dde7a..c646fa50a1 100644 --- a/crates/miden-objects/src/account/component/template/mod.rs +++ b/crates/miden-objects/src/account/component/template/mod.rs @@ -104,7 +104,7 @@ impl Deserializable for AccountComponentTemplate { /// /// ``` /// # use semver::Version; -/// # use std::collections::BTreeSet; +/// # use std::collections::{BTreeMap, BTreeSet}; /// # use miden_objects::{testing::account_code::CODE, account::{ /// # AccountComponent, AccountComponentMetadata, StorageEntry, /// # StorageValueName, @@ -126,8 +126,10 @@ impl Deserializable for AccountComponentTemplate { /// .with_description("this is the first entry in the storage layout"); /// let storage_entry = StorageEntry::new_value(0, word_representation); /// -/// let init_storage_data = -/// InitStorageData::new([(StorageValueName::new("test_value.foo")?, "300".to_string())]); +/// let init_storage_data = InitStorageData::new( +/// [(StorageValueName::new("test_value.foo")?, "300".to_string())], +/// BTreeMap::new(), +/// ); /// /// let component_template = AccountComponentMetadata::new( /// "test name".into(), @@ -327,7 +329,7 @@ impl Deserializable for AccountComponentMetadata { #[cfg(test)] mod tests { - use std::collections::BTreeSet; + use std::collections::{BTreeMap, BTreeSet}; use std::string::ToString; use assert_matches::assert_matches; @@ -377,7 +379,7 @@ mod tests { storage, }; - let serialized = original_config.as_toml().unwrap(); + let serialized = original_config.to_toml().unwrap(); let deserialized = AccountComponentMetadata::from_toml(&serialized).unwrap(); assert_eq!(deserialized, original_config); } @@ -500,10 +502,13 @@ mod tests { // Fail to instantiate on a duplicate key - let init_storage_data = InitStorageData::new([( - StorageValueName::new("map.duplicate_key").unwrap(), - "0x0000000000000000000000000000000000000000000000000100000000000000".to_string(), - )]); + let init_storage_data = InitStorageData::new( + [( + StorageValueName::new("map.duplicate_key").unwrap(), + "0x0000000000000000000000000000000000000000000000000100000000000000".to_string(), + )], + BTreeMap::new(), + ); let account_component = AccountComponent::from_template(&template, &init_storage_data); assert_matches!( account_component, @@ -513,10 +518,10 @@ mod tests { ); // Successfully instantiate a map (keys are not duplicate) - let valid_init_storage_data = InitStorageData::new([( - StorageValueName::new("map.duplicate_key").unwrap(), - "0x30".to_string(), - )]); + let valid_init_storage_data = InitStorageData::new( + [(StorageValueName::new("map.duplicate_key").unwrap(), "0x30".to_string())], + BTreeMap::new(), + ); AccountComponent::from_template(&template, &valid_init_storage_data).unwrap(); } } diff --git a/crates/miden-objects/src/account/component/template/storage/entry_content.rs b/crates/miden-objects/src/account/component/template/storage/entry_content.rs index 221c7d59ee..54780435b3 100644 --- a/crates/miden-objects/src/account/component/template/storage/entry_content.rs +++ b/crates/miden-objects/src/account/component/template/storage/entry_content.rs @@ -466,30 +466,52 @@ impl Deserializable for FeltRepresentation { /// Supported map representations for a component's storage entries. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(::serde::Deserialize, ::serde::Serialize))] -pub struct MapRepresentation { - /// The human-readable name of the map slot. - /// An optional description for the slot, explaining its purpose. - identifier: FieldIdentifier, - /// Storage map entries, consisting of a list of keys associated with their values. - entries: Vec, +pub enum MapRepresentation { + /// A map whose contents are provided during instantiation via placeholders. + Template { + /// The human-readable identifier of the map slot. + identifier: FieldIdentifier, + }, + /// A map with statically defined key/value pairs. + Value { + /// The human-readable identifier of the map slot. + identifier: FieldIdentifier, + /// Storage map entries, consisting of a list of keys associated with their values. + entries: Vec, + }, } impl MapRepresentation { /// Creates a new `MapRepresentation` from a vector of map entries. - pub fn new(entries: Vec, name: impl Into) -> Self { - Self { + pub fn new_value(entries: Vec, name: impl Into) -> Self { + MapRepresentation::Value { entries, identifier: FieldIdentifier::with_name(name.into()), } } + /// Creates a new templated map representation. + pub fn new_template(name: impl Into) -> Self { + MapRepresentation::Template { + identifier: FieldIdentifier::with_name(name.into()), + } + } + /// Sets the description of the [`MapRepresentation`] and returns `self`. pub fn with_description(self, description: impl Into) -> Self { - MapRepresentation { - entries: self.entries, - identifier: FieldIdentifier { - name: self.identifier.name, - description: Some(description.into()), + match self { + MapRepresentation::Template { identifier } => MapRepresentation::Template { + identifier: FieldIdentifier { + name: identifier.name, + description: Some(description.into()), + }, + }, + MapRepresentation::Value { identifier, entries } => MapRepresentation::Value { + entries, + identifier: FieldIdentifier { + name: identifier.name, + description: Some(description.into()), + }, }, } } @@ -497,36 +519,60 @@ impl MapRepresentation { /// Returns an iterator over all of the storage entries' placeholder keys, alongside their /// expected type. pub fn template_requirements(&self) -> TemplateRequirementsIter<'_> { - Box::new( - self.entries - .iter() - .flat_map(move |entry| entry.template_requirements(self.identifier.name.clone())), - ) + match self { + MapRepresentation::Template { identifier } => Box::new(iter::once(( + identifier.name.clone(), + PlaceholderTypeRequirement { + description: identifier.description.clone(), + r#type: TemplateType::storage_map(), + }, + ))), + MapRepresentation::Value { identifier, entries } => Box::new( + entries + .iter() + .flat_map(move |entry| entry.template_requirements(identifier.name.clone())), + ), + } } /// Returns a reference to map entries. pub fn entries(&self) -> &[MapEntry] { - &self.entries + match self { + MapRepresentation::Value { entries, .. } => entries, + MapRepresentation::Template { .. } => &[], + } } /// Returns a reference to the map's name within the storage metadata. pub fn name(&self) -> &StorageValueName { - &self.identifier.name + match self { + MapRepresentation::Template { identifier } + | MapRepresentation::Value { identifier, .. } => &identifier.name, + } } /// Returns a reference to the field's description. pub fn description(&self) -> Option<&String> { - self.identifier.description.as_ref() + match self { + MapRepresentation::Template { identifier } + | MapRepresentation::Value { identifier, .. } => identifier.description.as_ref(), + } } - /// Returns the number of key-value pairs in the map. + /// Returns the number of statically defined key-value pairs in the map. pub fn len(&self) -> usize { - self.entries.len() + match self { + MapRepresentation::Value { entries, .. } => entries.len(), + MapRepresentation::Template { .. } => 0, + } } - /// Returns `true` if there are no entries in the map. + /// Returns `true` if there are no statically defined entries in the map. pub fn is_empty(&self) -> bool { - self.entries.is_empty() + match self { + MapRepresentation::Value { entries, .. } => entries.is_empty(), + MapRepresentation::Template { .. } => true, + } } /// Attempts to convert the [MapRepresentation] into a [StorageMap]. @@ -537,66 +583,98 @@ impl MapRepresentation { &self, init_storage_data: &InitStorageData, ) -> Result { - let entries = self - .entries - .iter() - .map(|map_entry| { - let key = map_entry - .key() - .try_build_word(init_storage_data, self.identifier.name.clone())?; - let value = map_entry - .value() - .try_build_word(init_storage_data, self.identifier.name.clone())?; - Ok((key, value)) - }) - .collect::, _>>()?; - - StorageMap::with_entries(entries) - .map_err(|err| AccountComponentTemplateError::StorageMapHasDuplicateKeys(Box::new(err))) - } - - /// Validates map keys by checking for duplicates. - /// - /// Because keys can be represented in a variety of ways, the `to_string()` implementation is - /// used to check for duplicates. - pub(crate) fn validate(&self) -> Result<(), AccountComponentTemplateError> { - let mut seen_keys = BTreeSet::new(); - for entry in self.entries() { - entry.key().validate()?; - entry.value().validate()?; - if let Ok(key) = entry - .key() - .try_build_word(&InitStorageData::default(), StorageValueName::empty()) - && !seen_keys.insert(key) - { - return Err(AccountComponentTemplateError::StorageMapHasDuplicateKeys(Box::from( - format!("key `{key}` is duplicated"), - ))); - } - } + match self { + MapRepresentation::Value { identifier, entries } => { + let entries = entries + .iter() + .map(|map_entry| { + let key = map_entry + .key() + .try_build_word(init_storage_data, identifier.name.clone())?; + let value = map_entry + .value() + .try_build_word(init_storage_data, identifier.name.clone())?; + Ok((key, value)) + }) + .collect::, _>>()?; + + StorageMap::with_entries(entries).map_err(|err| { + AccountComponentTemplateError::StorageMapHasDuplicateKeys(Box::new(err)) + }) + }, + MapRepresentation::Template { identifier } => { + if let Some(entries) = init_storage_data.map_entries(&identifier.name) { + return StorageMap::with_entries(entries.clone()).map_err(|err| { + AccountComponentTemplateError::StorageMapHasDuplicateKeys(Box::new(err)) + }); + } - Ok(()) + Err(AccountComponentTemplateError::PlaceholderValueNotProvided( + identifier.name.clone(), + )) + }, + } } -} -impl From for Vec { - fn from(value: MapRepresentation) -> Self { - value.entries + /// Validates the map representation by checking for duplicate keys and placeholder validity. + pub(crate) fn validate(&self) -> Result<(), AccountComponentTemplateError> { + match self { + MapRepresentation::Template { .. } => Ok(()), + MapRepresentation::Value { entries, .. } => { + let mut seen_keys = BTreeSet::new(); + for entry in entries.iter() { + entry.key().validate()?; + entry.value().validate()?; + if let Ok(key) = entry + .key() + .try_build_word(&InitStorageData::default(), StorageValueName::empty()) + && !seen_keys.insert(key) + { + return Err(AccountComponentTemplateError::StorageMapHasDuplicateKeys( + Box::from(format!("key `{key}` is duplicated")), + )); + } + } + + Ok(()) + }, + } } } impl Serializable for MapRepresentation { fn write_into(&self, target: &mut W) { - self.entries.write_into(target); - target.write(&self.identifier); + match self { + MapRepresentation::Value { identifier, entries } => { + target.write_u8(0u8); + target.write(identifier); + target.write(entries); + }, + MapRepresentation::Template { identifier } => { + target.write_u8(1u8); + target.write(identifier); + }, + } } } impl Deserializable for MapRepresentation { fn read_from(source: &mut R) -> Result { - let entries = Vec::::read_from(source)?; - let identifier = FieldIdentifier::read_from(source)?; - Ok(Self { entries, identifier }) + let tag = source.read_u8()?; + match tag { + 0 => { + let identifier = FieldIdentifier::read_from(source)?; + let entries = Vec::::read_from(source)?; + Ok(MapRepresentation::Value { entries, identifier }) + }, + 1 => { + let identifier = FieldIdentifier::read_from(source)?; + Ok(MapRepresentation::Template { identifier }) + }, + other => Err(DeserializationError::InvalidValue(format!( + "unknown tag '{other}' for MapRepresentation" + ))), + } } } diff --git a/crates/miden-objects/src/account/component/template/storage/init_storage_data.rs b/crates/miden-objects/src/account/component/template/storage/init_storage_data.rs index b8d6693125..7bf195ad07 100644 --- a/crates/miden-objects/src/account/component/template/storage/init_storage_data.rs +++ b/crates/miden-objects/src/account/component/template/storage/init_storage_data.rs @@ -1,7 +1,9 @@ use alloc::collections::BTreeMap; use alloc::string::String; +use alloc::vec::Vec; use super::StorageValueName; +use crate::Word; /// Represents the data required to initialize storage entries when instantiating an /// [AccountComponent](crate::account::AccountComponent) from a @@ -10,8 +12,12 @@ use super::StorageValueName; /// An [`InitStorageData`] can be created from a TOML string when the `std` feature flag is set. #[derive(Clone, Debug, Default)] pub struct InitStorageData { + // TODO: Both the below fields could be a single field with a two variant enum + // (eg, BTreeMap) /// A mapping of storage placeholder names to their corresponding storage values. - storage_placeholders: BTreeMap, + value_entries: BTreeMap, + /// A mapping of map placeholder names to their corresponding key/value entries. + map_entries: BTreeMap>, } impl InitStorageData { @@ -23,23 +29,35 @@ impl InitStorageData { /// # Parameters /// /// - `entries`: An iterable collection of key-value pairs. - pub fn new(entries: impl IntoIterator) -> Self { + /// - `map_entries`: An iterable collection of storage map entries keyed by placeholder. + pub fn new( + entries: impl IntoIterator, + map_entries: impl IntoIterator)>, + ) -> Self { + let value_entries = entries + .into_iter() + .filter(|(entry_name, _)| !entry_name.as_str().is_empty()) + .collect::>(); + InitStorageData { - storage_placeholders: entries - .into_iter() - .filter(|(entry_name, _)| !entry_name.as_str().is_empty()) - .collect(), + value_entries, + map_entries: map_entries.into_iter().collect(), } } /// Retrieves a reference to the storage placeholders. pub fn placeholders(&self) -> &BTreeMap { - &self.storage_placeholders + &self.value_entries } /// Returns a reference to the name corresponding to the placeholder, or /// [`Option::None`] if the placeholder is not present. pub fn get(&self, key: &StorageValueName) -> Option<&String> { - self.storage_placeholders.get(key) + self.value_entries.get(key) + } + + /// Returns the map entries associated with the given placeholder name, if any. + pub(crate) fn map_entries(&self, key: &StorageValueName) -> Option<&Vec<(Word, Word)>> { + self.map_entries.get(key) } } diff --git a/crates/miden-objects/src/account/component/template/storage/mod.rs b/crates/miden-objects/src/account/component/template/storage/mod.rs index af7d14a22f..e75e4c5ebc 100644 --- a/crates/miden-objects/src/account/component/template/storage/mod.rs +++ b/crates/miden-objects/src/account/component/template/storage/mod.rs @@ -372,7 +372,7 @@ impl Deserializable for MapEntry { #[cfg(test)] mod tests { - use alloc::collections::BTreeSet; + use alloc::collections::{BTreeMap, BTreeSet}; use alloc::string::ToString; use core::error::Error; use core::panic; @@ -421,7 +421,7 @@ mod tests { let test_word: Word = word!("0x000001"); let test_word = test_word.map(FeltRepresentation::from); - let map_representation = MapRepresentation::new( + let map_representation = MapRepresentation::new_value( vec![ MapEntry { key: WordRepresentation::new_template( @@ -496,7 +496,7 @@ mod tests { supported_types: BTreeSet::from([AccountType::FungibleFaucet]), storage, }; - let toml = config.as_toml().unwrap(); + let toml = config.to_toml().unwrap(); let deserialized = AccountComponentMetadata::from_toml(&toml).unwrap(); assert_eq!(deserialized, config); @@ -570,18 +570,21 @@ mod tests { assert_eq!(template, template_deserialized); // Fail to parse because 2800 > u8 - let storage_placeholders = InitStorageData::new([ - ( - StorageValueName::new("map_entry.map_key_template").unwrap(), - "0x123".to_string(), - ), - ( - StorageValueName::new("token_metadata.max_supply").unwrap(), - 20_000u64.to_string(), - ), - (StorageValueName::new("token_metadata.decimals").unwrap(), "2800".into()), - (StorageValueName::new("default_recallable_height").unwrap(), "0".into()), - ]); + let storage_placeholders = InitStorageData::new( + [ + ( + StorageValueName::new("map_entry.map_key_template").unwrap(), + "0x123".to_string(), + ), + ( + StorageValueName::new("token_metadata.max_supply").unwrap(), + 20_000u64.to_string(), + ), + (StorageValueName::new("token_metadata.decimals").unwrap(), "2800".into()), + (StorageValueName::new("default_recallable_height").unwrap(), "0".into()), + ], + BTreeMap::new(), + ); let component = AccountComponent::from_template(&template, &storage_placeholders); assert_matches::assert_matches!( @@ -594,18 +597,21 @@ mod tests { ); // Instantiate successfully - let storage_placeholders = InitStorageData::new([ - ( - StorageValueName::new("map_entry.map_key_template").unwrap(), - "0x123".to_string(), - ), - ( - StorageValueName::new("token_metadata.max_supply").unwrap(), - 20_000u64.to_string(), - ), - (StorageValueName::new("token_metadata.decimals").unwrap(), "128".into()), - (StorageValueName::new("default_recallable_height").unwrap(), "0x0".into()), - ]); + let storage_placeholders = InitStorageData::new( + [ + ( + StorageValueName::new("map_entry.map_key_template").unwrap(), + "0x123".to_string(), + ), + ( + StorageValueName::new("token_metadata.max_supply").unwrap(), + 20_000u64.to_string(), + ), + (StorageValueName::new("token_metadata.decimals").unwrap(), "128".into()), + (StorageValueName::new("default_recallable_height").unwrap(), "0x0".into()), + ], + BTreeMap::new(), + ); let component = AccountComponent::from_template(&template, &storage_placeholders).unwrap(); assert_eq!( @@ -658,6 +664,234 @@ mod tests { assert_matches::assert_matches!(err, AccountComponentTemplateError::InvalidType(_, _)) } + #[test] + fn map_template_can_build_from_entries() { + let map_name = StorageValueName::new("procedure_thresholds").unwrap(); + let map_entry = StorageEntry::new_map(0, MapRepresentation::new_template(map_name.clone())); + + let init_data = InitStorageData::from_toml( + r#" + procedure_thresholds = [ + { key = "0x0000000000000000000000000000000000000000000000000000000000000001", value = "0x0000000000000000000000000000000000000000000000000000000000000010" }, + { key = "0x0000000000000000000000000000000000000000000000000000000000000002", value = "0x0000000000000000000000000000000000000000000000000000000000000020" } + ] + "#, + ) + .unwrap(); + + let entries = init_data.map_entries(&map_name).expect("map entries missing"); + assert_eq!(entries.len(), 2); + assert_eq!( + entries[0], + ( + Word::parse("0x0000000000000000000000000000000000000000000000000000000000000001",) + .unwrap(), + Word::parse("0x0000000000000000000000000000000000000000000000000000000000000010",) + .unwrap(), + ) + ); + + let slots = map_entry.try_build_storage_slots(&init_data).unwrap(); + assert_eq!(slots.len(), 1); + + match &slots[0] { + StorageSlot::Map(storage_map) => { + assert_eq!(storage_map.num_entries(), 2); + let main_key = Word::parse( + "0x0000000000000000000000000000000000000000000000000000000000000001", + ) + .unwrap(); + let main_value_expected = Word::parse( + "0x0000000000000000000000000000000000000000000000000000000000000010", + ) + .unwrap(); + assert_eq!(storage_map.get(&main_key), main_value_expected); + }, + _ => panic!("expected map storage slot"), + } + } + + #[test] + fn map_template_requires_entries() { + let map_name = StorageValueName::new("procedure_thresholds").unwrap(); + let map_entry = StorageEntry::new_map(0, MapRepresentation::new_template(map_name.clone())); + + let result = map_entry.try_build_storage_slots(&InitStorageData::default()); + + assert_matches::assert_matches!( + result, + Err(AccountComponentTemplateError::PlaceholderValueNotProvided(name)) + if name.as_str() == "procedure_thresholds" + ); + + // try with an empty list + + let init_data = InitStorageData::from_toml( + r#" + procedure_thresholds = [] + "#, + ) + .unwrap(); + + let result = map_entry.try_build_storage_slots(&init_data).unwrap(); + + assert_eq!(result.len(), 1); + match &result[0] { + StorageSlot::Map(storage_map) => assert_eq!(storage_map.num_entries(), 0), + _ => panic!("expected map storage slot"), + } + } + + #[test] + fn map_placeholder_requirement_is_reported() { + let targets = [AccountType::RegularAccountImmutableCode].into_iter().collect(); + let map = + MapRepresentation::new_template(StorageValueName::new("procedure_thresholds").unwrap()) + .with_description("Configures procedure thresholds"); + + let metadata = AccountComponentMetadata::new( + "test".into(), + "desc".into(), + Version::new(1, 0, 0), + targets, + vec![StorageEntry::new_map(0, map)], + ) + .unwrap(); + + let requirements = metadata.get_placeholder_requirements(); + let requirement = requirements + .get(&StorageValueName::new("procedure_thresholds").unwrap()) + .expect("map placeholder should be reported"); + + assert_eq!(requirement.r#type.as_str(), "map"); + assert_eq!(requirement.description.as_deref(), Some("Configures procedure thresholds"),); + } + + #[test] + fn toml_template_map_roundtrip() { + let toml_text = r#" + name = "Test Component" + description = "Component with templated map" + version = "1.0.0" + supported-types = ["RegularAccountImmutableCode"] + + [[storage]] + name = "my_map" + description = "Some description" + slot = 0 + type = "map" + "#; + + let metadata = AccountComponentMetadata::from_toml(toml_text).unwrap(); + assert_eq!(metadata.storage_entries().len(), 1); + match metadata.storage_entries().first().unwrap() { + StorageEntry::Map { map, .. } => match map { + MapRepresentation::Template { identifier } => { + assert_eq!(identifier.name.as_str(), "my_map"); + assert_eq!(identifier.description.as_deref(), Some("Some description")); + }, + MapRepresentation::Value { .. } => panic!("expected template map"), + }, + _ => panic!("expected map storage entry"), + } + + let toml_roundtrip = metadata.to_toml().unwrap(); + assert!(toml_roundtrip.contains("type = \"map\"")); + } + + #[test] + fn map_placeholder_populated_via_toml_array() { + let storage_entry = StorageEntry::new_map( + 0, + MapRepresentation::new_template(StorageValueName::new("my_map").unwrap()), + ); + + let init_data = InitStorageData::from_toml( + r#" + my_map = [ + { key = "0x0000000000000000000000000000000000000000000000000000000000000001", value = "0x0000000000000000000000000000000000000000000000000000000000000090" }, + { key = "0x0000000000000000000000000000000000000000000000000000000000000002", value = ["1", "2", "3", "4"] } + ] + other_placeholder = "0xAB" + "#, + ) + .unwrap(); + + assert_eq!( + init_data.get(&StorageValueName::new("other_placeholder").unwrap()).unwrap(), + "0xAB" + ); + + let slots = storage_entry.try_build_storage_slots(&init_data).unwrap(); + assert_eq!(slots.len(), 1); + match &slots[0] { + StorageSlot::Map(storage_map) => { + assert_eq!(storage_map.num_entries(), 2); + let second_value = Word::from([ + Felt::new(1u64), + Felt::new(2u64), + Felt::new(3u64), + Felt::new(4u64), + ]); + let second_key = Word::try_from( + "0x0000000000000000000000000000000000000000000000000000000000000002", + ) + .unwrap(); + assert_eq!(storage_map.get(&second_key), second_value); + }, + _ => panic!("expected map storage slot"), + } + } + + #[test] + fn toml_map_type_with_values_is_invalid() { + let toml_text = r#" + name = "Invalid" + description = "Invalid map" + version = "1.0.0" + supported-types = ["RegularAccountImmutableCode"] + + [[storage]] + name = "bad_map" + slot = 0 + type = "map" + values = [ { key = "0x1", value = "0x2" } ] + "#; + + let metadata = AccountComponentMetadata::from_toml(toml_text).unwrap(); + match metadata.storage_entries().first().unwrap() { + StorageEntry::Map { map, .. } => match map { + MapRepresentation::Value { entries, .. } => { + assert_eq!(entries.len(), 1); + }, + _ => panic!("expected static map"), + }, + _ => panic!("expected map storage entry"), + } + } + + #[test] + fn toml_map_values_with_non_map_type_is_invalid() { + let toml_text = r#" + name = "Invalid" + description = "Invalid map" + version = "1.0.0" + supported-types = ["RegularAccountImmutableCode"] + + [[storage]] + name = "bad_map" + slot = 0 + type = "word" + values = [ { key = "0x1", value = "0x2" } ] + "#; + + let result = AccountComponentMetadata::from_toml(toml_text); + assert_matches::assert_matches!( + result, + Err(AccountComponentTemplateError::TomlDeserializationError(_)) + ); + } + #[test] fn toml_fail_multislot_arity_mismatch() { let toml_text = r#" diff --git a/crates/miden-objects/src/account/component/template/storage/placeholder.rs b/crates/miden-objects/src/account/component/template/storage/placeholder.rs index f6927421f2..6508417ab1 100644 --- a/crates/miden-objects/src/account/component/template/storage/placeholder.rs +++ b/crates/miden-objects/src/account/component/template/storage/placeholder.rs @@ -236,6 +236,11 @@ impl TemplateType { TemplateType::new("word").expect("type is well formed") } + /// Returns the [`TemplateType`] for storage map placeholders. + pub fn storage_map() -> TemplateType { + TemplateType::new("map").expect("type is well formed") + } + /// Returns a reference to the inner string. pub fn as_str(&self) -> &str { &self.0 diff --git a/crates/miden-objects/src/account/component/template/storage/toml.rs b/crates/miden-objects/src/account/component/template/storage/toml.rs index 28cbd6cfe7..f1e1b1a49c 100644 --- a/crates/miden-objects/src/account/component/template/storage/toml.rs +++ b/crates/miden-objects/src/account/component/template/storage/toml.rs @@ -47,7 +47,7 @@ impl AccountComponentMetadata { } /// Serializes the account component template into a TOML string. - pub fn as_toml(&self) -> Result { + pub fn to_toml(&self) -> Result { let toml = toml::to_string(self).map_err(AccountComponentTemplateError::TomlSerializationError)?; Ok(toml) @@ -315,14 +315,25 @@ impl From for RawStorageEntry { ..Default::default() }, }, - StorageEntry::Map { slot, map } => RawStorageEntry { - slot: Some(slot), - identifier: Some(FieldIdentifier { - name: map.name().clone(), - description: map.description().cloned(), - }), - values: Some(StorageValues::MapEntries(map.into())), - ..Default::default() + StorageEntry::Map { slot, map } => match map { + MapRepresentation::Value { identifier, entries } => RawStorageEntry { + slot: Some(slot), + identifier: Some(FieldIdentifier { + name: identifier.name, + description: identifier.description, + }), + values: Some(StorageValues::MapEntries(entries)), + ..Default::default() + }, + MapRepresentation::Template { identifier } => RawStorageEntry { + slot: Some(slot), + identifier: Some(FieldIdentifier { + name: identifier.name, + description: identifier.description, + }), + word_type: Some(TemplateType::storage_map()), + ..Default::default() + }, }, StorageEntry::MultiSlot { slots, word_entries } => match word_entries { MultiWordRepresentation::Value { identifier, values } => RawStorageEntry { @@ -368,11 +379,30 @@ impl<'de> Deserialize<'de> for StorageEntry { raw.identifier.ok_or_else(|| missing_field_for("identifier", "map entry"))?; let name = identifier.name; let slot = raw.slot.ok_or_else(|| missing_field_for("slot", "map entry"))?; - let mut map = MapRepresentation::new(map_entries, name); + if let Some(word_type) = raw.word_type.clone() + && word_type != TemplateType::storage_map() + { + return Err(D::Error::custom( + "map storage entries with `values` must have `type = \"map\"`", + )); + } + let mut map = MapRepresentation::new_value(map_entries, name); if let Some(desc) = identifier.description { map = map.with_description(desc); } Ok(StorageEntry::Map { slot, map }) + } else if let Some(word_type) = raw.word_type.clone() + && word_type == TemplateType::storage_map() + { + let identifier = + raw.identifier.ok_or_else(|| missing_field_for("identifier", "map entry"))?; + let slot = raw.slot.ok_or_else(|| missing_field_for("slot", "map entry"))?; + let FieldIdentifier { name, description } = identifier; + let mut map = MapRepresentation::new_template(name); + if let Some(desc) = description { + map = map.with_description(desc); + } + Ok(StorageEntry::Map { slot, map }) } else if let Some(StorageValues::Words(values)) = raw.values { let identifier = raw .identifier @@ -425,10 +455,17 @@ impl InitStorageData { /// - If the TOML string includes arrays pub fn from_toml(toml_str: &str) -> Result { let value: toml::Value = toml::from_str(toml_str)?; - let mut placeholders = BTreeMap::new(); + let mut value_entries = BTreeMap::new(); + let mut map_entries = BTreeMap::new(); // Start with an empty prefix (i.e. the default, which is an empty string) - Self::flatten_parse_toml_value(StorageValueName::empty(), &value, &mut placeholders)?; - Ok(InitStorageData::new(placeholders)) + Self::flatten_parse_toml_value( + StorageValueName::empty(), + value, + &mut value_entries, + &mut map_entries, + )?; + + Ok(InitStorageData::new(value_entries, map_entries)) } /// Recursively flattens a TOML `Value` into a flat mapping. @@ -438,8 +475,9 @@ impl InitStorageData { /// an error is returned. Arrays are not supported. fn flatten_parse_toml_value( prefix: StorageValueName, - value: &toml::Value, - map: &mut BTreeMap, + value: toml::Value, + value_entries: &mut BTreeMap, + map_entries: &mut BTreeMap>, ) -> Result<(), InitStorageDataError> { match value { toml::Value::Table(table) => { @@ -452,19 +490,35 @@ impl InitStorageData { let new_key = StorageValueName::new(key.to_string()) .map_err(InitStorageDataError::InvalidStorageValueName)?; let new_prefix = prefix.clone().with_suffix(&new_key); - Self::flatten_parse_toml_value(new_prefix, val, map)?; + Self::flatten_parse_toml_value(new_prefix, val, value_entries, map_entries)?; + } + }, + toml::Value::Array(items) if items.is_empty() => { + if prefix.as_str().is_empty() { + return Err(InitStorageDataError::ArraysNotSupported); } + map_entries.insert(prefix, Vec::new()); }, - toml::Value::Array(_) => { - return Err(InitStorageDataError::ArraysNotSupported); + toml::Value::Array(items) => { + if prefix.as_str().is_empty() + || !items.iter().all(|item| matches!(item, toml::Value::Table(_))) + { + return Err(InitStorageDataError::ArraysNotSupported); + } + + let entries = items + .into_iter() + .map(parse_map_entry_value) + .collect::, _>>()?; + map_entries.insert(prefix, entries); }, toml_value => { // Get the string value, or convert to string if it's some other type let value = match toml_value { toml::Value::String(s) => s.clone(), - _ => value.to_string(), + _ => toml_value.to_string(), }; - map.insert(prefix, value); + value_entries.insert(prefix, value); }, } Ok(()) @@ -484,6 +538,9 @@ pub enum InitStorageDataError { #[error("invalid storage value name")] InvalidStorageValueName(#[source] StorageValueNameError), + + #[error("invalid map entry: {0}")] + InvalidMapEntry(String), } impl Serialize for FieldIdentifier { @@ -576,6 +633,34 @@ fn parse_field_identifier( }) } +/// Parses a `{ key, value }` TOML table into a `(Word, Word)` pair, rejecting templates. +fn parse_map_entry_value(item: toml::Value) -> Result<(Word, Word), InitStorageDataError> { + // Try to deserialize the user input as a map entry + let entry: MapEntry = MapEntry::deserialize(item) + .map_err(|err| InitStorageDataError::InvalidMapEntry(err.to_string()))?; + + // Make sure the entry does not contain templates, only static + if entry.key().template_requirements(StorageValueName::empty()).next().is_some() + || entry.value().template_requirements(StorageValueName::empty()).next().is_some() + { + return Err(InitStorageDataError::InvalidMapEntry( + "map entries cannot contain templates".into(), + )); + } + + // Interpret the user input as static words + let key = entry + .key() + .try_build_word(&InitStorageData::default(), StorageValueName::empty()) + .map_err(|err| InitStorageDataError::InvalidMapEntry(err.to_string()))?; + let value = entry + .value() + .try_build_word(&InitStorageData::default(), StorageValueName::empty()) + .map_err(|err| InitStorageDataError::InvalidMapEntry(err.to_string()))?; + + Ok((key, value)) +} + // TESTS // ================================================================================================ @@ -644,6 +729,30 @@ mod tests { ); } + #[test] + fn parse_map_entries_from_array() { + let toml_str = r#" + my_map = [ + { key = "0x0000000000000000000000000000000000000000000000000000000000000001", value = "0x0000000000000000000000000000000000000000000000000000000000000010" }, + { key = "0x0000000000000000000000000000000000000000000000000000000000000002", value = ["1", "2", "3", "4"] } + ] + "#; + + let storage = InitStorageData::from_toml(toml_str).expect("Failed to parse map entries"); + let map_name = StorageValueName::new("my_map").unwrap(); + let entries = storage.map_entries(&map_name).expect("map entries missing"); + assert_eq!(entries.len(), 2); + + let first_key = + Word::try_from("0x0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + assert_eq!(entries[0].0, first_key); + + let second_value = + Word::from([Felt::new(1u64), Felt::new(2u64), Felt::new(3u64), Felt::new(4u64)]); + assert_eq!(entries[1].1, second_value); + } + #[test] fn error_on_empty_subtable() { let toml_str = r#" diff --git a/docs/src/account/components.md b/docs/src/account/components.md index 297c0ff310..b304910423 100644 --- a/docs/src/account/components.md +++ b/docs/src/account/components.md @@ -63,12 +63,24 @@ name = "map_storage_entry" slot = 2 values = [ { key = "0x1", value = ["0x0", "249381274", "998123581", "124991023478"] }, - { key = "0xDE0B1140012A9FD912F18AD9EC85E40F4CB697AE", value = { name = "value_placeholder", description = "This value will be defined at the moment of instantiation" } } + { + key = "0xDE0B1140012A9FD912F18AD9EC85E40F4CB697AE", + value = { + name = "value_placeholder", + description = "This value will be defined at the moment of instantiation" + } + } ] +[[storage]] +name = "procedure_thresholds" +description = "Map which stores procedure thresholds (PROC_ROOT -> signature threshold)" +slot = 3 +type = "map" + [[storage]] name = "multislot_entry" -slots = [3,4] +slots = [4,5] values = [ ["0x1","0x2","0x3","0x4"], ["50000","60000","70000","80000"] @@ -137,11 +149,16 @@ In the above example, the first and second storage entries are single-slot value Storage map entries can specify the following fields: - `slot`: Specifies the slot index in which the root of the map will be placed -- `values`: Contains a list of map entries, defined by a `key` and `value` - -Where keys and values are word values, which can be defined as placeholders. +- `values` (optional): Contains a list of map entries, defined by a `key` and `value`. Each entry is + interpreted as a word, and keys or values may themselves be expressed via placeholders. +- `type = "map"` (optional): When provided without `values`, the entry is treated as a templated map + whose contents must be provided at instantiation time through [`InitStorageData`](#initializing-placeholder-values). + If `values` are present, the entry is interpreted as a static map regardless of the `type` field, so + specifying `type = "map"` becomes purely descriptive in that case. -In the example, the third storage entry defines a storage map. +In the example, the third storage entry defines a static storage map with two initial entries, while +the fourth entry (`procedure_thresholds`) is a templated map whose contents are supplied at +instantiation time. ##### Multi-slot value @@ -152,4 +169,26 @@ For multi-slot values, the following fields are expected: - `slots`: Specifies the list of contiguous slots that the value comprises - `values`: Contains the initial storage value for the specified slots -Placeholders can currently not be defined for multi-slot values. In our example, the fourth entry defines a two-slot value. +Placeholders can currently not be defined for multi-slot values. In our example, the fifth entry defines a two-slot value. + +#### Initializing placeholder values + +When a storage entry introduces placeholders, an implementation must provide their concrete values +at instantiation time. This is done through `InitStorageData` (available as `miden_objects::account::InitStorageData`), which can be created programmatically or loaded from TOML using `InitStorageData::from_toml()`. + +For example, the templated map entry above can be populated from TOML as follows: + +```toml +procedure_thresholds = [ + { + key = "0xd2d1b6229d7cfb9f2ada31c5cb61453cf464f91828e124437c708eec55b9cd07", + value = "0x00000000000000000000000000000000000000000000000000000000000001" + }, + { + key = "0x2217cd9963f742fc2d131d86df08f8a2766ed17b73f1519b8d3143ad1c71d32d", + value = ["0", "0", "0", "2"] + } +] +``` + +Each element in the array is a fully specified key/value pair. Keys and values can be written either as hexadecimal words or as an array of four field elements (decimal or hexadecimal strings). This syntax complements the existing `values = [...]` form used for static maps, and mirrors how map entries are provided in component metadata. From 10e34703e34744796f37a424c8cfb76a7bd14d1f Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Wed, 5 Nov 2025 02:09:25 -0800 Subject: [PATCH 133/133] chore: update changelog --- CHANGELOG.md | 58 ++++++++++++++++++++++++++-------------------------- README.md | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0edc734060..6e3060ba83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,41 +1,41 @@ # Changelog -## 0.12.0 (TBD) +## 0.12.0 (11-05-2025) ### Features -- Removed `create_p2id_note` and `create_p2any_note` methods from `MockChainBuilder`, users should use `add_p2id_note` and `add_p2any_note` instead ([#1990](https://github.com/0xMiden/miden-base/issues/1990)). - Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). +- Added `update_signers_and_threshold` procedure to update owner public keys and threshold config in multisig authentication component ([#1707](https://github.com/0xMiden/miden-base/issues/1707)). - Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). -- [BREAKING] Enabled lazy loading of storage map entries during transaction execution ([#1857](https://github.com/0xMiden/miden-base/pull/1857)). +- Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). - Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). - Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). - Enabled lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). -- [BREAKING] Enabled lazy loading of foreign accounts during transaction execution ([#1873](https://github.com/0xMiden/miden-base/pull/1873)). - Added lazy loading of the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). -- Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). +- [BREAKING] Enabled lazy loading of storage map entries during transaction execution ([#1857](https://github.com/0xMiden/miden-base/pull/1857)). +- [BREAKING] Enabled lazy loading of foreign accounts during transaction execution ([#1873](https://github.com/0xMiden/miden-base/pull/1873)). - [BREAKING] Move account seed into `PartialAccount` ([#1875](https://github.com/0xMiden/miden-base/pull/1875), [#2003](https://github.com/0xMiden/miden-base/pull/2003)). +- Added `get_initial_item` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). +- Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)). - [BREAKING] Enabled lazy loading of assets and storage map items for foreign accounts during transaction execution ([#1888](https://github.com/0xMiden/miden-base/pull/1888)). -- Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)) - [BREAKING] Represent new accounts as account deltas ([#1896](https://github.com/0xMiden/miden-base/pull/1896)). - Implement `SlotName` for named storage slots ([#1932](https://github.com/0xMiden/miden-base/issues/1932)) -- Added `get_initial_item` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). -- Added `update_signers_and_threshold` procedure to update owner public keys and threshold config in multisig authentication component ([#1707](https://github.com/0xMiden/miden-base/issues/1707)). - [BREAKING] Removed `get_falcon_signature` from `miden-tx` crate ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - Created a `Signature` wrapper to simplify the preparation of "native" signatures for use in the VM ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). - - Added per-procedure approval thresholds to `AuthRpoFalcon512Multisig` auth component ([#1968](https://github.com/0xMiden/miden-base/pull/1968)). +- Added per-procedure approval thresholds to `AuthRpoFalcon512Multisig` auth component ([#1968](https://github.com/0xMiden/miden-base/pull/1968)). - Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933](https://github.com/0xMiden/miden-base/pull/1933)). - Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937)). - Added `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935](https://github.com/0xMiden/miden-base/pull/1935)). - Added `AssetVault::{num_assets, num_leaves, inner_nodes}` ([#1939](https://github.com/0xMiden/miden-base/pull/1939)). -- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973](https://github.com/0xMiden/miden-base/pull/1973)). +- [BREAKING] Enabled computing the transaction ID from the data in a `TransactionHeader` ([#1973](https://github.com/0xMiden/miden-base/pull/1973)). - Added `account::get_initial_balance` procedure to `miden` lib ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). -- [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963)). +- [BREAKING] Changed `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963)). - Added `MastArtifact`, `PackageExport`, `PackageManifest`, `AttributeSet`, `QualifiedProcedureName`, `Section` and `SectionId` to re-export section ([#1984](https://github.com/0xMiden/miden-base/pull/1984) and [#2015](https://github.com/0xMiden/miden-base/pull/2015)). -- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973](https://github.com/0xMiden/miden-base/pull/1973). +- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973](https://github.com/0xMiden/miden-base/pull/1973)). - [BREAKING] Introduce `AssetVaultKey` newtype wrapper for asset vault keys ([#1978](https://github.com/0xMiden/miden-base/pull/1978), [#2024](https://github.com/0xMiden/miden-base/pull/2024)). -- [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963). +- [BREAKING] Change `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963)). - Added `network_fungible_faucet` and `MINT` & `BURN` notes ([#1925](https://github.com/0xMiden/miden-base/pull/1925)) +- Removed `create_p2id_note` and `create_p2any_note` methods from `MockChainBuilder`, users should use `add_p2id_note` and `add_p2any_note` instead ([#1990](https://github.com/0xMiden/miden-base/issues/1990)). - [BREAKING] Introduced `AuthScheme` and `PublicKey` enums in `miden-objects::account::auth` module ([#1994](https://github.com/0xMiden/miden-base/pull/1994)). - [BREAKING] Added `get_note_script()` method to `DataStore` trait to enable lazy loading of note scripts during transaction execution ([#1995](https://github.com/0xMiden/miden-base/pull/1995)). - Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). @@ -54,7 +54,7 @@ ### Changes -- [BREAKING] Incremented MSRV to 1.89. +- [BREAKING] Incremented MSRV to 1.90. - [BREAKING] Migrated to `miden-vm` v0.18 and `miden-crypto` v0.17 ([#1832](https://github.com/0xMiden/miden-base/pull/1832)). - [BREAKING] Removed `MockChain::add_pending_p2id_note` in favor of using `MockChainBuilder` ([#1842](https://github.com/0xMiden/miden-base/pull/#1842)). - [BREAKING] Removed versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). @@ -64,35 +64,35 @@ - [BREAKING] Removed some of the `note` kernel procedures and use `input_note` procedures instead ([#1834](https://github.com/0xMiden/miden-base/pull/1834)). - [BREAKING] Replaced `Account` with `PartialAccount` in `TransactionInputs` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). - [BREAKING] Renamed `Account::init_commitment` to `Account::initial_commitment` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). -- [BREAKING] Rename the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). -- [BREAKING] Move `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). -- Remove `ProvenTransactionExt`([#1867](https://github.com/0xMiden/miden-base/pull/1867)). +- [BREAKING] Renamed the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). +- [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). +- Removed `ProvenTransactionExt`([#1867](https://github.com/0xMiden/miden-base/pull/1867)). - [BREAKING] Renamed the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). - [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). - [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). -- [BREAKING] Move `TransactionKernelError` to miden-tx ([#1859](https://github.com/0xMiden/miden-base/pull/1859)). +- [BREAKING] Moved `TransactionKernelError` to miden-tx ([#1859](https://github.com/0xMiden/miden-base/pull/1859)). - [BREAKING] Changed `PartialStorageMap` to track the correct set of key+value pairings ([#1878](https://github.com/0xMiden/miden-base/pull/1878), [#1921](https://github.com/0xMiden/miden-base/pull/1921)). - Change terminology of "current note" to "active note" ([#1863](https://github.com/0xMiden/miden-base/issues/1863)). -- [BREAKING] Move and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). +- [BREAKING] Moved and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). - Merge `bench-prover` into `bench-tx` crate ([#1894](https://github.com/0xMiden/miden-base/pull/1894)). - Replace `eqw` usages with `exec.word::test_eq` and `exec.word::eq`, remove `is_key_greater` and `is_key_less` from `link_map` module ([#1897](https://github.com/0xMiden/miden-base/pull/1897)). - [BREAKING] Make AssetVault and PartialVault APIs more type safe ([#1916](https://github.com/0xMiden/miden-base/pull/1916)). - [BREAKING] Remove `MockChain::add_pending_note` to simplify mock chain internals ([#1903](https://github.com/0xMiden/miden-base/pull/1903)). -- [BREAKING] Move active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). -- [BREAKING] Remove account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). -- [BREAKING] Rename `TransactionInputs` to `TransactionExecutionInputs` and make a new `TransactionInputs` struct which does not contain `InputNotes` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). -- [BREAKING] Refactor `TransactionInputs` and remove `TransactionWitness` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). -- Simplify `MockChain` internals and rework its documentation ([#1942](https://github.com/0xMiden/miden-base/pull/1942). -- [BREAKING] Change the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). -- [BREAKING] Rename `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). +- [BREAKING] Moved active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). +- [BREAKING] Removed account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). +- [BREAKING] Renamed `TransactionInputs` to `TransactionExecutionInputs` and make a new `TransactionInputs` struct which does not contain `InputNotes` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). +- [BREAKING] Refactored `TransactionInputs` and remove `TransactionWitness` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). +- Simplify `MockChain` internals and rework its documentation ([#1942](https://github.com/0xMiden/miden-base/pull/1942)). +- [BREAKING] Changed the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). +- [BREAKING] Renamed `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). - Dynamically lookup all masm `EventId`s from source ([#1954](https://github.com/0xMiden/miden-base/pull/1954)). - [BREAKING] Return `ExecutionOutput` from `TransactionContext::execute_code` ([#1955](https://github.com/0xMiden/miden-base/pull/1955)). -- [BREAKING] Rename `get_item_init` and `get_map_item_init` to `get_initial_item` and `get_initial_map_item` respectively ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). +- [BREAKING] Renamed `get_item_init` and `get_map_item_init` to `get_initial_item` and `get_initial_map_item` respectively ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). - Update the type signature syntax in the `account_components` module ([#1971](https://github.com/0xMiden/miden-base/pull/1971)). - [BREAKING] Assert nonce is non-zero after the auth procedure ([#1982](https://github.com/0xMiden/miden-base/pull/1982)). - [BREAKING] Removed `Rng` from `BasicAuthenticator` ([#1994](https://github.com/0xMiden/miden-base/pull/1994)). -- [BREAKING] Change the outputs of the `output_note::add_asset` procedure: now the values that are the same as the passed parameters are dropped ([#2031](https://github.com/0xMiden/miden-base/pull/2031)). -- [BREAKING] Upgrade VM to 0.19 ([#2042](https://github.com/0xMiden/miden-base/pull/2042)). +- [BREAKING] Changed the outputs of the `output_note::add_asset` procedure: now the values that are the same as the passed parameters are dropped ([#2031](https://github.com/0xMiden/miden-base/pull/2031)). +- [BREAKING] Upgraded VM to 0.19 ([#2042](https://github.com/0xMiden/miden-base/pull/2042)). ## 0.11.5 (2025-10-02) diff --git a/README.md b/README.md index 60ccca9ae9..394bf72716 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/0xMiden/miden-base/blob/main/LICENSE) [![test](https://github.com/0xMiden/miden-base/actions/workflows/test.yml/badge.svg)](https://github.com/0xMiden/miden-base/actions/workflows/test.yml) [![build](https://github.com/0xMiden/miden-base/actions/workflows/build.yml/badge.svg)](https://github.com/0xMiden/miden-base/actions/workflows/build.yml) -[![RUST_VERSION](https://img.shields.io/badge/rustc-1.89+-lightgray.svg)](https://www.rust-lang.org/tools/install) +[![RUST_VERSION](https://img.shields.io/badge/rustc-1.90+-lightgray.svg)](https://www.rust-lang.org/tools/install) [![GitHub Release](https://img.shields.io/github/release/0xMiden/miden-base)](https://github.com/0xMiden/miden-base/releases/) Description and core structures for the Miden Rollup protocol.