diff --git a/docs/builder/migration/01-imports-dependencies.md b/docs/builder/migration/01-imports-dependencies.md index c1a11e73..3ed263a3 100644 --- a/docs/builder/migration/01-imports-dependencies.md +++ b/docs/builder/migration/01-imports-dependencies.md @@ -1,72 +1,117 @@ --- sidebar_position: 1 title: "Imports & Dependencies" -description: "Crate version bumps, MSRV changes, and the non-round-tripping 0.14 artifacts in v0.15" +description: "Crate version bumps, the VM 0.23 to 0.29 jump, MSRV 1.96, and the artifacts that must be regenerated for v0.16" --- # Imports & Dependencies :::warning Breaking Change -Miden VM dependencies move from 0.22 to 0.23 and `miden-crypto` from 0.23 to 0.25. The protocol crates move from 0.14 to **0.15.3** and `miden-client` from 0.14 to 0.15, and the client's MSRV is now Rust **1.93**. Because the native hash and the MAST/serialization formats changed, **0.14 artifacts (accounts, notes, proofs, serialized stores, `.masl`/`.masp` packages) do not round-trip.** +The protocol crates move from 0.15.3 to **0.16.0**, `miden-client` from 0.15 to **0.16.0**, and the VM crates jump **0.23 → 0.29.1** — six minor versions, not one. `miden-crypto` was absorbed into the Miden VM workspace and now shares its version number (0.25 → **0.29.1**). The MSRV is Rust **1.96**. Because the MAST wire format, the package format, and several commitment preimages changed, **0.15 artifacts do not round-trip**: re-assemble every package from source, recreate your local store, and upgrade your node in lockstep with your client. ::: ## Quick Fix ```toml title="Cargo.toml" # Replace these -miden-client = "0.14" -miden-client-sqlite-store = "0.14" -miden-protocol = "0.14" -miden-standards = "0.14" -miden-tx = "0.14" -miden-assembly = "0.22" -miden-core = "0.22" -miden-core-lib = "0.22" -miden-processor = "0.22" -miden-prover = "0.22" -miden-crypto = "0.23" - -# With these miden-client = "0.15" miden-client-sqlite-store = "0.15" miden-protocol = "0.15.3" miden-standards = "0.15.3" miden-tx = "0.15.3" +miden-tx-batch-prover = "0.15.3" miden-assembly = "0.23" miden-core = "0.23" miden-core-lib = "0.23" miden-processor = "0.23" miden-prover = "0.23" miden-crypto = "0.25" + +# With these +miden-client = "0.16.0-rc.1" +miden-client-sqlite-store = "0.16.0-rc.1" +miden-protocol = "0.16.0-rc.6" +miden-standards = "0.16.0-rc.6" +miden-tx = "0.16.0-rc.6" +miden-tx-batch = "0.16.0-rc.6" # renamed from miden-tx-batch-prover +miden-assembly = "0.29.1" +miden-core = "0.29.1" +miden-core-lib = "0.29.1" +miden-processor = "0.29.1" +miden-prover = "0.29.1" +miden-crypto = "0.29.1" +``` + +```json title="package.json (Web SDK)" +{ + "@miden-sdk/miden-sdk": "0.16.0-rc.2", + "@miden-sdk/react": "0.16.0-rc.2" +} +``` + +Then run: + +```bash +cargo update && cargo build ``` +If you encounter errors, continue reading for detailed migration steps. + +:::note Pin the exact version +The 0.16 protocol and client crates currently publish as `0.16.0-rc.N` pre-releases. Cargo does **not** match a pre-release against a plain requirement, so `miden-protocol = "0.16"` will fail to resolve. Pin the exact string as shown above until the final release is published. +::: + +:::warning 0.15 artifacts do not round-trip +The MAST wire format moved `0.0.3` → `0.0.4` and the package format `4.0.0` → `6.0.0`, so **serialized packages and `MastForest` blobs from 0.15 will not load**. The `.masl` library format no longer exists at all. Several commitment preimages also changed (ECDSA public keys, MMR peaks, empty domain-separated hashes), so derived values must be recomputed. Re-assemble from source and re-sync into a fresh store. +::: + --- ## Summary -Every Miden crate moves up a minor: the protocol crates (`miden-protocol`, `miden-standards`, `miden-tx`, `miden-testing`) go `0.14` → `0.15.3`, the VM crates (`miden-assembly`, `miden-core`, `miden-core-lib`, `miden-processor`, `miden-prover`) go `0.22` → `0.23`, and `miden-crypto` goes `0.23` → `0.25`. `miden-client` and `miden-client-sqlite-store` go `0.14` → `0.15`; the Web SDK packages go `0.14` → `0.15`. The client's MSRV is Rust **1.93** (the base crates build on 1.90+). +Every layer of the stack moves: -> The prover crate is **`miden-prover`** in this line — it is *not* `miden-prove`. Keep depending on `miden-prover`. +- The **protocol crates** (`miden-protocol`, `miden-standards`, `miden-tx`, `miden-testing`) go `0.15.3` → `0.16.0`. +- The **VM crates** (`miden-assembly`, `miden-core`, `miden-core-lib`, `miden-processor`, `miden-prover`, `miden-mast-package`) go `0.23` → `0.29.1`. This is a much larger jump than previous releases and carries breaking MASM language changes — see [VM & Assembler Changes](./vm-assembler). +- **`miden-crypto`** goes `0.25` → `0.29.1`. It is no longer an independent crate line: it was imported into the Miden VM workspace and now shares the VM version number. +- **`miden-client`** and `miden-client-sqlite-store` go `0.15` → `0.16.0`. +- The **Web SDK** packages go `0.15` → `0.16.0`. -Because the native hash and the MAST/serialization formats changed upstream, **0.14 artifacts (accounts, notes, proofs, serialized stores, `.masl`/`.masp` packages) do not round-trip.** Re-assemble from source and re-sync into a fresh store. +Two crates changed identity: `miden-tx-batch-prover` is now **`miden-tx-batch`**, and a new **`miden-protocol-build-utils`** crate provides MASM assembly helpers. On the VM side the core package was split, adding a **`miden-precompiles`** package alongside `miden-core`. --- ## Version Bumps -| Crate | v0.14 | v0.15 | +| Crate | v0.15 | v0.16 | |-------|-------|-------| -| `miden-client` | 0.14 | 0.15 | -| `miden-client-sqlite-store` | 0.14 | 0.15 | -| `miden-protocol` | 0.14 | 0.15.3 | -| `miden-standards` | 0.14 | 0.15.3 | -| `miden-tx` | 0.14 | 0.15.3 | -| `miden-assembly` | 0.22 | 0.23 | -| `miden-core` | 0.22 | 0.23 | -| `miden-core-lib` | 0.22 | 0.23 | -| `miden-processor` | 0.22 | 0.23 | -| `miden-prover` | 0.22 | 0.23 | -| `miden-crypto` | 0.23 | 0.25 | +| `miden-client` | 0.15 | 0.16.0 | +| `miden-client-sqlite-store` | 0.15 | 0.16.0 | +| `miden-protocol` | 0.15.3 | 0.16.0 | +| `miden-standards` | 0.15.3 | 0.16.0 | +| `miden-tx` | 0.15.3 | 0.16.0 | +| `miden-testing` | 0.15.3 | 0.16.0 | +| `miden-tx-batch-prover` | 0.15.3 | **renamed** to `miden-tx-batch` 0.16.0 | +| `miden-protocol-build-utils` | — | 0.16.0 *(new)* | +| `miden-assembly` | 0.23 | 0.29.1 | +| `miden-core` | 0.23 | 0.29.1 | +| `miden-core-lib` | 0.23 | 0.29.1 | +| `miden-processor` | 0.23 | 0.29.1 | +| `miden-prover` | 0.23 | 0.29.1 | +| `miden-verifier` | 0.23 | 0.29.1 | +| `miden-mast-package` | 0.23 | 0.29.1 | +| `miden-precompiles` | — | 0.29.1 *(new)* | +| `miden-crypto` | 0.25 | 0.29.1 | + +| npm package | v0.15 | v0.16 | +|-------------|-------|-------| +| `@miden-sdk/miden-sdk` | 0.15.x | 0.16.0 | +| `@miden-sdk/react` | 0.15.x | 0.16.0 | +| `@miden-sdk/vite-plugin` | — | 0.16.0 | + +:::note `miden-idxdb-store` is not a package +Earlier guidance listed a `miden-idxdb-store` npm dependency. No such package exists on the public registry — the IndexedDB store ships inside `@miden-sdk/miden-sdk`. Remove it from your `package.json` if you carried it over. +::: --- @@ -74,60 +119,69 @@ Because the native hash and the MAST/serialization formats changed upstream, **0 **Cargo.toml:** ```diff -- miden-client = "0.14" -- miden-client-sqlite-store = "0.14" -- miden-protocol = "0.14" -- miden-standards = "0.14" -- miden-tx = "0.14" -- miden-assembly = "0.22" -- miden-core = "0.22" -- miden-core-lib = "0.22" -- miden-processor = "0.22" -- miden-prover = "0.22" -- miden-crypto = "0.23" -+ miden-client = "0.15" -+ miden-client-sqlite-store = "0.15" -+ miden-protocol = "0.15.3" -+ miden-standards = "0.15.3" -+ miden-tx = "0.15.3" -+ miden-assembly = "0.23" -+ miden-core = "0.23" -+ miden-core-lib = "0.23" -+ miden-processor = "0.23" -+ miden-prover = "0.23" -+ miden-crypto = "0.25" +- miden-client = "0.15" +- miden-client-sqlite-store = "0.15" +- miden-protocol = "0.15.3" +- miden-standards = "0.15.3" +- miden-tx = "0.15.3" +- miden-tx-batch-prover = "0.15.3" +- miden-assembly = "0.23" +- miden-core = "0.23" +- miden-core-lib = "0.23" +- miden-processor = "0.23" +- miden-prover = "0.23" +- miden-crypto = "0.25" ++ miden-client = "0.16.0-rc.1" ++ miden-client-sqlite-store = "0.16.0-rc.1" ++ miden-protocol = "0.16.0-rc.6" ++ miden-standards = "0.16.0-rc.6" ++ miden-tx = "0.16.0-rc.6" ++ miden-tx-batch = "0.16.0-rc.6" ++ miden-assembly = "0.29.1" ++ miden-core = "0.29.1" ++ miden-core-lib = "0.29.1" ++ miden-processor = "0.29.1" ++ miden-prover = "0.29.1" ++ miden-crypto = "0.29.1" ``` **package.json (Web SDK):** ```diff -- "@miden-sdk/miden-sdk": "^0.14.0", -- "@miden-sdk/react": "^0.14.0", -- "miden-idxdb-store": "^0.14.0" -+ "@miden-sdk/miden-sdk": "^0.15.0", -+ "@miden-sdk/react": "^0.15.0", -+ "miden-idxdb-store": "^0.15.0" +- "@miden-sdk/miden-sdk": "^0.15.0", +- "@miden-sdk/react": "^0.15.0", +- "miden-idxdb-store": "^0.15.0" ++ "@miden-sdk/miden-sdk": "0.16.0-rc.2", ++ "@miden-sdk/react": "0.16.0-rc.2" ``` --- ## MSRV (Minimum Supported Rust Version) -If you depend on `miden-client`, update your `rust-toolchain.toml` to Rust **1.93** (the base crates build on 1.90+): +The MSRV rose across the board. Update your `rust-toolchain.toml` to Rust **1.96**: ```toml title="rust-toolchain.toml" [toolchain] -channel = "1.93" +channel = "1.96" ``` +| Component | v0.15 | v0.16 | +|-----------|-------|-------| +| protocol crates | 1.90 | 1.96.1 | +| `miden-client` | 1.93 | 1.96 | +| Miden VM | 1.90 | 1.96 | + --- ## Migration Steps -1. Bump every Miden crate per the table above and run `cargo update` to pull the matching `miden-crypto 0.25` and VM `0.23` minors. -2. Do **not** rename `miden-prover` to `miden-prove`. -3. Set the client toolchain to at least Rust `1.93`. -4. Bump `@miden-sdk/miden-sdk`, `@miden-sdk/react`, and `miden-idxdb-store` to `^0.15.0` together — a mix of 0.14/0.15 packages will not link against the shared WASM ABI. -5. Point the client at a `0.15` node (the protocol version is negotiated at connect; a mismatch is rejected), and re-sync into a fresh store. +1. Bump every Miden crate per the table above, pinning the exact `0.16.0-rc.N` strings for the protocol and client crates, and run `cargo update`. +2. Rename the `miden-tx-batch-prover` dependency to **`miden-tx-batch`** if you used it. +3. Set your toolchain to at least Rust `1.96`. +4. Bump `@miden-sdk/miden-sdk` and `@miden-sdk/react` together — mixing 0.15 and 0.16 packages will not link against the shared WASM ABI. Drop any `miden-idxdb-store` dependency. +5. Re-assemble every `.masp` package from source under the new toolchain, and delete cached `MastForest` blobs. The `.masl` format is gone entirely. +6. **Recreate your local store.** The SQLite store's schema fingerprint changed and existing databases are rejected; browser users have their IndexedDB store cleared automatically on the version bump. See [Client Changes](./client-changes). +7. **Upgrade your node together with your client.** 0.16 clients seal transaction inputs before submission; a 0.16 node rejects plaintext submissions and an older node rejects sealed ones, so the two cannot be mixed. --- @@ -135,6 +189,10 @@ channel = "1.93" | Error Message | Cause | Solution | | --- | --- | --- | -| `failed to select a version for miden-prove` | Crate not renamed in 0.15 | Keep depending on `miden-prover`. | -| `MastForest deserialization failed: unexpected version` | MAST wire format bumped to `0.0.3` | Re-assemble every `.masl`/`.masp` from source under `0.23`. | -| node version negotiation failure | `0.15` client against a `0.14` node | Upgrade the node to `0.15`. | +| `failed to select a version for miden-protocol` | A plain `"0.16"` requirement will not match a `0.16.0-rc.N` pre-release | Pin the exact version string, e.g. `"0.16.0-rc.6"`. | +| `failed to select a version for miden-tx-batch-prover` | Crate renamed in 0.16 | Depend on `miden-tx-batch` instead. | +| `MastForest deserialization failed: unexpected version` | MAST wire format moved to `0.0.4` | Re-assemble every package from source under VM 0.29.1. | +| package fails to load with a version mismatch | Package format moved to `6.0.0` | Rebuild the `.masp`; `.masl` is no longer supported at all. | +| `Migration error: Attempt to migrate a database with a migration number that is too high` | Existing SQLite store predates the 0.16 schema | Delete and recreate the store, then re-sync. | +| Node rejects a submitted transaction | Client and node versions are mixed | Upgrade both to 0.16; sealed and plaintext submissions are mutually incompatible. | +| `rustc` version error during build | MSRV raised to 1.96 | Update `rust-toolchain.toml`. | diff --git a/docs/builder/migration/02-hashing-crypto.md b/docs/builder/migration/02-hashing-crypto.md new file mode 100644 index 00000000..7b3c9330 --- /dev/null +++ b/docs/builder/migration/02-hashing-crypto.md @@ -0,0 +1,135 @@ +--- +sidebar_position: 2 +title: "Hashing & Crypto Changes" +description: "Commitment preimages that changed in v0.16 — ECDSA public keys, MMR peaks, and domain-separated empty inputs" +--- + +# Hashing & Crypto Changes + +:::warning Breaking Change +Four commitment preimages changed. Any value you have **persisted** — in account storage, note storage, an advice map key, or your own database — that was derived from an ECDSA public key, an MMR peak set, a domain-separated empty input, or `hash_bytes(&[])` is now wrong and must be recomputed. These changes are silent: nothing fails to compile, and the old values simply no longer match. +::: + +## Quick Fix + +```rust +// Recompute every stored ECDSA public-key commitment +use miden_crypto::dsa::ecdsa_k256_keccak::PublicKey; +let commitment: Word = public_key.to_commitment(); +``` + +Then re-derive anything downstream: account storage slots, note storage, and advice-map keys built from those commitments. + +If you encounter errors, continue reading for detailed migration steps. + +--- + +## Summary + +Unlike most of this release, nothing here breaks your build. These are value changes, so the symptom is a proof that fails to verify, an account whose storage no longer matches, or an advice-map lookup that misses — all at runtime, all without a compiler error pointing at the cause. + +The rule of thumb: if you stored a hash, recompute it. If you only ever compute hashes on the fly from current inputs, you are unaffected. + +--- + +## ECDSA k256 public-key commitment format changed + +### Summary + +The ECDSA-k256/Keccak public-key commitment now hashes the **native affine coordinate limbs** (`qx || qy` as little-endian `u32` limbs) instead of the compressed SEC1 public-key bytes. Compressed SEC1 *serialization* of the key itself is unchanged — only the commitment value changed ([#3342](https://github.com/0xMiden/miden-vm/pull/3342), [crypto#1075](https://github.com/0xMiden/crypto/issues/1075)). + +### Affected Code + +```text +v0.15: PK_COMM = Poseidon2::hash_elements( 33 compressed SEC1 bytes packed as 9 felts ) +v0.16: PK_COMM = Poseidon2::hash_elements( QX[8] || QY[8] ) # native LE u32 limbs +``` + +```rust +// After (0.16) — regenerate every stored commitment +use miden_crypto::dsa::ecdsa_k256_keccak::PublicKey; +let commitment: Word = public_key.to_commitment(); +``` + +### Migration Steps + +1. Recompute every stored ECDSA public-key commitment with `PublicKey::to_commitment()`. +2. Re-derive anything downstream of that commitment — account storage slots, note storage, advice-map keys. +3. No MASM call-site changes are needed. The operand-stack contract of `ecdsa_k256_keccak::verify` is still `[PK_COMM, MSG_WORD, ...]`; only the value of `PK_COMM` moved. The **advice** layout did change, though — see [MASM Changes](./masm-changes#ecdsa-advice-and-signature-abi-changed). + +--- + +## MMR peak commitments now bind the leaf count + +### Summary + +MMR peak commitments are computed over `[num_leaves, 0, 0, 0] || padded_peaks` instead of `padded_peaks` alone, on both the Rust and MASM sides. **All MMR peak commitments change** ([#3388](https://github.com/0xMiden/miden-vm/pull/3388)). + +### Affected Code + +```text +v0.15: hash_peaks() = Poseidon2::hash_elements( padded_peaks ) +v0.16: hash_peaks() = Poseidon2::hash_elements( [num_leaves, 0, 0, 0] || padded_peaks ) +``` + +The `miden::core::collections::mmr` `pack` and `unpack` procedures were updated to the same preimage. In 0.15, `pack` hashed the range starting at `mmr_ptr + 4`, skipping the leaf-count word; in 0.16 it hashes from `mmr_ptr`, so the leaf count is absorbed first. The MASM stack contracts (`[mmr_ptr, ...] -> [HASH, ...]`) are unchanged. + +### Migration Steps + +1. Recompute and re-persist every stored MMR peak commitment. +2. Invalidate any cached chain-MMR commitment, advice-map entry keyed by an MMR commitment, or proof whose witness depends on one. +3. No MASM call-site changes — `mmr::pack` and `mmr::unpack` keep their signatures. + +--- + +## Domain-separated empty-input hashing changed + +### Summary + +`hash_elements_in_domain(&[], d)` for a nonzero domain `d` used to collide with other inputs. The fix marks the empty-input case in the third capacity element and applies a permutation, so the result is now a distinct, nonzero digest ([#3447](https://github.com/0xMiden/miden-vm/pull/3447), refining [#3366](https://github.com/0xMiden/miden-vm/pull/3366)). + +Related and also digest-changing: `hash_bytes(&[])` no longer returns `Word::default()`. The empty-bytes input now absorbs a padding marker and permutes, producing a nonzero digest consistent with the `10*` sponge padding rule ([#3366](https://github.com/0xMiden/miden-vm/pull/3366)). + +### Affected Code + +```rust +// After (0.16) — the empty-input branch, from the algebraic sponge implementation +} else if total_len == 0 && state[CAPACITY_RANGE.start + 1] != ZERO { + // Mark an empty domain-separated input in an otherwise unused capacity element. + state[CAPACITY_RANGE.start + 2] = Felt::ONE; + S::apply_permutation(&mut state); +} +``` + +### Migration Steps + +1. Re-derive any commitment computed as `hash_elements_in_domain` over an empty element list with a nonzero domain — typically "empty collection" sentinel values. +2. Re-derive any value computed as `hash_bytes(&[])`. A stored zero word is no longer the right answer. +3. `merge_in_domain` and non-empty `hash_elements_in_domain` inputs are unaffected. + +--- + +## `AeadPoseidon2` key derivation restored to canonical decoding + +### Summary + +`AeadPoseidon2::key_from_bytes` was restored to canonical-`Felt` decoding ([#3366](https://github.com/0xMiden/miden-vm/pull/3366)). Keys persisted under the brief SHA-256 KDF contract must be re-derived. + +### Migration Steps + +1. If you persisted AEAD keys derived with `key_from_bytes` during the 0.16 pre-release window, re-derive them. +2. Data encrypted under a key derived by the interim contract cannot be decrypted with a canonically-derived key — re-encrypt it. + +--- + +## Common Errors + +These changes do not produce compile errors. Expect runtime symptoms instead: + +| Symptom | Cause | Solution | +| --- | --- | --- | +| Signature verification traps for a key that worked in 0.15 | Stored `PK_COMM` uses the old preimage | Recompute with `PublicKey::to_commitment()`. | +| Advice-map lookup misses for a key you know you inserted | The key is a changed commitment | Re-derive the key. | +| Chain-MMR commitment mismatch after upgrading | Peak commitment now binds the leaf count | Recompute and re-persist. | +| An "empty" sentinel commitment no longer matches | Empty-input hashing changed | Re-derive the sentinel. | +| Previously encrypted data fails to decrypt | AEAD key derivation changed | Re-derive the key and re-encrypt. | diff --git a/docs/builder/migration/02-hashing-stack.md b/docs/builder/migration/02-hashing-stack.md deleted file mode 100644 index a46812d1..00000000 --- a/docs/builder/migration/02-hashing-stack.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -sidebar_position: 2 -title: "Hashing, SMT & Crypto Changes" -description: "SMT leaf domain separation, miden-crypto 0.25, and downstream crypto renames in v0.15" ---- - -# Hashing, SMT & Crypto Changes - -:::warning Breaking Change -SMT leaf hashing now mixes a Poseidon2 leaf-domain separator into the capacity word, and `miden-crypto` bumped to `0.25`. These are digest-changing: persisted SMT roots, leaf digests, and `PartialSmt` values from earlier versions do not round-trip. -::: - ---- - -## SMT leaf hashing switched to Poseidon2 domain separation - -### Summary - -The core library's Sparse Merkle Tree leaf hashing (`miden::core` `collections::smt`) now mixes a leaf‑domain separator into the Poseidon2 capacity word, so MASM‑side leaf digests match `SmtLeaf::hash()` in `miden-crypto`. Leaf preimages are hashed with `poseidon2::merge_in_domain` using `LEAF_DOMAIN = 0x13af`. This is a **digest‑changing** change: any SMT leaf digest, SMT root, or advice‑map key derived from MASM‑side leaf hashing under `0.22` will not reproduce under `0.23`. It pairs with the `miden-crypto 0.25` bump. - -### Affected Code - -**MASM (core‑lib `collections::smt`, simplified):** -```diff -- exec.poseidon2::merge assert_eqw -+ push.LEAF_DOMAIN exec.poseidon2::merge_in_domain assert_eqw -``` -The per‑leaf cycle cost also changed (the `pair_count` coefficient went from `3` to `6`), so any hard‑coded cycle‑count expectations around `smt::get` / `smt::set` need updating. - -### Migration Steps - -1. Re‑derive every persisted SMT root, leaf digest, and advice‑map key computed from a MASM‑side SMT leaf hash under `0.22`. -2. If you compute SMT leaf digests in Rust via `miden-crypto`, upgrade to `0.25` so both sides agree. -3. Discard cached proofs / transaction artifacts whose witnesses depend on the old leaf hashing. - ---- - -## `miden-crypto` 0.25 downstream renames - -### Summary - -Bumping to `miden-crypto 0.25` (and `miden-vm 0.23`) surfaces several renames in code that builds against the protocol crates directly: - -- `Felt::new(n)` call sites that want the previous (non‑reducing) behaviour are now **`Felt::new_unchecked(n)`** (`Felt::new` now reduces modulo the field). -- The ECDSA secret key type `ecdsa_k256_keccak::SecretKey` is renamed **`SigningKey`**; the EdDSA/X25519 key `eddsa_25519_sha512::SecretKey` is **`KeyExchangeKey`**. Falcon's `falcon512_poseidon2::SecretKey` is unchanged. -- The kernel's `EMPTY_SMT_ROOT` constant was recomputed for the Plonky3‑aligned Poseidon2 and the domain‑separated `SmtLeaf::hash` — any hard‑coded SMT‑root literal changes. -- In kernel/standards MASM, the immediate form of `adv_push` was dropped and cross‑module‑referenced MASM constants/procedures must be marked `pub`. - -### Affected Code - -```diff -- let f = Felt::new(value); -- use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; -- use miden_protocol::crypto::dsa::eddsa_25519_sha512::SecretKey as EdSecretKey; -+ let f = Felt::new_unchecked(value); -+ use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey; -+ use miden_protocol::crypto::dsa::eddsa_25519_sha512::KeyExchangeKey; -``` - -### Migration Steps - -1. Replace `Felt::new(...)` with `Felt::new_unchecked(...)` where you relied on the non‑reducing constructor. -2. Rename `ecdsa_k256_keccak::SecretKey` → `SigningKey` and `eddsa_25519_sha512::SecretKey` → `KeyExchangeKey`. -3. Mark any cross‑module‑referenced MASM constants/procedures `pub`, and regenerate hard‑coded `EMPTY_SMT_ROOT` / SMT‑root literals. - ---- - -## `PartialSmt` serialization changed - -### Summary - -In `miden-crypto 0.25` the serialized byte layout of `PartialSmt` changed. Old serialized `PartialSmt` values are **not compatible** with `0.25` and will not deserialize correctly. - -### Migration Steps - -1. Discard any `PartialSmt` values serialized under an earlier `miden-crypto`. -2. Rebuild them from current state, or re‑fetch them under `0.25`. - ---- - -## Custom `LargeSmt` storage backends: reads move to `SmtStorageReader` - -### Summary - -Custom `LargeSmt` storage backends need a small trait update: reads moved to a dedicated **`SmtStorageReader`**. Writable storage still implements `SmtStorage`, but now also sets an associated `type Reader` and returns a point‑in‑time reader via `reader()`. Read operations go through `SmtStorageReader` rather than the writable `SmtStorage` directly. - -### Migration Steps - -1. Keep your writable backend implementing `SmtStorage`, and add the associated `type Reader` plus a `reader()` method that returns a point‑in‑time `SmtStorageReader`. -2. Move read operations onto the `SmtStorageReader` returned by `reader()`. - ---- - -## Direct `miden-crypto` 0.24 API breaks - -### Summary - -For the rare consumers that depend on `miden-crypto` directly, the `0.24` step carries a few additional API breaks: - -- The `WORD_SIZE`, `WORD_SIZE_FELTS`, and `WORD_SIZE_BYTES` constants moved to **`Word::NUM_ELEMENTS`** / **`Word::SERIALIZED_SIZE`**. -- `LexicographicWord` is now just **`Word`**. -- `Felt` no longer derefs. -- Custom multi‑AIR prover/verifier code must handle `StarkProof` log trace heights and `air_order`. - -### Migration Steps - -1. Replace `WORD_SIZE` / `WORD_SIZE_FELTS` with `Word::NUM_ELEMENTS` and `WORD_SIZE_BYTES` with `Word::SERIALIZED_SIZE`. -2. Replace `LexicographicWord` with `Word`. -3. Remove any reliance on `Felt`'s `Deref`; access the inner value explicitly. -4. If you maintain custom multi‑AIR prover/verifier code, update it to handle `StarkProof` log trace heights and `air_order`. diff --git a/docs/builder/migration/03-account-changes.md b/docs/builder/migration/03-account-changes.md index 613513b6..1c46191e 100644 --- a/docs/builder/migration/03-account-changes.md +++ b/docs/builder/migration/03-account-changes.md @@ -1,100 +1,325 @@ --- sidebar_position: 3 title: "Account Changes" -description: "AccountType/AccountStorageMode rework, network-account allowlist, typed roots, and auth/policy renames in v0.15" +description: "Auth components become ordinary components, Approver replaces raw key arguments, component names change, and account updates move to AccountPatch" --- # Account Changes :::warning Breaking Change -The v0.15 release simplifies the account ID so that its prefix no longer encodes whether the account is a faucet or regular account, whether its code is mutable, or whether it is a network account. The old `AccountType` enum is removed, `AccountStorageMode` is renamed to `AccountType` (`{ Private, Public }`), and the account ID version is renamed `0` → `1`. All of the changes below require code updates when migrating from v0.14. +`AccountBuilder::with_auth_component` was removed — auth components are now passed through `with_component` like any other, and identified by their MASM `@auth_script` attribute. Auth components take an `Approver` instead of a raw key and scheme. Every standard component's `NAME` constant changed, and because component metadata feeds the storage schema commitment, **accounts rebuilt from the same seed will have different commitments**. ::: -## `AccountType` removed; `AccountStorageMode` renamed to `AccountType` +## Quick Fix + +```rust +// Before (0.15) +let account = AccountBuilder::new(init_seed) + .account_type(AccountType::Public) + .with_auth_component(AuthSingleSig::new(pub_key, auth_scheme)) + .with_component(BasicWallet) + .build()?; + +// After (0.16) +let account = AccountBuilder::new(init_seed) + .account_type(AccountType::Public) + .with_component(AuthSingleSig::new(Approver::new(pub_key, auth_scheme))) + .with_component(BasicWallet) + .build()?; +``` + +If you encounter errors, continue reading for detailed migration steps. + +--- + +## Summary + +Three independent shifts land on accounts in this release. + +**Auth stops being special.** In 0.15 the builder had a dedicated auth slot; in 0.16 the auth component is just a component, and the builder finds it by looking for the `@auth_script` attribute in its MASM. This is what makes the fee change possible — the auth procedure is now also where fees get paid, so it needed to compose with everything else. + +**Keys are wrapped in an `Approver`.** `AuthSingleSig::new` took a public-key commitment and a scheme; it now takes a single `Approver` carrying both. Multi-signature components take an `ApproverSet` with a threshold. This is a mechanical rewrite, but it touches every account you construct. + +**Component names were normalised**, which changes commitments. Every standard component's `NAME` dropped its `components::` segment. Since the name feeds component metadata, and metadata feeds the storage schema commitment, this silently changes account commitments even when nothing else about your account changed. + +--- + +## `AccountBuilder::with_auth_component` removed ### Summary -The account ID was simplified so its prefix no longer encodes whether the account is a faucet/regular account or whether its code is mutable. As a result: +`AccountBuilder` now takes all components uniformly through `with_component`, and identifies the auth component by its `@auth_script` MASM attribute. -- The old `AccountType` enum (`FungibleFaucet`, `NonFungibleFaucet`, `RegularAccountImmutableCode`, `RegularAccountUpdatableCode`) is **removed**. -- `AccountStorageMode` is **renamed to `AccountType`** and trimmed to `{ Private, Public }` (the `Network` variant is gone — see the next section). -- The `AccountId` accessors `is_faucet()`, `is_regular_account()`, `storage_mode()`, `is_network()`, and the old `account_type()` semantics no longer exist. `AccountId::account_type()` now returns the visibility‑style `AccountType` (`Private`/`Public`). -- The account ID **version is renamed 0 → 1**; encoded version `0` is now invalid. +### Affected Code + +```diff + let account = AccountBuilder::new(init_seed) + .account_type(AccountType::Public) +- .with_auth_component(auth_component) ++ .with_component(auth_component) + .with_component(BasicWallet) + .build()?; +``` + +Reach for `with_component` whenever you are naming a component yourself — it takes `impl Into`, so no explicit `.into()` is needed. `with_components` is for the case where the count is not known at the call site: a configuration value that expands into one or several components depending on its variant. `AuthNetworkAccount` is the example in this release — see [Network accounts require a fee policy](#network-accounts-require-a-fee-policy). + +`AccountBuilder` also gained `with_asset_callbacks(AssetCallbackFlag)`. Whether a faucet's assets trigger callbacks is now encoded in the account ID rather than in separate storage, so this is set at construction time. + +### Migration Steps -Faucet‑vs‑regular is now a property of the account's *code/components*, not its ID. +1. Drop `with_auth_component` and pass the auth component through `with_component`, like any other. +2. If you author a custom auth component, make sure its entry procedure carries the `@auth_script` attribute — that is how the builder recognises it. +3. If you build a faucet whose assets should trigger callbacks, set `with_asset_callbacks`. + +### Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| `no method named with_auth_component` | Method removed | Use `with_component`. | +| Build fails reporting no auth component | Custom component lacks the attribute | Annotate the entry procedure with `@auth_script`. | + +--- + +## `Approver` and `ApproverSet` replace raw key arguments + +### Summary + +`Approver` bundles a public-key commitment with its signature scheme; `ApproverSet` bundles a list of approvers with a threshold. Both are new in 0.16. The `AuthMethod` enum was removed, and `AuthSingleSigAcl` / `AuthSingleSigAclConfig` were removed outright. ### Affected Code ```rust -// 0.15 — new API: -use miden_protocol::account::AccountType; // formerly AccountStorageMode -let kind: AccountType = account_id.account_type(); // Private / Public -let is_pub = account_id.is_public(); -// "is this a faucet?" now comes from the account's code/interface, not the id. +// Before (0.15) +AuthSingleSig::new(pub_key: PublicKeyCommitment, auth_scheme: AuthScheme) -> Self +``` + +```rust +// After (0.16) +Approver::new(pub_key: PublicKeyCommitment, auth_scheme: AuthScheme) -> Approver +AuthSingleSig::new(approver: Approver) -> Self +ApproverSet::new(approvers: Vec, threshold: u32) -> Result +AuthMultisig::new(approver_set: ApproverSet) -> Self +``` + +The convenience constructors are unchanged and remain the shortest path when you have a concrete key: + +```rust +// Identical in 0.15 and 0.16 +AuthSingleSig::falcon512_poseidon2(pub_key) +AuthSingleSig::ecdsa_k256_keccak(pub_key) +AuthSingleSig::from_public_key(pub_key) ``` -`AccountId::new(seed, version, ..)` keeps the same parameters, but `version` must be `AccountIdVersion::Version1` (`Version0` no longer exists). + +New accessors: `AuthSingleSig::approver()`, `ApproverSet::approvers()`, and `ApproverSet::threshold()`. ### Migration Steps -1. Replace imports of `AccountStorageMode` with `AccountType`; the variants are `Private` / `Public`. -2. Delete the old `AccountType` import (`Regular*`/`*Faucet`); that enum is gone. -3. Replace `id.storage_mode()` with `id.account_type()` (or `id.is_public()` / `id.is_private()`). -4. Replace `id.is_faucet()` / `id.is_regular_account()` with checks on the account's code/components. -5. Replace `AccountIdVersion::Version0` with `Version1`; regenerate any persisted account IDs. +1. Wrap existing `AuthSingleSig::new(pub_key, scheme)` arguments in `Approver::new(pub_key, scheme)`. +2. Replace multi-signature construction with `ApproverSet::new(approvers, threshold)?` — note it is fallible. +3. Remove any use of `AuthMethod`; the scheme now travels inside the `Approver`. +4. If you used `AuthSingleSigAcl`, there is no drop-in replacement. Rebuild the access-control policy using the components under `miden::standards::access` (for example `RoleBasedAccessControl` or `Authority`). + +### Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| `this function takes 1 argument but 2 were supplied` on `AuthSingleSig::new` | Signature changed | Wrap the arguments in `Approver::new`. | +| `cannot find type AuthMethod` | Removed | Use `Approver` / `AuthScheme`. | +| `cannot find type AuthSingleSigAcl` | Removed | Rebuild with an access-control component. | --- -## `AccountStorageMode::Network` removed; network accounts via an allowlist +## Component names changed, and account commitments with them ### Summary -The `Network` storage mode was removed (`AccountStorageMode` itself is now `AccountType`). An account is now recognised as a network account by the presence of a standardized **`NetworkAccountNoteAllowlist`** storage slot, with helpers `NetworkAccount` (a wrapper for identification) and the `AuthNetworkAccount` auth component. `AccountId::is_network()` is gone. +Every standard component's `NAME` constant was normalised by dropping the `components::` segment. Component metadata feeds the storage schema commitment, so **the commitment of an account built from the same seed and the same components differs between 0.15 and 0.16**. + +### Affected Code + +```diff +- "miden::standards::components::auth::singlesig" ++ "miden::standards::auth::singlesig" + +- "miden::standards::components::wallets::basic_wallet" ++ "miden::standards::wallets::basic_wallet" + +- "miden::standards::components::access::rbac" ++ "miden::standards::access::rbac" + +- "miden::standards::components::faucets::fungible_faucet" ++ "miden::standards::faucets::fungible" +``` + +A few names already lacked the segment in 0.15 — `miden::standards::auth::network_account` and `miden::standards::access::ownable2step` are unchanged. Note that the fungible faucet also lost its `_faucet` suffix, so it is not a pure prefix change. + +The `miden::standards::account::metadata` module was also renamed to `miden::standards::account::inspection`, both in MASM and in Rust. ### Migration Steps -1. Remove any use of `AccountStorageMode::Network`; pick `Public` and add the network‑account components (`AuthNetworkAccount::with_allowed_notes(...)`). -2. Replace `id.is_network()` with `NetworkAccount::new(account)` / the `NetworkAccountNoteAllowlist` slot check. +1. Update any hard-coded component name strings. +2. Expect new account IDs and commitments for accounts rebuilt from the same seed. If you have persisted an account ID derived under 0.15, it will not be reproduced by 0.16 construction. +3. Update references to `account::metadata` to `account::inspection`. --- -## `AuthNetworkAccount` gains a tx‑script allowlist +## Component MASM must annotate exported procedures ### Summary -*(v0.15.2)* The `AuthNetworkAccount` auth component previously banned transaction scripts outright. It now gates them with a **root allowlist**, so approved tx scripts (e.g. setting the expiration delta) can run. The note‑allowlist constructor `with_allowlist` was renamed to **`with_allowed_notes`** to pair with the new **`with_allowed_tx_scripts`** setter. +Account component MASM must annotate every exported procedure with `@account_procedure`. Un-annotated procedures are not exported. This attribute is new in 0.16 — the protocol's own MASM went from zero uses to 99. + +`@auth_script` and `@note_script` already existed in 0.15 and are unchanged. + +### Affected Code + +```masm +# After (0.16) +@account_procedure +pub proc receive_asset(asset: word) + # … +end + +@auth_script +pub proc auth_tx(auth_args: word) + # … +end +``` ### Migration Steps -1. Rename `AuthNetworkAccount::with_allowlist(...)` to `with_allowed_notes(...)`. -2. To permit specific transaction scripts, chain `.with_allowed_tx_scripts(roots)` (an empty set — the default — permits none). +1. Add `@account_procedure` to every procedure your component intends to export. +2. Re-check the resulting `AccountCode` procedure list — a missing annotation shows up as a procedure that silently is not callable, not as a compile error. --- -## `procedure_digest!` → `procedure_root!`; new `NoteScriptRoot` / `AccountComponentName` +## `AccountCode::from_parts` is now fallible ### Summary -Several root/identifier values gained dedicated newtypes: +`AccountCode::from_parts` validated its procedure count with `assert!` in 0.15 and now returns a `Result` instead of panicking. -- The `procedure_digest!` macro is renamed **`procedure_root!`**. It now returns an `AccountProcedureRoot` (instead of `Word`) and takes an `AccountComponentCode` (`Component::code()`) instead of a library‑producing closure. -- `NoteScript::root()` returns a new **`NoteScriptRoot`** newtype instead of `Word` (convert with `.into()`). -- A new **`AccountComponentName`** string wrapper validates component names. +### Affected Code + +```rust +// Before (0.15) +pub fn from_parts(mast: Arc, procedures: Vec) -> Self + +// After (0.16) +pub fn from_parts( + mast: Arc, + procedures: Vec, +) -> Result +``` + +### Migration Steps + +Add `?` or explicit error handling at every call site. + +--- + +## `AccountId` no longer converts into `[Felt; 2]` + +### Summary + +The `impl From for [Felt; 2]` was removed. Use the `prefix()` and `suffix()` accessors instead. Conversions to `[u8; 15]` and `u128` are unchanged. + +### Affected Code + +```rust +// Before (0.15) +let felts: [Felt; 2] = account_id.into(); +``` + +```rust +// After (0.16) +let prefix: AccountIdPrefix = account_id.prefix(); +let suffix: Felt = account_id.suffix(); +``` + +### Migration Steps + +Replace the `into()` conversion with the two accessors. Note `prefix()` returns an `AccountIdPrefix`, not a bare `Felt`. + +--- + +## Account updates move from `AccountDelta` to `AccountPatch` + +### Summary + +Account **updates** moved from the relative `AccountDelta` to the absolute `AccountPatch`. `ExecutedTransaction` and `AccountUpdateDetails` now carry a patch, and `Account::apply_delta` was replaced by applying a patch. + +:::info `AccountDelta` still exists +This is not a wholesale removal. `AccountDelta` remains, and `TransactionSummary::account_delta()` deliberately still returns one — the signed transaction summary binds a *relative* delta. Only account update representation moved to the absolute patch model. The same split exists on the TypeScript side, where `TransactionSummary.accountDelta()` is unchanged while the result's `accountDelta()` became `accountPatch()`. +::: + +### Affected Code + +```rust +// Before (0.15) +let delta = executed_tx.account_delta(); +account.apply_delta(&delta)?; +``` + +```rust +// After (0.16) +let patch = executed_tx.account_patch(); +account.apply_patch(&patch)?; + +// Or build a fresh account from the patch instead of mutating one: +let account = Account::try_from(&patch)?; +``` + +`apply_patch` is the direct replacement for `apply_delta` and keeps the same in-place shape, so it is the smaller edit for existing code. The same rename applies further down: `AssetVault::apply_delta` became `apply_patch`, taking an `AccountVaultPatch`. ### Migration Steps -1. Rename `procedure_digest!` → `procedure_root!` and pass `Component::code()` as the last argument; the static is now a `LazyLock`. -2. Update bindings of `note_script.root()` to `NoteScriptRoot` (convert with `.into()` / `Word::from(..)` where a `Word` is needed). -3. Use `AccountComponentName::new(...)` where a validated component name is required. +1. Replace `account_delta()` with `account_patch()` on `ExecutedTransaction` and on client transaction results. +2. Replace `Account::apply_delta(&delta)` with `Account::apply_patch(&patch)`, or construct a new account with `Account::try_from(&patch)`. +3. Leave `TransactionSummary::account_delta()` call sites alone — that one is intentionally still a delta. --- -## `is_compatible_with` removed; auth/policy renames +## Network accounts require a fee policy ### Summary -A handful of standards‑library APIs changed: +`AuthNetworkAccount::new` now takes a `FeePolicyManager` alongside the allowed-note set. The manager carries the fee faucet and the active fee policy, and expands into the policy's components when the auth component is installed — so you do not install policy components separately. + +:::note This applies to network accounts and faucets, not ordinary accounts +Fee *policies* describe how an account that sponsors or charges fees estimates them. An ordinary user account paying a fee does not install one; it supplies fee conversion info per transaction instead. See [Transaction Changes](./transaction-changes#transaction-fees-are-paid-by-the-auth-procedure). +::: + +### Affected Code + +```rust +// After (0.16) +let manager = FeePolicyManager::builder() + .fee_faucet_id(fee_faucet_id) + .active_fee_policy(FeePolicy::from(BasicConstantFeePolicy::new())) + .build(); + +let auth = AuthNetworkAccount::new(allowed_notes, manager)?; +``` + +`AuthNetworkAccount` no longer converts into a single `AccountComponent` — it expands into several, so install it through `with_components`. + +### Migration Steps + +1. Build a `FeePolicyManager` with the fee faucet and an active fee policy, registering any alternatives with `allowed_fee_policy` for runtime switching. +2. Pass it to `AuthNetworkAccount::new`, which is fallible. +3. Install the auth component with `with_components`, not `with_component`, since it expands to several components. + +--- -- `StandardNote::is_compatible_with` and `AccountInterfaceExt::is_compatible_with` were **removed** — perform compatibility checks via the account interface directly. -- The guarded‑multisig API was renamed `AuthMultisigGuardian` → `AuthGuardedMultisig` (the `guardian` auth namespace is retained). -- `OwnerControlledBlocklist` was renamed `BlocklistOwnerControlled`. -- New standard components were added: `Pausable`, `Authority`, allowlist/blocklist transfer policies, and `FungibleTokenMetadata`. +## Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| `no method named with_auth_component` | Removed | Use `with_component`, or `with_components` for `AuthNetworkAccount`. | +| `this function takes 1 argument but 2 were supplied` | `AuthSingleSig::new` takes an `Approver` | Wrap in `Approver::new`. | +| `cannot find type AuthMethod` / `AuthSingleSigAcl` | Removed | See the auth section above. | +| `expected Result, found AccountCode` | `from_parts` is fallible | Add `?`. | +| `the trait From is not implemented for [Felt; 2]` | Conversion removed | Use `prefix()` / `suffix()`. | +| Account commitment differs from 0.15 for the same seed | Component names changed | Expected; re-record the new ID. | +| Component procedure is not callable but compiles | Missing `@account_procedure` | Annotate the procedure. | diff --git a/docs/builder/migration/04-note-changes.md b/docs/builder/migration/04-note-changes.md index 7a7bf06b..eaf5a2ff 100644 --- a/docs/builder/migration/04-note-changes.md +++ b/docs/builder/migration/04-note-changes.md @@ -1,177 +1,171 @@ --- sidebar_position: 4 title: "Note Changes" -description: "Note identity split, multiple attachments, metadata reshape, nullifier, and capacity changes in v0.15" +description: "Standard notes gain typed builders, the per-note asset limit drops from 64 to 16, and mint/burn scripts are unified" --- # Note Changes :::warning Breaking Change -Note identity is split in two: the old `NoteId` (recipient + assets) becomes `NoteDetailsCommitment`, and a brand‑new `NoteId` also commits to metadata. Notes now carry multiple attachments off the metadata (`NoteMetadata` → `PartialNoteMetadata`), and nullifiers fold in the metadata word and attachments commitment. None of these values roundtrip with 0.14 — recompute and re‑persist note ids and nullifiers. +Every standard note changed shape. `P2idNote`, `P2ideNote`, `SwapNote`, `MintNote`, and `BurnNote` were marker types with a `create(..)` associated function returning a `Note`; they are now real structs built with a typed builder and converted with `.into()`. Separately, **`MAX_ASSETS_PER_NOTE` dropped from 64 to 16**, so any note packing more than 16 assets now fails to build. ::: ---- - -## `NoteId` → `NoteDetailsCommitment`; new `NoteId` commits to metadata +## Quick Fix -### Summary +```rust +// Before (0.15) +let note = P2idNote::create( + sender, target, vec![asset], NoteType::Public, attachments, &mut rng, +)?; + +// After (0.16) +use miden_standards::note::P2idNote; + +let note: Note = P2idNote::builder() + .sender(sender) + .target(target) + .assets(vec![asset]) + .note_type(NoteType::Public) + .generate_serial_number(&mut rng) + .build()? + .into(); +``` -The 0.14 `NoteId` (a commitment over recipient + assets only) is renamed to **`NoteDetailsCommitment`**. A brand‑new **`NoteId`** is introduced that hashes the details commitment together with the note metadata commitment, so the public note ID now changes when the note's metadata (sender, type, tag, attachments) changes. +If you encounter errors, continue reading for detailed migration steps. -```text -NoteDetailsCommitment = hash(NOTE_RECIPIENT_DIGEST || NOTE_ASSETS_COMMITMENT) // == old NoteId -NoteId = hash(NOTE_DETAILS_COMMITMENT || NOTE_METADATA_COMMITMENT) // new -``` +--- -### Affected Code +## Summary -```rust -// 0.15 — new API: -use miden_protocol::note::{NoteDetailsCommitment, NoteId}; -let details_commitment = NoteDetailsCommitment::new(&recipient, &assets); -let id = NoteId::new(details_commitment, &metadata); // the real NoteId mixes in metadata -// On a built note: note.id() / note.details_commitment(). -``` -`NoteDetails::commitment()` now returns a `NoteDetailsCommitment` (not the public note id, which requires metadata). +In 0.15 each standard note was a unit struct — `pub struct P2idNote;` — with a `create` associated function that took every parameter positionally and returned a finished `Note`. In 0.16 each is a real struct holding its fields, built through a `bon` builder and converted to a `Note` with `Into`. -### Migration Steps +The cost is that every call site changes. The benefit is worth more than a mechanical rewrite, so it is worth pausing on before you reach for search-and-replace: each standard note is now a distinct type, which means your own functions can take a `P2idNote` or a `SwapNote` instead of a bare `Note`. What used to be a runtime check — is this really a P2ID note? — becomes a signature the compiler enforces, and the conversion to `Note` happens once, at the boundary where you actually need one. Optional parameters also stop being positional, and the typed value is inspectable before you convert it. -1. Rename any value you treated as a "note id without metadata" to `NoteDetailsCommitment`. -2. Replace `NoteId::new(recipient, asset_commitment)` with `NoteDetailsCommitment::new(&recipient, &assets)`. -3. To obtain the public `NoteId`, call `note.id()`, or `NoteId::new(details_commitment, &metadata)`. -4. Recompute and re‑persist any stored note IDs — 0.14 ids do not roundtrip, and an id now changes if metadata changes. +Watch the asset limit separately. Nothing about it is visible at compile time, so your build stays green and only notes carrying more than 16 assets fail, at the point they are built. --- -## `NoteMetadata` → `PartialNoteMetadata`; multiple attachments per note +## Standard notes are built with typed builders ### Summary -The metadata types were renamed and reshuffled to support **multiple attachments per note** (up to `NoteAttachments::MAX_COUNT` = 4): - -| 0.14 | 0.15 | -| --- | --- | -| `NoteMetadata` (sender/type/tag + single attachment) | `PartialNoteMetadata` (sender/type/tag only) | -| `NoteMetadataHeader` (the on‑stack metadata word) | `NoteMetadata` (metadata word + attachment headers + attachments commitment) | -| `NoteAttachment` (single, `with_attachment`) | `NoteAttachments` (collection, `with_attachments`) | - -`Note::new(assets, metadata, recipient)` now takes a **`PartialNoteMetadata`**; a new `Note::with_attachments(assets, partial_metadata, recipient, attachments)` carries the attachments. The single `NoteMetadata::with_attachment` / `.attachment()` API is gone. +Each standard note struct now exposes `builder()` and converts into `Note` via `From` / `Into`. ### Affected Code ```rust -// 0.15 — new API: -use miden_protocol::note::{Note, PartialNoteMetadata, NoteType, NoteAttachments}; -let partial = PartialNoteMetadata::new(sender, NoteType::Public).with_tag(tag); -let attachments = NoteAttachments::new(vec![attachment_a, attachment_b])?; // 0..=4 -let note = Note::with_attachments(assets, partial, recipient, attachments); -// (use Note::new(assets, partial, recipient) for a note with no attachments) -let found = note.attachments().find(scheme); +// Before (0.15) +pub fn create( + sender: AccountId, + target: AccountId, + assets: Vec, + note_type: NoteType, + attachments: NoteAttachments, + rng: &mut R, +) -> Result ``` -### Migration Steps - -1. Search/replace the *type* used to construct a note from `NoteMetadata` to `PartialNoteMetadata`. -2. Replace `NoteMetadataHeader` with `NoteMetadata` (the word‑shaped metadata is `NoteMetadata::to_metadata_word()`). -3. Replace `.with_attachment(a)` with a `NoteAttachments::new(vec![...])` collection and `Note::with_attachments(...)`. -4. Replace single `metadata.attachment()` reads with `note.attachments().get(i)` / `.find(scheme)` / `note.has_attachments()`. - ---- - -## Attachment MASM: `set_*` → `add_*`; `get_metadata` drops attachments - -### Summary +```rust +// After (0.16) +let p2id = P2idNote::builder() + .sender(sender) + .target(target) + .assets(vec![asset]) // or .asset(x), repeatable + .note_type(NoteType::Public) + .generate_serial_number(&mut rng) // or .serial_number(word) + .build()?; +let note: Note = p2id.into(); +``` -Because a note can now hold several attachments, the kernel/protocol attachment procedures were rewritten: +Note two naming details that are easy to get wrong: the setter is `serial_number`, not `serial_num`, and `generate_serial_number(&mut rng)` is the direct replacement for the old `rng` parameter. -- `output_note::set_attachment` → **`add_attachment`** (and `set_word_attachment` → `add_word_attachment`, `set_array_attachment` → `add_attachment_from_memory`). They *append* instead of overwriting, and the stack signature dropped the `attachment_kind` field. -- `note::extract_attachment_info_from_metadata` → **`metadata_into_attachment_schemes`**, returning the four attachment scheme markers. -- All `get_metadata` procedures (`active_note`, `input_note`, `output_note`) **no longer return attachments** — they return just the single `METADATA` word. +`P2ideNote` takes its optional parameters as optional setters rather than positionally: -### Affected Code - -```masm -# 0.15 — new API: -# Operand Stack: [attachment_scheme, ATTACHMENT_COMMITMENT, note_idx] -exec.output_note::add_attachment -exec.active_note::get_metadata # => [METADATA] -exec.note::metadata_into_attachment_schemes -# => [attachment_0_scheme, attachment_1_scheme, attachment_2_scheme, attachment_3_scheme] +```rust +let p2ide = P2ideNote::builder() + .sender(sender) + .target(target) + .assets(vec![asset]) + .note_type(NoteType::Private) + .serial_number(serial_number) + .reclaimer(reclaimer_account_id) // optional; defaults to the sender + .reclaim_height(BlockNumber::from(n)) // optional + .timelock_height(BlockNumber::from(m)) // optional + .build()?; ``` -### Migration Steps - -1. Rename `set_attachment` / `set_word_attachment` / `set_array_attachment` to `add_attachment` / `add_word_attachment` / `add_attachment_from_memory`, and drop the `attachment_kind` operand. -2. Replace `extract_attachment_info_from_metadata` with `metadata_into_attachment_schemes`. -3. Audit every `get_metadata` consumer: it now leaves only `[METADATA]` — remove the extra cleanup that handled the attachment word. +`SwapNote` follows the same pattern. In 0.15 `SwapNote::create` returned a `(Note, NoteDetails)` tuple carrying the payback details; in 0.16 you build the `SwapNote` and read its parts from the typed value. ---- +The same builder treatment applies to `MintNote`, `BurnNote`, `PswapNote`, and `TxFeeNote`, along with the configuration notes (`AllowlistConfigNote`, `OwnerConfigNote`, `FaucetMetadataConfigNote`, `NetworkAccountConfigNote`, `FaucetPolicyConfigNote`, `MinBurnAmountConfigNote`). -## `NoteType` encoding 2‑bit → 1‑bit; `Private` is the default +### Migration Steps -### Summary +1. Replace every `XNote::create(..)` call with the corresponding `XNote::builder()` chain ending in `.build()?`, then `.into()` wherever a `Note` is required. +2. Replace the trailing `rng` argument with `.generate_serial_number(&mut rng)`. +3. Use `.assets(..)` for a collection or `.asset(..)` repeatedly for individual assets. +4. For `P2ideNote`, set only the optional parameters you actually need. `reclaimer` still defaults to the sender, so existing "sender can reclaim" behaviour is preserved without changes. +5. Push the `.into()` outward while you are here. Any function of yours that only ever handles one kind of note can take the typed note instead of a `Note`, which turns a runtime check into a compile-time guarantee; convert once, where a `Note` is genuinely needed. -`NoteType` dropped from a 2‑bit encoding to 1 bit. The numeric encodings flipped and `NoteType::Private` is now the `#[default]`. Anything that serialized a note type, packed it into a tag, or relied on the old `Public = 0b01` / `Private = 0b10` values changes. +### Common Errors -| | 0.14 | 0.15 | +| Error Message | Cause | Solution | | --- | --- | --- | -| `Public` | `0b01` | `1` | -| `Private` | `0b10` | `0` (default) | - -### Migration Steps - -1. Drop any hard‑coded `0b01` / `0b10` note‑type bit literals; use the `NoteType` variants. -2. Re‑derive note tags / metadata words that packed the old 2‑bit type (`SwapNote::build_tag`, for example, now uses the 1‑bit encoding — script‑root bits 14 → 15). -3. If you relied on a particular default, note it is now `Private`. +| `no function or associated item named create` | Replaced by the builder | Use `XNote::builder()`. | +| `no method named serial_num` | Setter renamed | Use `serial_number` or `generate_serial_number`. | +| `expected Note, found P2idNote` | The builder yields the typed note | Add `.into()`. | +| `a P2ID note must contain at least one asset` | Built with no assets | Add at least one asset. | --- -## Nullifier now includes metadata and attachments commitment +## `MAX_ASSETS_PER_NOTE` dropped from 64 to 16 ### Summary -The note nullifier hash now folds in the note's **metadata word** and **attachments commitment** in addition to the serial number, script root, storage commitment, and asset commitment. `Nullifier::new` gained two parameters and the `From<&NoteDetails>` conversion was replaced by `Nullifier::from_details_and_metadata`, because a nullifier can no longer be computed from details alone. +The protocol limit on assets carried by a single note fell from 64 to 16. ### Affected Code -```rust -// 0.15 — new API: -let nf = Nullifier::new( - script_root, storage_commitment, asset_commitment, serial_num, - metadata.to_metadata_word(), // new - metadata.attachments_commitment(), // new -); -let nf2 = Nullifier::from_details_and_metadata(¬e_details, &metadata); +```diff +- pub const MAX_ASSETS_PER_NOTE: usize = 64; ++ pub const MAX_ASSETS_PER_NOTE: usize = 16; ``` +This is enforced by `NoteAssets::new`, so it surfaces as a `NoteError` at build time rather than a compile error. + ### Migration Steps -1. Thread the metadata word and attachments commitment into every `Nullifier::new` call. -2. Replace `Nullifier::from(&details)` / `(&details).into()` with `Nullifier::from_details_and_metadata(&details, &metadata)`. -3. Recompute and re‑persist nullifiers — 0.14 nullifiers will not match. +1. Audit any code path that batches assets into a single note and cap it at 16. +2. If you previously relied on packing up to 64 assets, split the payload across multiple notes. +3. If you compute a batch size from the constant rather than hard-coding it, no change is needed beyond a rebuild. --- -## `MAX_ASSETS_PER_NOTE` 255 → 64; `NOTE_MEM_SIZE` 3072 → 1024 +## `MINT` and `BURN` are unified across faucet kinds ### Summary -The per‑note asset cap was reduced from 255 to **64**, and the kernel note memory region (`NOTE_MEM_SIZE`) shrank from 3072 to **1024**. Notes carrying more than 64 assets now fail to build, and MASM that hard‑codes note‑memory offsets against the old 3072‑word region must be reworked. +One `mint.masm` and one `burn.masm` script now serve both fungible and non-fungible faucets, with the variant carried in the note storage. **The script roots changed**, so any hard-coded or cached root is now wrong. ### Migration Steps -1. Cap note asset lists at 64; split larger payloads across multiple notes. -2. Audit any MASM that indexes into the note memory region against the new `NOTE_MEM_SIZE = 1024`. +1. Recompute and re-store any cached standard note script roots. +2. Remove per-faucet-kind branching that selected between separate mint or burn scripts. --- -## `SwapNote`/`MintNote` storage trimmed; `PSWAP` added +## Other note changes -### Summary +- **`PswapNote` (partial swap)** gained a minimum-fill parameter, and its fields were renamed. +- **`NoteFile` was reworked and moved to `miden-standards`**, with variants keyed on `NoteId`, `ExpectedNote`, and `Committed`. This mostly affects client code — see [Client Changes](./client-changes). +- **`NoteTag`** moved under `miden::standards::note::note_tag` in MASM. In the released `0.16.0-rc` line it is still reachable at `miden::standards::note_tag`. -Unused fields were removed from standard note storage: `payback_attachment` from `SwapNoteStorage` and `attachment` from `MintNoteStorage`. A new **`PSWAP`** (partial swap) note and `PswapNote` API (with a `PswapAttachment` scheme and `payback_note` / `remainder_note` discovery helpers) supports partial‑fill asset exchange with remainder re‑creation. +--- -### Migration Steps +## Common Errors -1. Stop reading/writing the removed `payback_attachment` / `attachment` storage fields on swap/mint notes. -2. Use `PswapNote` for partial‑fill swaps; reconstruct private paybacks via `PswapNote::payback_note` / `remainder_note`. +| Error Message | Cause | Solution | +| --- | --- | --- | +| `no function or associated item named create` | Notes use builders now | Rewrite with `XNote::builder()`. | +| `NoteError` about exceeding asset limits | Limit is now 16 | Split across multiple notes. | +| Note script root mismatch for mint or burn | Scripts were unified | Recompute the roots. | +| `expected Note, found MintNote` | Builder returns the typed note | Add `.into()`. | diff --git a/docs/builder/migration/05-asset-vault-faucet.md b/docs/builder/migration/05-asset-vault-faucet.md index 90285e15..9bf7a24b 100644 --- a/docs/builder/migration/05-asset-vault-faucet.md +++ b/docs/builder/migration/05-asset-vault-faucet.md @@ -1,81 +1,158 @@ --- sidebar_position: 5 -title: "Assets, Vault & Faucet" -description: "AssetAmount newtype, AssetVaultKey balance lookups, the unified FungibleFaucet component, and AssetComposition changes in v0.15" +title: "Assets, Vault & Faucet Changes" +description: "AssetVaultKey becomes AssetId, the old AssetId becomes AssetClass, and faucet factories split by authentication scheme" --- -# Assets, Vault & Faucet +# Assets, Vault & Faucet Changes :::warning Breaking Change -Fungible amounts are now a validated `AssetAmount` newtype rather than a raw `u64`. The separate `BasicFungibleFaucet` and `NetworkFungibleFaucet` components are unified into a single `FungibleFaucet` (built with a `bon` builder) configured by a `TokenPolicyManager`. Vault balance lookups now take an `AssetVaultKey` instead of an `AccountId`. +The asset model was renamed one level down. What was `AssetVaultKey` is now **`AssetId`**, and what was `AssetId` is now **`AssetClass`**. Because the name `AssetId` survives with a different meaning, a careless search-and-replace will compile and be wrong — do the `AssetId` → `AssetClass` rename first. Separately, the single `create_fungible_faucet` factory split into six auth-specific factories. +::: + +## Quick Fix + +```rust +// Before (0.15) +let key: AssetVaultKey = asset.vault_key(); +let key = AssetVaultKey::new_fungible(faucet_id, callback_flag); + +// After (0.16) +let id: AssetId = asset.id(); +let id = AssetId::new_fungible(faucet_id); // callback flag now lives on the AccountId +``` + +If you encounter errors, continue reading for detailed migration steps. + +--- + +## Summary + +The rename reflects a conceptual correction. The per-asset vault key is the thing that actually identifies an asset, so it took the name `AssetId`; the faucet-level identifier it used to share a name with describes a *class* of assets, so it became `AssetClass`. + +This is the most dangerous rename in the release precisely because it is not a removal. `AssetId` still exists after the upgrade, so code referring to it keeps compiling while silently meaning something different. Rename in the right order and let the compiler find the rest. + +:::note Several related types did not change +`AssetAmount`, `AssetComposition`, and `AssetCallbackFlag` all existed in 0.15 and are unchanged. `AssetVault::get_balance` already returned `AssetAmount` in 0.15 — only its parameter type was renamed. If you saw `AssetAmount` described as new, that applies to the [client surface](./client-changes), not the protocol. ::: --- -## `FungibleAsset::amount()` / `get_balance()` return `AssetAmount` +## `AssetVaultKey` → `AssetId`, and `AssetId` → `AssetClass` ### Summary -A new validated `AssetAmount` newtype wraps fungible amounts. `FungibleAsset::amount()` now returns `AssetAmount` (was `u64`), and `AssetVault::get_balance()` returns `Result` (was `Result`) **and takes an `AssetVaultKey` instead of an `AccountId`**. The vault key carries the asset's `AssetComposition`, so balance lookups are explicit about fungible‑vs‑non‑fungible. +The vault key type was renamed to `AssetId`, the previous `AssetId` became `AssetClass`, and `Asset::vault_key()` became `Asset::id()`. `AssetIdHash` is the corresponding hash type. ### Affected Code ```rust -// 0.15 — new API: -use miden_protocol::asset::{AssetAmount, AssetCallbackFlag, AssetVaultKey}; -let amt: AssetAmount = fungible_asset.amount(); -let raw: u64 = amt.as_u64(); // or: u64::from(amt) -let key = AssetVaultKey::new_fungible(faucet_id, AssetCallbackFlag::Disabled); -let bal: AssetAmount = vault.get_balance(key)?; +// Before (0.15) +let key: AssetVaultKey = asset.vault_key(); +let balance: AssetAmount = vault.get_balance(vault_key)?; +let key = AssetVaultKey::new_fungible(faucet_id, callback_flag); +``` + +```rust +// After (0.16) +let id: AssetId = asset.id(); +let balance: AssetAmount = vault.get_balance(asset_id)?; +let id = AssetId::new_fungible(faucet_id); +// or, fully explicit: +let id = AssetId::new(asset_class, faucet_id, composition); ``` -`AssetAmount` implements `From` and `TryFrom` (validating against the max fungible amount), plus `Add`/`Sub` and `Display`. + +Note that `AssetId::new_fungible` **no longer takes a callback flag**. Whether a faucet's assets trigger callbacks is encoded in the account ID itself, set at construction time via `AccountBuilder::with_asset_callbacks`. + +`Asset` itself is unchanged in shape — still an enum with `Fungible` and `NonFungible` variants — and `FungibleAsset::new(faucet_id, amount)` keeps its signature. ### Migration Steps -1. Wrap `u64` amounts you pass into faucet/asset constructors in `AssetAmount` (`AssetAmount::from(n)` for small ints, `AssetAmount::try_from(n)` for `u64`). -2. Unwrap `AssetAmount` back to `u64` with `.as_u64()` / `u64::from(_)` where a raw integer is needed. -3. Replace `vault.get_balance(faucet_id)` with `vault.get_balance(AssetVaultKey::new_fungible(faucet_id, callback_flag))`. -4. Update error handling from `AssetVaultError` to `AssetError` on `get_balance`. +1. Rename `AssetId` → `AssetClass` **first**, throughout your codebase. +2. Then rename `AssetVaultKey` → `AssetId`. +3. Replace `asset.vault_key()` with `asset.id()`. +4. Drop the callback-flag argument from `new_fungible` calls; set it on the account instead with `with_asset_callbacks`. +5. Re-index any persisted vault or asset data. Serialized asset identifiers are not compatible across the rename. + +### Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| `cannot find type AssetVaultKey` | Renamed | Use `AssetId`. | +| `no method named vault_key` | Renamed | Use `id()`. | +| `this function takes 1 argument but 2 were supplied` on `new_fungible` | Callback flag removed | Drop it; set `with_asset_callbacks` on the account. | +| Type mismatch where `AssetId` used to work | `AssetId` now means the vault key | The old meaning is `AssetClass`. | --- -## `FungibleFaucet` replaces `BasicFungibleFaucet` + `NetworkFungibleFaucet` +## Faucet factories split by authentication scheme ### Summary -The separate `BasicFungibleFaucet` and `NetworkFungibleFaucet` components were merged into a single **`FungibleFaucet`** component, and its old `FungibleFaucetBuilder` was replaced with a `bon`‑generated builder (`FungibleFaucet::builder()`). The constructor accepts a structured `TokenName` plus optional token‑metadata fields and an `AssetAmount` `max_supply`. A companion `FungibleTokenMetadata` component exposes the metadata via MASM getters. For the end‑to‑end client construction recipe (with `TokenPolicyManager`), see [(Rust) `FungibleFaucet` builder + `TokenPolicyManager`](./client-changes#rust-fungiblefaucet-builder--tokenpolicymanager-construction). +`create_fungible_faucet` took an `AuthMethod` and an `AccessControl` argument and dispatched internally. Since `AuthMethod` was removed (see [Account Changes](./account-changes#approver-and-approverset-replace-raw-key-arguments)), the factory split into one function per authentication scheme, each taking a concrete auth component. ### Affected Code ```rust -// 0.15 — new API: -use miden_protocol::asset::{AssetAmount, TokenSymbol}; -use miden_standards::account::faucets::{FungibleFaucet, TokenName}; -let faucet = FungibleFaucet::builder() - .name(TokenName::new("My Token")?) - .symbol(TokenSymbol::new("MTK")?) - .decimals(8) - .max_supply(AssetAmount::from(1_000_000u32)) - .build()?; +// Before (0.15) +pub fn create_fungible_faucet( + init_seed: [u8; 32], + faucet: FungibleFaucet, + account_type: AccountType, + auth_method: AuthMethod, + access_control: AccessControl, + token_policy_manager: TokenPolicyManager, +) -> Result ``` -### Migration Steps +```rust +// After (0.16) +pub fn create_singlesig_user_fungible_faucet( + init_seed: [u8; 32], + faucet: FungibleFaucet, + auth_component: AuthSingleSig, + token_policy_manager: TokenPolicyManager, + account_type: AccountType, +) -> Result +``` -1. Replace `BasicFungibleFaucet` / `NetworkFungibleFaucet` imports with `FungibleFaucet`. -2. Switch construction to `FungibleFaucet::builder()` with the required setters `name`, `symbol`, `decimals`, `max_supply`. -3. Convert `max_supply` from `Felt` to `AssetAmount`. +Note that the parameter **order** changed as well as the parameter list — `account_type` moved to the end. ---- +The full set of factories: -## `AssetComposition` and the `AssetVaultKey` composition byte +| Faucet kind | v0.16 factory | +| --- | --- | +| Fungible, single signature | `create_singlesig_user_fungible_faucet` | +| Fungible, multisig | `create_multisig_user_fungible_faucet` | +| Fungible, guarded multisig | `create_guarded_user_fungible_faucet` | +| Fungible, network account | `create_network_fungible_faucet` | +| Non-fungible, user account | `create_user_non_fungible_faucet` | +| Non-fungible, network account | `create_network_non_fungible_faucet` | -### Summary +Non-fungible faucet factories are new in this release; 0.15 shipped only the fungible factory. + +### Migration Steps -A new **`AssetComposition`** enum (`None`, `Fungible`, `Custom`) discriminates assets, and the asset vault key's metadata byte now encodes the composition (plus the asset‑callback flag). `AssetVaultKey::new(asset_id, faucet_id, composition, callback_flag)` is the general constructor; `AssetVaultKey::new_fungible(faucet_id, callback_flag)` is the fungible shortcut. (`Custom` composition is reserved and currently rejected.) +1. Choose the factory matching your authentication scheme and pass a concrete auth component instead of an `AuthMethod`. +2. Drop the `access_control` argument. The factories install `Authority::AuthControlled` and the pausable components for you. +3. Check the argument order — `account_type` is now last. +4. Expect a different account ID for a faucet rebuilt from the same seed, since the component set and names changed. -**What composition means:** it describes how two instances of the same asset combine in a vault — `None` (non‑fungible: instances never merge), `Fungible` (instances merge by summing amounts), and `Custom` (reserved for faucet‑defined logic; rejected at construction today). Because composition is carried in the key's metadata byte rather than derived from the faucet ID, the vault key is self‑describing. Read it back with `AssetVaultKey::composition()` and the callback flag with `AssetVaultKey::callback_flag()`. See the v0.15 asset encoding reference and composition reference for the full layout, and [MASM Changes](./masm-changes#asset-vault-key-composition) for the procedure‑level effects. +### Common Errors -### Migration Steps +| Error Message | Cause | Solution | +| --- | --- | --- | +| `cannot find function create_fungible_faucet` | Split into per-scheme factories | Use the matching factory from the table. | +| `cannot find type AuthMethod` | Removed | Pass a concrete auth component. | +| Arguments of the wrong type | Parameter order changed | `account_type` moved to the end. | + +--- + +## Common Errors -1. Where you constructed a raw vault key word, use `AssetVaultKey::new_fungible` / `AssetVaultKey::new`. -2. Branch on `AssetComposition` (via `AssetVaultKey::composition()`) instead of inspecting raw bits. +| Error Message | Cause | Solution | +| --- | --- | --- | +| `cannot find type AssetVaultKey` | Renamed to `AssetId` | Rename, after renaming old `AssetId` to `AssetClass`. | +| Silent behaviour change around asset identity | `AssetId` kept its name with a new meaning | Audit every `AssetId` reference. | +| Persisted vault lookups miss after upgrading | Asset identifier serialization changed | Re-index persisted vault data. | +| `cannot find function create_fungible_faucet` | Factories split | Use the auth-specific factory. | diff --git a/docs/builder/migration/06-transaction-changes.md b/docs/builder/migration/06-transaction-changes.md index 71e50988..40c32a1b 100644 --- a/docs/builder/migration/06-transaction-changes.md +++ b/docs/builder/migration/06-transaction-changes.md @@ -1,53 +1,314 @@ --- sidebar_position: 6 title: "Transaction Changes" -description: "FeeParameters fee faucet rename, typed transaction-script roots, and batch construction changes in v0.15" +description: "Fees move into the auth procedure, transaction inputs are sealed before submission, and TransactionSummary binds the reference block — so multi-party signing needs a ChainAnchor" --- # Transaction Changes :::warning Breaking Change -`FeeParameters::native_asset_id` was renamed to `fee_faucet_id` and `FeeParameters::new` is now infallible. The transaction-script root is now a typed `TransactionScriptRoot` rather than a raw `Word`. These changes affect anyone constructing fee parameters, reading transaction-script roots, or building batches by hand. +Transaction fees moved out of the kernel epilogue and into the **authentication procedure**. On a chain with a non-zero `verification_base_fee`, transactions signed by `AuthSingleSig` or `AuthMultisig` must commit fee conversion info through the transaction's auth args, and the paying account must hold the fee asset. Separately, transaction inputs are now **sealed (encrypted)** before submission, so a 0.16 client cannot submit to a 0.15 node or vice versa. ::: +## Quick Fix + +```rust +// After (0.16) — declare how the fee is paid +use miden_client::account::component::FeeConversionInfo; + +let info = FeeConversionInfo::one_to_one(fee_faucet_id); +let request = TransactionRequestBuilder::new() + .fee_conversion_info(info, salt) // salt: Word + .build()?; +``` + +If you encounter errors, continue reading for detailed migration steps. + +--- + +## Summary + +The fee change is the largest behavioural change in the release, and it is easy to under-estimate because it does not necessarily break your build. If you target a chain that charges no fee, nothing changes. If you target a chain that does, transactions that used to succeed now fail unless the request declares fee conversion info. + +The reason for the move is visible in the standard auth component's own documentation: paying the fee *before* the transaction summary is created means the fee note and the vault withdrawal funding it are covered by the signature. Under the old model the kernel deducted the fee outside anything the user signed. + +The sealing change is a hard compatibility boundary rather than an API change — it mostly costs you a coordinated upgrade rather than a code edit. + +The summary change is the quiet one. Its constructor break is mechanical, but the binding it introduces silently invalidates any flow that collects signatures on one client and executes on another; that code still compiles and only fails once it runs. + --- -## `fee_faucet_id` replaces `native_asset_id` on `FeeParameters` +## Transaction fees are paid by the auth procedure ### Summary -`FeeParameters::native_asset_id` (field, getter, and `new` parameter) was renamed to **`fee_faucet_id`**. Because account IDs no longer encode faucet‑ness, `FeeParameters::new` no longer validates that the ID is a fungible faucet and is now **infallible** (returns `Self`, not `Result`). +In 0.15 the kernel computed and burned the fee from the native account's vault automatically. In 0.16 the auth procedure reads a `FeeConversionInfo` blob out of the auth args, computes the fee, and emits a `TX_FEE` note to the fee faucet. + +The standard auth components already do this for you — `AuthSingleSig`'s MASM calls `fee::load_conversion_info` and `fee::pay_fee` before authenticating. What you must supply is the auth args. + +### Affected Code + +```rust +// Before (0.15) +// Nothing to declare: the kernel handled the fee. +let request = TransactionRequestBuilder::new().build()?; +let fee = executed_tx.fee(); +``` + +```rust +// After (0.16) +use miden_client::account::component::FeeConversionInfo; + +// Pay in the chain's native fee asset at rate 1/1: +let info = FeeConversionInfo::one_to_one(fee_faucet_id); +// or specify an explicit conversion rate: +let info = FeeConversionInfo::new(fee_faucet_id, rate_num, rate_den)?; + +let request = TransactionRequestBuilder::new() + .fee_conversion_info(info, salt) + .build()?; +``` + +The exact signatures: + +```rust +FeeConversionInfo::new(faucet_id: AccountId, rate_num: u64, rate_den: u64) -> Result +FeeConversionInfo::one_to_one(faucet_id: AccountId) -> Self +commit_fee_conversion_info(conversion_info: FeeConversionInfo, salt: Word) -> (Word, Vec) + +// on the client builder: +pub fn fee_conversion_info(self, conversion_info: FeeConversionInfo, salt: Word) -> Self +``` + +:::caution The `salt` argument is mandatory and undocumented upstream +`fee_conversion_info` takes a second `salt: Word` parameter that the changelog does not mention. Code written from the changelog alone will not compile. +::: + +`FeeConversionInfo` is reachable at `miden_client::account::component::FeeConversionInfo` — it is *not* exported from `miden_client::auth`, where you would naturally look first. Adding a direct `miden-standards` dependency also works. + +### When it is required, and when it is rejected + +Only auth components that actually read the auth args honour the conversion info. The client validates this **before execution** rather than silently paying in the native asset: + +```rust +// miden_client::transaction::TransactionRequestError +FeeConversionInfoUnsupported(String) +// "the request declares fee conversion info but the account's auth component {0} does not read it" +``` + +The check passes only for `AuthSingleSig` and `AuthMultisig`. Declaring fee conversion info for any other auth component — `NoAuth`, for example — is rejected. If the request does not declare fee conversion info at all, the check is skipped, so existing code that never calls the builder method is unaffected by this validation. + +Because `fee_conversion_info` consumes the auth arg, it **conflicts with a manually set `auth_arg`** — whichever is applied last wins. + +### Migration Steps + +1. On a chain with a non-zero `verification_base_fee`, call `fee_conversion_info(info, salt)` on every request signed by an `AuthSingleSig` or `AuthMultisig` account. Use `FeeConversionInfo::one_to_one(fee_faucet_id)` for the native fee asset. +2. Ensure the paying account holds a balance of the fee asset — the auth procedure debits it. +3. Do **not** call `fee_conversion_info` for accounts using any other auth component. +4. If you set `auth_arg` manually, pick one or the other. +5. Replace `executed_tx.fee()` with inspection of the `TX_FEE` output note. `ExecutedTransaction::compute_fee()` still exists but only under the `testing` feature — do not use it in production. +6. If you wrote a **custom auth component** in MASM, it must now call `miden::standards::fee::load_conversion_info` followed by `miden::standards::fee::pay_fee`, or the transaction will fail fee validation. +7. On a zero-fee chain, no change is required. + +### Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| `this function takes 2 arguments but 1 was supplied` | The `salt` parameter | Pass a `Word` salt. | +| `FeeConversionInfoUnsupported` | Auth component does not read auth args | Only declare it for `AuthSingleSig` / `AuthMultisig`. | +| `cannot find FeeConversionInfo in miden_client::auth` | Exported elsewhere | Use `miden_client::account::component::FeeConversionInfo`. | +| Transaction aborts on a fee-charging chain | No fee conversion info declared, or no fee asset balance | Declare the info and fund the account. | +| Custom auth component transaction fails fee validation | MASM does not pay the fee | Call `fee::load_conversion_info` then `fee::pay_fee`. | + +--- + +## Transaction inputs are sealed before submission + +### Summary + +Transaction inputs are encrypted ("sealed") before being submitted. The RPC layer gained a `get_transaction_encryption_key` method plus a `miden_client::rpc::encryption` module. + +**This is a hard compatibility boundary.** A 0.16 node rejects plaintext submissions and an older node rejects sealed ones, so the client and node must be upgraded together. + +:::note Most applications do not change any code here +`Client::submit_proven_transaction` keeps its 0.15 signature exactly — it still takes `impl Into`, and sealing happens beneath it. Only the `NodeRpcClient` **trait** methods changed to take `SealedTransactionInputs`, so this is a source-breaking change solely for code that implements that trait. +::: + +The requirement this does impose on every application is a **sync before submitting**: sealing resolves an encryption key against the chain state, so a client that has not synced the genesis and chain-tip headers fails with `ClientError::ChainValidationError`. ### Migration Steps -1. Rename `native_asset_id` → `fee_faucet_id` at the constructor, field, and getter. -2. Drop the `?` / `FeeError` handling on `FeeParameters::new`. +1. Upgrade your node and client together. There is no configuration that makes a 0.16 client talk to a 0.15 node. +2. Ensure the client has synced before submitting, or key resolution fails with `ChainValidationError`. +3. If you implement `NodeRpcClient` yourself, update `submit_proven_transaction` and `submit_proven_batch` to take `SealedTransactionInputs`, and add `get_transaction_encryption_key`. + +:::note Key types are not re-exported from the encryption module +The changelog states that `miden_client::rpc::encryption` re-exports the validator DSA key types. It does not — that module contains no `pub use` statements. Reach them via `miden_client::crypto::{ecdsa_k256_keccak, eddsa_25519_sha512}`. +::: --- -## `TransactionScript::root()` returns `TransactionScriptRoot` +## `TransactionSummary` binds the reference block, expiration, and user params ### Summary -*(v0.15.2)* `TransactionScript::root()` now returns a typed **`TransactionScriptRoot`** newtype instead of a raw `Word` (convert with `.into()`). A new `TransactionScript::from_package(&Package)` builds a script from a compiled `miden-mast-package` package, and the new `tx::get_tx_script_root` kernel proc returns the executed tx‑script root (empty word if none). +`TransactionSummary::new` replaced its single `salt` parameter with a block commitment, an expiration delta, and a structured user-parameters value. + +### Affected Code + +```rust +// Before (0.15) +pub fn new( + account_delta: AccountDelta, + input_notes: InputNotes, + output_notes: RawOutputNotes, + salt: Word, +) -> Self +``` + +```rust +// After (0.16) +pub fn new( + account_delta: AccountDelta, + input_notes: InputNotes, + output_notes: RawOutputNotes, + block_commitment: Word, + expiration_delta: u16, + user_params: TransactionSummaryUserParams, +) -> Self +``` + +`TransactionSummaryUserParams` carries seven field elements. On the TypeScript side the corresponding accessor renamed from `TransactionSummary.salt()` to `TransactionSummary.userParams()`. + +The reference block commitment is included because it determines the fee parameters, and therefore the fee amount deducted; the expiration delta is included so the signature covers it. + +:::info `TransactionSummary` still uses `AccountDelta` +Note the first parameter. While account *updates* moved to the absolute `AccountPatch` model (see [Account Changes](./account-changes#account-updates-move-from-accountdelta-to-accountpatch)), the signed transaction summary deliberately still binds a relative `AccountDelta`. Do not rewrite these call sites. +::: ### Migration Steps -1. Update the binding type of `tx_script.root()` to `TransactionScriptRoot`. -2. Insert `.into()` / `Word::from(root)` where a `Word` is required. +1. Replace the `salt` argument with the block commitment, expiration delta, and user params. +2. In TypeScript, replace `summary.salt()` with `summary.userParams()`. --- -## `ProvenBatch::new` → `new_unchecked`; `NoteConsumptionInfo` fields private +## Collecting signatures across clients requires a `ChainAnchor` ### Summary -Batch/transaction housekeeping changes that affect anyone constructing these types by hand: +The binding above has a consequence that never surfaces as a compile error. Because the summary commits to the reference block, a summary derived at one block only authorizes an execution at *that* block. Clients execute at their own sync height by default, so in any flow that derives a summary, collects signatures, and executes later — a multisig proposal, offline co-signing — the proposer, each co-signer, and the executor sit at different heights and derive three different summaries. The collected signatures do not apply. + +Code written for 0.15 compiles unchanged and fails at run time, which makes this the easiest change in the release to miss. + +`ChainAnchor` is the remedy. It pins execution to a chosen reference block, so the same summary reproduces on a client at any height: the proposer captures one, ships it alongside the summary, and every party executes against it. + +:::note Newer than the versions pinned in Quick Upgrade +`ChainAnchor` landed after the versions in [Quick Upgrade](./#quick-upgrade). It needs `miden-client` `0.16.0-rc.2` and `@miden-sdk/miden-sdk` / `@miden-sdk/react` `0.16.0-rc.3` or later. +::: + +### Affected Code + +```rust +// Before (0.15) +// The proposer and the executor each ran this at their own sync height, +// and the summary the co-signers signed still reproduced. +let result = client.execute_transaction(account_id, request).await?; +``` + +```rust +// After (0.16) +use miden_client::transaction::ChainAnchor; +use miden_client::{Deserializable, Serializable}; + +// Proposer: pin the reference block, and ship the anchor with the summary. +let anchor = client.chain_anchor_for_request(&request).await?; +let bytes = anchor.to_bytes(); + +// Co-signer and executor: rebuild it and execute at the same block. +let anchor = ChainAnchor::read_from_bytes(&bytes)?; +let result = client.execute_transaction_at(account_id, request, anchor).await?; +``` + +```typescript +// After (0.16) — Web SDK +const anchor = await client.transactions.captureAnchor(request); -- `ProvenBatch::new` was renamed **`ProvenBatch::new_unchecked`** to signal it skips validation. -- `NoteConsumptionInfo` (and related types) gained cycle counts and had their fields made private; use the accessor methods `successful()` / `failed()`. +const summary = await client.transactions.preview({ + operation: "custom", account, request, anchor, +}); +// ... collect signatures over `summary`, shipping `anchor.serialize()` ... + +await client.transactions.submit(account, request, { anchor }); +``` + +```tsx +// After (0.16) — React +const { captureAnchor, anchoredRequest } = useChainAnchor(); +const { preview } = usePreview(); +const { execute } = useTransaction(); + +const anchor = await captureAnchor({ request: buildRequest }); +const summary = await preview({ accountId, request: anchoredRequest, anchor }); +// ... collect signatures ... +await execute({ accountId, request: anchoredRequest, anchor }); +``` + +Single-client flows need no anchor. `execute_transaction` and an `anchor`-less `submit` keep their 0.15 behaviour of executing at the current sync height, which is correct whenever the client deriving the summary is also the one executing. + +### Verify an anchor you did not capture + +An anchor arrives from whoever proposed the transaction. Deserialization rejects malformed bytes, so the remaining risk is a well-formed anchor pinned to the wrong block. Compare its commitment against the one signed into the summary before executing: + +```typescript +if (anchor.commitment().toHex() !== summary.blockCommitment().toHex()) { + throw new Error("anchor does not match the signed summary"); +} +``` + +The Rust accessor is `ChainAnchor::block_commitment`. Note what this check does and does not prove: it establishes that the anchor and the summary agree, not that either is what you meant to sign, since the proposer supplies both. Inspect the summary's effects separately. ### Migration Steps -1. Rename `ProvenBatch::new` → `ProvenBatch::new_unchecked`. -2. Replace direct field access on `NoteConsumptionInfo` with the accessor methods. +1. Find every flow that derives a transaction summary on one client and executes on another — multisig proposals and offline co-signing are the common cases. Single-client flows are unaffected. +2. Capture an anchor at proposal time with `chain_anchor_for_request` (Rust), `transactions.captureAnchor` (Web), or `useChainAnchor` (React), and ship the serialized bytes alongside the summary. +3. Re-derive the summary at the anchor when verifying a proposal, rather than at the local sync height: `executeForSummaryAt` on the WASM client, or `preview({ …, anchor })` at the ergonomic layers. Deriving it locally produces a different summary and the comparison always fails. +4. Execute at the anchor: `execute_transaction_at`, or the `anchor` option on `executeRequest` / `submit` / `useTransaction().execute`. +5. Check a received anchor against `summary.blockCommitment()` before using it. +6. In React, preview and execute against the `anchoredRequest` the hook returns, not the value you passed in. Re-resolving a request factory builds a different transaction — anything creating an output note draws a fresh serial number — so the anchor would pin a request nobody executes. +7. Budget for expiry. The expiration delta counts from the anchored block, not from the executing client's height, so an anchor that sat too long during signature collection fails with `AnchoredTransactionExpired`. + +:::caution Rust consumes the anchor; JavaScript does not +`execute_transaction_at` takes the `ChainAnchor` by value, so capture it again — or clone it — for a preview-then-execute sequence. The wasm bindings take it by reference, so one JS handle survives both calls. Because a JS anchor carries a partial blockchain, call `anchor.free()` when done rather than leaving it to the finalizer. +::: + +### Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| Collected signatures are rejected, with no compile error | Each party derived the summary at its own sync height | Capture an anchor and derive and execute at it. | +| `TRANSACTION_ALREADY_AUTHORIZED` | The transaction needs no further signatures | Submit it with `execute` / `submit` instead of previewing. | +| `AnchoredTransactionExpired` | The anchor aged past the transaction's expiration during signature collection | Re-capture the anchor and re-collect. | +| `ChainAnchorError` on execution | An authenticated input note's creation block is not tracked by the anchor | Capture the anchor with `chain_anchor_for_request`, which tracks those blocks. | +| `INVALID_CHAIN_ANCHOR` | A sync landed mid-capture | Retry the capture. | +| `captureAnchor is not a function` | Web SDK older than `0.16.0-rc.3` | Bump `@miden-sdk/miden-sdk` and `@miden-sdk/react` together. | + +--- + +## Other transaction changes + +- **Proving is synchronous; execution stays asynchronous.** Adjust any code that awaited the proving step. +- **`ExecutedTransaction::account_delta()` became `account_patch()`**, matching the account update model. See [Account Changes](./account-changes#account-updates-move-from-accountdelta-to-accountpatch). +- **`ExecutedTransaction::compute_fee()` is gated behind the `testing` feature.** Production fee figures come from the `TX_FEE` note. + +--- + +## Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| Node rejects a submitted transaction | Client and node versions mixed | Upgrade both to 0.16. | +| `this function takes 6 arguments but 4 were supplied` | `TransactionSummary::new` changed | Pass block commitment, expiration delta, and user params. | +| `no method named salt` on a summary | Renamed | Use `userParams()`. | +| `no method named fee` on an executed transaction | Fees now flow through the `TX_FEE` note | Inspect the output note. | +| `no method named account_delta` on an executed transaction | Renamed | Use `account_patch()`. | diff --git a/docs/builder/migration/07-client-changes.md b/docs/builder/migration/07-client-changes.md index 2db81422..07e96f98 100644 --- a/docs/builder/migration/07-client-changes.md +++ b/docs/builder/migration/07-client-changes.md @@ -1,496 +1,346 @@ --- sidebar_position: 7 title: "Client Changes" -description: "Client RPC rebuilt around GetAccount, removed check_nullifiers, required block_to, and Web/React/CLI surface changes in v0.15" +description: "Rust, Web, React and CLI client changes, plus the mandatory local store recreation" --- # Client Changes -:::warning Breaking Change -v0.15 rebuilds the client RPC surface around the node's `GetAccount` endpoint: `get_account_proof` / `get_account_details` are reshaped, `check_nullifiers` is **removed** (use `sync_nullifiers`), and most sync methods now require an explicit `block_to`. The Web SDK moved to the 0.15 protocol surface — there is no more `"network"` storage mode, the WASM `AccountType` narrowed to `{ Private, Public }`, attachments are word‑vector‑shaped, `Felt` / `Word` throw on overflow, several methods return `undefined` / `string` instead of throwing or returning objects, the raw client's `proveTransactionWithProver` is renamed `proveTransaction`, and `storeIdentifier()` went async. Review every sub‑section below if you maintain Rust, Web, React, or CLI client code. +:::danger Your local store must be recreated +Every pre-0.16 SQLite store is rejected. There is no migration path: delete the database and re-sync. Browser applications are handled automatically — the IndexedDB store detects the version bump and wipes itself on first open. In both cases **any state that existed only locally is lost**, including records for accounts not yet committed on-chain. ::: -Sub‑sections are grouped by surface — **Rust client**, **Web SDK**, **React SDK**, and **CLI**. Each change keeps its own `##` heading prefixed with `(Rust)` / `(Web)` / `(React)` / `(CLI)`. - ---- - -## (Rust) `NodeRpcClient` `GetAccount` surface reshaped +:::warning Client and node must be upgraded together +0.16 clients seal (encrypt) transaction inputs before submission. A 0.16 node rejects plaintext submissions and an older node rejects sealed ones, so the two cannot be mixed. Upgrade both. +::: -### Summary -The account‑fetching surface on the `NodeRpcClient` trait was rebuilt around the node's `/GetAccount` endpoint: -- `get_account_proof(account_id, storage_requirements, account_state, known_account_code, known_vault_commitment)` was **replaced** by `get_account(account_id, request: GetAccountRequest)`, where `GetAccountRequest` bundles the previous positional args behind a builder. -- `get_account_details` no longer returns a `FetchedAccount` enum — it returns `Result, RpcError>` (`None` for accounts without public state), fetching all of a public account's storage maps and vault in a single round‑trip. It no longer returns anything for private accounts; use `get_account` for a private account's commitment. -- New default helpers `resolve_oversize_vault` / `resolve_oversize_storage_maps` fill in vault/map entries the node flagged as oversize. +## Quick Fix -`GetAccountRequest`, `StorageMapFetch`, `VaultFetch`, and `AccountStateAt` live under `miden_client::rpc::domain`. +```bash +# CLI: the send subcommand was renamed +miden-client transfer -t -a 100:: -n private +``` -### Affected Code ```rust -// 0.15 — new API: -use miden_client::rpc::domain::account::{GetAccountRequest, StorageMapFetch, VaultFetch}; -let (block, proof) = rpc_api - .get_account(account_id, GetAccountRequest::new() - .with_storage(StorageMapFetch::All) - .with_vault(VaultFetch::Always) - .with_known_code(Some(known_code))) - .await?; -let account: Option = rpc_api.get_account_details(account_id).await?; +// Rust: account updates are absolute patches now +let patch = tx_result.account_patch(); ``` -### Migration Steps -1. Replace `get_account_proof(...)` calls with `get_account(account_id, GetAccountRequest::new()....)`; move each positional arg onto the corresponding builder method. -2. Replace `match FetchedAccount { Public | Private }` on `get_account_details` with `Option` handling; route private‑account commitment lookups through `get_account`. -3. The default `get_account_details` now calls `resolve_oversize_vault` / `resolve_oversize_storage_maps` for you. - ---- - -## (Rust) `NodeRpcClient`: `SyncTarget`, `check_nullifiers` removed, required `block_to` - -### Summary -Several `NodeRpcClient` methods changed to match the `0.15` RPC definitions: -- `sync_chain_mmr`'s `block_to: Option` became `upper_bound: SyncTarget`. `SyncTarget` has exactly two variants: use `SyncTarget::CommittedChainTip` for the old `None` behavior, or `SyncTarget::ProvenChainTip` for the latest proven block. There is no explicit block-height target. -- `check_nullifiers` (and `RpcEndpoint::CheckNullifiers`, `EndpointError::CheckNullifiers`, `CheckNullifiersError`) were **removed**. Use `sync_nullifiers` to retrieve nullifier updates. -- `sync_nullifiers`, `sync_notes`, `sync_notes_with_details`, `sync_storage_maps`, and `sync_account_vault` lost their `Option` upper bound in favor of a required `block_to: BlockNumber`. -- `get_block_by_number` gained an `include_proof: bool` parameter. -- `submit_proven_batch` is a new required trait method. - -`SyncTarget` lives at `miden_client::rpc::domain::sync::SyncTarget`. - -### Affected Code -```rust -// 0.15 — new API: -use miden_client::rpc::domain::sync::SyncTarget; -let mmr = rpc_api.sync_chain_mmr(block_from, SyncTarget::CommittedChainTip).await?; -let updates = rpc_api.sync_nullifiers(&prefixes, block_from, chain_tip).await?; // check_nullifiers is gone -let block = rpc_api.get_block_by_number(block_num, /* include_proof */ false).await?; +```typescript +// Web: same split on the TypeScript side +const patch = txResult.accountPatch(); ``` -### Migration Steps -1. Replace `sync_chain_mmr(_, None)` with `sync_chain_mmr(_, SyncTarget::CommittedChainTip)`. v0.15 dropped explicit-height targeting, so map any explicit `Some(n)` to `SyncTarget::CommittedChainTip` as well (or `SyncTarget::ProvenChainTip` if you need the latest proven block). -2. Replace `check_nullifiers` with `sync_nullifiers` and adapt to `Vec` (drop the `SmtProof` path). -3. Pass an explicit `block_to` (e.g. the client's current sync height / chain tip) to `sync_nullifiers`, `sync_notes`, `sync_storage_maps`, `sync_account_vault`. -4. Add `include_proof` to `get_block_by_number` calls (`false` unless you need the block proof). -5. If you implement `NodeRpcClient` yourself, add `submit_proven_batch`. +If you encounter errors, continue reading for detailed migration steps. --- -## (Rust) Note‑import APIs return `Vec` +## Summary -### Summary -`Client::import_notes`, `Client::sync_note_transport`, and the `SyncSummary::new_private_notes` field now yield `Vec` instead of `Vec`. A metadata‑less import has no `NoteId` yet, so the client identifies such notes by their metadata‑independent details commitment. Resolve a commitment back to a record with `Client::get_input_notes(NoteFilter::DetailsCommitments(vec![..]))`. - -### Affected Code -```rust -// 0.15 — new API: -use miden_client::store::NoteFilter; -use miden_protocol::note::NoteDetailsCommitment; -let imported: Vec = client.import_notes(¬e_files).await?; -let private: Vec = sync_summary.new_private_notes; -// Resolve commitments to the stored records (works even before metadata is known): -let records = client.get_input_notes(NoteFilter::DetailsCommitments(imported)).await?; -``` - -### Migration Steps -1. Change the bound type of `import_notes` / `sync_note_transport` results and the `SyncSummary::new_private_notes` field from `NoteId` to `NoteDetailsCommitment`. -2. Where you used a returned `NoteId` to look a note up, switch to `NoteFilter::DetailsCommitments(..)` against `get_input_notes`. +The client changes fall into four groups. The **store break** is the one that costs users data, and it is unavoidable. The **fee and sealing changes** are covered in [Transaction Changes](./transaction-changes) — they surface here as a new builder method and a node-version requirement. The **rename churn** (`account_delta` → `account_patch`, `send` → `transfer`, and friends) is mechanical. And a handful of **silent behavioural changes** — the `call` argument counting, the transaction summary display, `notes.sendPrivate` requiring a scan height — will not fail your build but will change what your application does. --- -## (Rust) `FungibleFaucet` builder + `TokenPolicyManager` construction +## (Store) Every pre-0.16 SQLite store must be recreated ### Summary -The fungible‑faucet construction story was redesigned end to end. The client previously re‑exported `BasicFungibleFaucet` plus the `MintAuthControlled` / `MintOwnerControlled` / `BurnAuthControlled` / `BurnOwnerControlled` policy components. In `0.15`: -- The faucet component is `FungibleFaucet`, built via `FungibleFaucet::builder()`. -- Mint/burn policy is configured by installing a single `TokenPolicyManager` (`.with_mint_policy(MintPolicyConfig, PolicyRegistration).with_burn_policy(...)`), with standalone `MintAllowAll` / `MintOwnerOnly` / `BurnAllowAll` / `BurnOwnerOnly` policy components. The old `Mint*Controlled` / `Burn*Controlled` types were removed. -These are re‑exported from `miden_client::account`. +The store schema changed substantially: account SMT forest tables were added, account IDs and all digest columns were retyped from hex `TEXT` to `BLOB`, a `script_root` index was added, and the `migrations` table was dropped in favour of a schema fingerprint. ### Affected Code -```rust -// 0.15 — new API: -use miden_client::account::{ - AccountBuilder, AccountType, FungibleFaucet, TokenName, TokenPolicyManager, - MintPolicyConfig, BurnPolicyConfig, PolicyRegistration, -}; -use miden_protocol::asset::AssetAmount; -let faucet = FungibleFaucet::builder() - .name(TokenName::new(&symbol.to_string())?).symbol(symbol).decimals(10) - .max_supply(AssetAmount::new(max_supply)?).build()?; -let policy_manager = TokenPolicyManager::new() - .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?; -let account = AccountBuilder::new(init_seed) - .account_type(account_visibility) // AccountType::Public / ::Private - .with_auth_component(auth_component) - .with_component(faucet) - .with_components(policy_manager) - .build_with_schema_commitment()?; -``` -### Migration Steps -1. Replace `BasicFungibleFaucet::new(symbol, decimals, max_supply)` with `FungibleFaucet::builder()....build()`, wrapping `max_supply` in `AssetAmount::new(..)`. -2. Replace the standalone mint‑policy component with a `TokenPolicyManager` configured via `with_mint_policy` / `with_burn_policy`, installed with `.with_components(policy_manager)`. -3. Drop `Mint*Controlled` / `Burn*Controlled` imports. +A store written by miden-client 0.15.5 fails to open with: ---- +```text +Migration error: Attempt to migrate a database with a migration number that is too high +``` -## (Rust) `InputNoteRecord::new` takes `NoteAttachments`; store `attachments` column +:::note This is not the error the changelog names +The changelog says opening a pre-0.16 database fails with `SchemaHashMismatch`. In practice a 0.15.5 store sits at `user_version = 2`, and because 0.16 defines only one migration the fingerprint check is skipped entirely — the failure surfaces from the migration layer instead. `SchemaHashMismatch` is only reached by a store at `user_version = 1`. Both paths fail; only the message differs. +::: -### Summary -`InputNoteRecord::new` gained a second positional parameter, `attachments: NoteAttachments`, so input notes persist their attachment content. The SQLite store's `input_notes` table gained an `attachments` column. `NoteAttachments` is re‑exported from `miden_client::note`. +Beyond the account ID retyping, the schema diff also shows the `latest_account_assets` and `historical_account_assets` column `vault_key` renamed to `asset_id` (following the [protocol rename](./asset-vault-faucet)), a new unique index on `tags(tag, source)`, and *all* digest columns retyped to `BLOB` — `account_commitment`, `note_id`, `nullifier`, `script_root`, `recipient_digest`, and storage keys and values. ### Migration Steps -1. Thread a `NoteAttachments` (or `NoteAttachments::default()`) into every `InputNoteRecord::new` call as the second argument. -2. If you use a custom `Store`, add an `attachments` column to your input‑notes table and persist/load it. - ---- -## (Rust) `sync_notes` / `sync_transactions` return updates directly - -### Summary -`sync_notes` and `sync_transactions` now return the fetched updates directly. The wrapper structs `NoteSyncInfo` and `TransactionsInfo` were removed: `sync_notes` returns `Vec` and `sync_transactions` returns `Vec`. - -### Migration Steps -1. Drop the `.blocks` / `.transactions` field access — the methods return the collections directly. -2. Source the chain tip separately (e.g. `get_block_header_by_number(None, false)`) where you previously read it off `NoteSyncInfo`. +1. Delete the store database and let the client recreate it, then re-sync. +2. Export anything you need to keep **before** upgrading — private note files in particular. +3. Browser applications need no action; the IndexedDB store resets itself when the client's minor version increases. +4. If you implement a custom `Store`, note that `insert_block_header` now takes a `nodes` argument, `insert_partial_blockchain_nodes` was removed, and the new `NoteFilter::ScriptRoots` variant makes existing exhaustive matches fail to compile. --- -## (Rust) `get_note_script_by_root` returns `Option` +## (Rust) Account updates use `AccountPatch` -### Summary -`NodeRpcClient::get_note_script_by_root` no longer errors when the node has no script registered for the requested root — it returns `Ok(None)`. Implementations must still verify a returned script's root matches the request. +`TransactionResult::account_delta()` became `account_patch()`, and `Account::apply_delta` was replaced by construction from a patch. `TransactionSummary::account_delta()` is deliberately unchanged. This is covered in full under [Account Changes](./account-changes#account-updates-move-from-accountdelta-to-accountpatch). -### Migration Steps -1. Handle the `Option` — `None` means "no script for this root", which previously surfaced as an error. +One import detail specific to the client: in 0.15 `AccountStorageDelta` lived in `miden_client::asset`; the 0.16 replacement `AccountStoragePatch` lives in `miden_client::account`. The module moved as well as the name. `StorageMapDelta` and `StorageSlotDelta` were dropped from `miden_client::asset` alongside it, while `AccountVaultDelta` remains there. --- -## (Rust) `miden_client::note` re‑exports realigned - -### Summary -The protocol split attachment data off `NoteMetadata`, and the client's `note` re‑exports follow: -- **Removed:** `NoteAttachmentKind`, `NoteMetadataHeader`. -- **Added:** `NoteAttachmentHeader`, `NoteAttachments`, `PartialNoteMetadata`. -- `NoteScript::root()` now returns `NoteScriptRoot` (re‑exported from `miden_client::note`) instead of `Word`. +## (Rust) Fee conversion info on the transaction request -### Migration Steps -1. Replace `NoteAttachmentKind` / `NoteMetadataHeader` imports with `NoteAttachmentHeader` / `NoteAttachments` / `PartialNoteMetadata` as needed. -2. Where `note_script.root()` was used as a `Word`, insert a `.into()` / `Word::from(..)` conversion. +`TransactionRequestBuilder::fee_conversion_info(info, salt)` is new and required on fee-charging chains for `AuthSingleSig` and `AuthMultisig` accounts. See [Transaction Changes](./transaction-changes#transaction-fees-are-paid-by-the-auth-procedure) for the full flow, including the mandatory `salt` argument that the changelog omits. --- -## (Rust) `CommittedNoteMetadata` removed +## (Rust) Chain-anchored execution for multi-party signing -### Summary -The `CommittedNoteMetadata` enum (with `Full(NoteMetadata)` and a header‑only `Header { sender, note_type, tag, attachment_kind }` variant) was removed. Sync responses now always carry full metadata, so `CommittedNote::metadata()` returns `&NoteMetadata` directly — no `Option`, no header‑only case. +`Client::chain_anchor_for_request` and `Client::execute_transaction_at` are new, and they are not optional for any flow that derives a transaction summary on one client and executes it on another. Since the summary now binds the reference block, the parties must agree on that block or the collected signatures do not apply — see [Transaction Changes](./transaction-changes#collecting-signatures-across-clients-requires-a-chainanchor) for the full flow. -### Migration Steps -1. Delete the `CommittedNoteMetadata` match — read `NoteMetadata` directly off the committed note. +Two details specific to the client: `ClientError` gained a `ChainAnchorError` variant, so an exhaustive match over it no longer compiles; and both methods arrived in `0.16.0-rc.2`, one release after the version pinned in [Quick Upgrade](./#quick-upgrade). --- -## (Rust) `build_wallet_id` signature +## (Rust) Fungible amounts use `AssetAmount` ### Summary -`build_wallet_id` dropped its trailing `is_mutable: bool` (code mutability isn't encoded in the account ID) and its `storage_mode: AccountStorageMode` parameter was replaced by `account_visibility: AccountType`. -### Migration Steps -1. Drop the `is_mutable` argument. -2. Replace the `AccountStorageMode` argument with the corresponding `AccountType` visibility. - ---- - -## (Rust) `compile_note_script` expects a `@note_script` library - -### Summary -`client.code_builder().compile_note_script(src)` now expects a MASM **library** with a single procedure annotated `@note_script` instead of a bare `begin … end` program (the underlying assembly switched from `assemble_program` to `assemble_library`). +The client surface switched from raw `u64` to `AssetAmount` for fungible amounts. `AccountReader::get_balance` returns `AssetAmount`, and the token conversion helpers (`tokens_to_base_units`, `base_units_to_tokens`) and `build_pswap_consume` follow. ### Migration Steps -1. Wrap each note‑script body in `@note_script` + `pub proc main … end`; remove the `begin … end` framing. ---- - -## (Web) `AccountStorageMode.network()` removed - -### Summary -The `0.15` chain has no separate network‑account flag. `AccountStorageMode.network()` was removed, and the `StorageMode` string union dropped `"network"` (it is now `"public" | "private"`). Anywhere you constructed a network‑mode account — `AccountStorageMode.network()`, `StorageMode.Network`, or `accounts.create({ storage: "network" })` — must switch to `"public"` or `"private"`. `AccountStorageMode.tryFromStr("network")` now rejects. - -### Affected Code -```typescript -// 0.15 — new API: -const mode = AccountStorageMode.public(); // or .private() -await client.accounts.create({ storage: "public" }); // "public" | "private" -``` - -### Migration Steps -1. Replace every `AccountStorageMode.network()` with `.public()` or `.private()`. -2. Replace `StorageMode.Network` / the `"network"` string with `"public"` or `"private"`. -3. Audit `accounts.create({ storage })` and any `tryFromStr` call sites for `"network"`. +1. Wrap raw amounts with `AssetAmount`, or unwrap with the provided accessor where you need a `u64`. +2. Handle `TokenParseError::InvalidAmount` where you parse user-supplied amounts. --- -## (Web) `AccountType` narrowed; faucet checks move to `Account` +## (Rust) Auth and faucet re-exports changed ### Summary -The on‑chain `AccountType` no longer encodes faucet‑vs‑regular or updatable‑vs‑immutable. The WASM‑exported `AccountType` enum narrowed from `{ FungibleFaucet, NonFungibleFaucet, RegularAccountImmutableCode, RegularAccountUpdatableCode }` to `{ Private, Public }`. As a result: -- `AccountId.isFaucet()`, `AccountId.isNetwork()`, and `AccountId.isRegularAccount()` were **removed** (only `isPublic()` / `isPrivate()` remain). -- Faucet / regular detection now lives on `Account`: `Account.isFaucet()` / `Account.isRegularAccount()`. -- `Account.isNetwork()` and `Account.isUpdatable()` were **removed** outright. -> This is the low‑level WASM `AccountType` enum. The high‑level resource‑API `AccountType` constant used by `accounts.create({ type: "FungibleFaucet" | ... })` is unchanged. - -### Affected Code -```typescript -// 0.15 — new API: -if (account.isFaucet()) {} // moved onto Account -if (account.isRegularAccount()) {} -if (account.id().isPublic()) {} // isPublic()/isPrivate() still on both -// account.isNetwork() / account.isUpdatable() have no replacement — drop them. -``` - -### Migration Steps -1. Move `accountId.isFaucet()` / `.isRegularAccount()` onto the materialised `Account`. You need the full `Account`, not just its `AccountId`. -2. Remove `accountId.isNetwork()`, `account.isNetwork()`, and `account.isUpdatable()` — they have no replacement. -3. Replace any `switch` on the WASM `AccountType` enum's faucet/regular variants with the `Account` predicates; the enum only carries `Private` / `Public` now. - ---- +`AuthMethod` and `AuthSingleSigAcl` were removed, and the single fungible faucet factory split into auth-specific factories — see [Assets, Vault & Faucet Changes](./asset-vault-faucet#faucet-factories-split-by-authentication-scheme). Note that the client re-exports only two of the six upstream factory functions; for the rest, depend on `miden-standards` directly. -## (Web) `NoteAttachment` reshaped to word‑vector content +The account policy components were also renamed, a change absent from the changelog and found by diffing the re-export lists: -### Summary -Attachments are now always word‑vector‑shaped. The `NoteAttachmentKind { Word, Array }` dispatch and the per‑variant accessors/constructors `NoteAttachment.newWord` / `.newArray` / `.asWord` / `.asArray`, the `attachmentKind` getter, and the `NoteMetadata.attachment()` getter were all **removed**. Build attachments with `NoteAttachment.fromWord(scheme, word)` or `NoteAttachment.fromWords(scheme, words)`, and read them back with `NoteAttachment.toWords()`. The attachment words now live on the note record (`InputNoteRecord.attachments()`), not on `NoteMetadata`. `NoteAttachmentScheme` is now u16‑backed: its constructor throws if the value exceeds the u16 range, and `NoteAttachmentScheme.asU32()` was removed. - -### Affected Code -```typescript -// 0.15 — new API: -const att = NoteAttachment.fromWord(scheme, word); // single word -const attMulti = NoteAttachment.fromWords(scheme, [w0, w1]); -const words = att.toWords(); // Word[] — inverse of fromWord/fromWords -// NoteAttachmentKind, asWord, asArray, attachmentKind, scheme.asU32(), -// and NoteMetadata.attachment() no longer exist. +```diff +- AllowlistOwnerControlled ++ AllowlistManager +- BlocklistOwnerControlled ++ BlocklistManager ``` -### Migration Steps -1. Replace `NoteAttachment.newWord(scheme, word)` with `NoteAttachment.fromWord(scheme, word)` and `newArray(scheme, felts)` with `fromWords(scheme, words)`. -2. Replace `att.asWord()` / `att.asArray()` / `att.attachmentKind()` reads with `att.toWords()` and decode the `Word[]` yourself. -3. Replace `noteMetadata.attachment()` with `inputNoteRecord.attachments()`. -4. Drop `NoteAttachmentScheme.asU32()`; the scheme is u16‑backed and its constructor validates the range. - --- -## (Web) `InputNoteRecord.id()` returns `NoteId | undefined`; IDB re‑keyed +## (Rust) Note screening methods renamed ### Summary -A partial (metadata‑less) input note has no note ID yet, so `InputNoteRecord.id()` now returns `NoteId | undefined`. A new `InputNoteRecord.attachments()` getter returns the note's attachments (`NoteAttachment[]`; empty when none). On the storage side, `miden-idxdb-store` now keys input notes by their **details commitment** instead of their note ID (matching the SQLite store): the `InputNotes` table's primary index changed from `noteId` to `detailsCommitment`, so a partial note later completed with its note ID updates the same row instead of creating a duplicate. -### Affected Code -```typescript -// 0.15 — new API: -const id = record.id(); // NoteId | undefined -if (id) { const idStr = id.toString(); } -const attachments = record.attachments(); // NoteAttachment[] -``` +`NoteScreener::can_consume` became `get_consumability`, and `can_consume_batch` became `get_batch_consumability`. A new `get_batch_consumability_for_account` was added. `Client::get_consumable_notes` keeps its signature — passing a single account is now screened more efficiently, but nothing about the call changes. -### Migration Steps -1. Guard every `record.id()` use against `undefined` (partial notes have no ID). -2. Replace `noteMetadata.attachment()` reads with `record.attachments()`. -3. If you query IndexedDB directly, switch input‑note lookups from `noteId` to `detailsCommitment`. Re‑sync existing `0.14` stores under `0.15`. +:::note The rename is cosmetic +The changelog justifies it by saying the methods now return a consumption status per account rather than a boolean. They never returned a boolean — the return type is identical in 0.15 and 0.16. Rename the call sites; do not change how you handle the result. +::: --- -## (Web) `importNoteFile` / `notes.import` resolve to a hex `string` +## (Rust) Debug mode removed -### Summary -`WebClient.importNoteFile(...)` (and `notes.import(...)`) now resolves to a hex `string` instead of a `NoteId` object. Upstream `Client::import_notes` returns details‑commitments rather than note IDs, so the web method returns the note‑id hex for a metadata‑bearing file, or the details‑commitment hex for a details‑only file. Pass it to `NoteId.fromHex(...)` if you need a `NoteId` instance. - -### Migration Steps -1. Treat the return value as a hex `string`; drop any `.toString()` / `NoteId` method calls on it. -2. If you need a `NoteId`, wrap the result with `NoteId.fromHex(hex)`. -3. Be aware the returned hex may be a details‑commitment (not a note‑id) for details‑only files. +`DebugMode`, `ClientBuilder::in_debug_mode`, `Client::in_debug_mode`, and the `MIDEN_DEBUG` environment variable were all removed. The VM replaced the flag-gated `debug.*` decorators with `miden::core::debug` procedures that print unconditionally, so there is nothing left to gate. See [MASM Changes](./masm-changes#debug-and-trace-decorators-removed). --- -## (Web) `Felt` and `Word` constructors throw on overflow - -### Summary -`new Felt(value)` and `new Word(values)` now throw when an input is at or beyond the field modulus, instead of silently constructing an out‑of‑range value. Each input must be a canonical field element. Wrap construction from untrusted input in `try`/`catch`. +## (Rust) Other library changes -### Migration Steps -1. Reduce or validate values to the field modulus before constructing `Felt` / `Word`, or catch the thrown error. -2. Audit any code that fed raw `u64` / `bigint` values (e.g. from external systems) into these constructors. +- **`StateSyncUpdate` is immutable** — construct with `from_parts`, read through accessors, and destructure with `into_parts`. `PartialBlockchainUpdates::insert` lost its nodes argument, and `extend_authentication_nodes` was added. +- **`miden_client::assembly::Library` was removed.** Use `miden_client::vm::Package`. Note that `Package` is not new — it was already re-exported in 0.15; only the `Library` removal is a 0.16 change. +- **`Client::fetch_all_private_notes` was removed**, replaced by note transport syncing. +- **`TransactionRecord` gained a private field**, so struct literal construction no longer compiles. +- **`send_notes` reads its payload from the advice provider** and requires a payload-commitment script argument. A `script_arg` passed alongside a `SendNotes` template is ignored. +- **`AccountSmtForest` is generic over its backend**, and the root-staging API was removed. +- **Response verification moved into `VerifyingRpcClient`.** The built-in gRPC constructors now wrap the transport in it automatically, but `ClientBuilder::rpc` does **not** — passing your own `NodeRpcClient` compiles and runs while silently losing response verification. Wrap it yourself with `VerifyingRpcClient::new(..)`. --- -## (Web) `syncNotes` `blockTo` required; `chainTip()` removed +## (Web) Package and API changes -### Summary -`RpcClient.syncNotes(blockFrom, blockTo, noteTags)`'s `blockTo` parameter is now **required** (was optional). The upstream RPC no longer returns the chain tip, so `NoteSyncInfo.chainTip()` was removed — use `client.syncState()` to learn the chain tip. `NoteSyncInfo.blockTo()` still exists. +Bump `@miden-sdk/miden-sdk` and `@miden-sdk/react` together — mixing 0.15 and 0.16 packages will not link against the shared WASM ABI. -### Migration Steps -1. Always pass an explicit `blockTo` to `syncNotes`. -2. Replace `NoteSyncInfo.chainTip()` reads with `client.syncState()`. +| Change | Migration | +| --- | --- | +| `ClientOptions.debugMode` removed; `createClient*` drops the trailing `debugMode` argument | Delete the option and the argument. | +| `accountDelta()` → `accountPatch()`; `AccountStorageDelta` removed | Rename. `TransactionSummary.accountDelta()` is unchanged. | +| `TransactionSummary.salt()` → `userParams()` | Rename; the value is now seven field elements. | +| `transactions.preview(..)` returns only a summary while authorization is pending | Do not expect full transaction details from a preview. | +| A summary derived on one client no longer reproduces on another | Capture a `ChainAnchor` and pass it as the `anchor` option. See [Transaction Changes](./transaction-changes#collecting-signatures-across-clients-requires-a-chainanchor). | +| `notes.sendPrivate` requires `scanAfterBlockNum`; new `notes.sendPrivateOutput` | Pass a scan height. | +| `notes.fetchPrivate({ mode: "all" })` removed | Use note transport syncing. | +| `AccountComponent.createNetworkAuth` → `createNetworkAuthComponents` | Rename; it now returns several components. | +| `FungibleAsset.withCallbacks(flag)` removed | Set callbacks on the account at construction. | +| P2ID and P2IDE notes must carry at least one asset | Building an empty note now throws. | +| Production WASM strips MASM debug metadata | Expect less detail in production stack traces. | +| Notes carrying a `NetworkAccountTarget` are priced via a foreign procedure invocation into the target | Behavioural; see the note below. | ---- +Additive: `notes.list({ scriptRoots })`, `NoteScript.networkAccountConfig()`, `NoteScript.feeSponsorship()`, and `compile.component({ namespace })`. -## (Web) `getNoteScriptByRoot` returns `NoteScript | undefined` +Additive in `0.16.0-rc.3`, one release later: `transactions.captureAnchor(request)`, an `anchor` option on `preview` / `executeRequest` / `submit`, the wasm-level `chainAnchorForRequest` / `executeTransactionAt` / `executeForSummaryAt`, and `TransactionSummary.blockCommitment()` / `expirationDelta()`. -### Summary -`RpcClient.getNoteScriptByRoot(scriptRoot)` now resolves to `NoteScript | undefined` instead of throwing when the node has no script for the given root. +If you author MASM through the Web SDK, the language changes apply to you as well — `@account_procedure` annotations, `mod` declarations, and the new import syntax. See [MASM Changes](./masm-changes). -### Migration Steps -1. Replace the `try`/`catch` "not found" path with an `=== undefined` check. -2. Keep a `try`/`catch` only for genuine transport/RPC failures. +:::caution Unverified +The `NetworkAccountTarget` foreign-procedure-invocation requirement is reported from the changelog. We were not able to locate the enforcing call site in source, so treat it as a lead rather than a confirmed behaviour. +::: --- -## (Web) `FetchedNote` exposes `noteId`/`metadata`; `header` removed - -### Summary -`FetchedNote` (returned by `RpcClient.getNotesById(...)`) now stores `noteId` and `metadata` directly and exposes them as getters. The synthetic `header` getter was **removed** — a `NoteHeader` can no longer be reconstructed from header‑shaped fields alone for private notes. A new `attachments` getter exposes the note's attachments (populated for both public and private fetched notes). The JS constructor signature is unchanged. +## (React) Send hooks relay through `sendPrivateOutputNote` -### Migration Steps -1. Replace `fetched.header.id()` with `fetched.noteId` and `fetched.header.metadata()` with `fetched.metadata`. -2. Remove any code that reconstructs a `NoteHeader` from a `FetchedNote`. -3. Read attachments via `fetched.attachments` (works for private notes too). +`useSend`, `useTransaction`, and `useMultiSend` now relay private note output via `sendPrivateOutputNote`, following the `notes.sendPrivate` change above. If you wrapped these hooks, re-check the relay path. --- -## (Web) `newFaucet` rebuilt on `FungibleFaucet` + `TokenPolicyManager` +## (React) `useChainAnchor` and `usePreview` -### Summary -`WebClient.newFaucet(...)` (and `accounts.create({ type: "FungibleFaucet", ... })`) now assembles a faucet from the `0.15` `FungibleFaucet` component plus a `TokenPolicyManager` that registers `AllowAll` mint and burn policies (transfer policies are intentionally omitted). `non_fungible = true` still fails fast. The JS signature `newFaucet(storageMode, nonFungible, tokenName, tokenSymbol, decimals, maxSupply, authScheme)` is unchanged. Separately, `BasicFungibleFaucetComponent.fromAccount(account)` now reads the new `0.15` metadata slot, so **faucets minted by prior SDK versions can no longer be introspected through it**. +Both are new in `0.16.0-rc.3`, and `useTransaction().execute` accepts an `anchor` alongside them. `usePreview` is the first summary surface in the React SDK — before it, verifying and co-signing a multisig proposal meant dropping to the WASM client. -### Migration Steps -1. No call‑site change for creating faucets — the JS signature is the same. -2. Re‑create faucets under `0.15` if you need `BasicFungibleFaucetComponent.fromAccount(...)` to introspect them. -3. Keep passing `non_fungible = false`; `true` still throws "Non‑fungible faucets are not supported yet". +If you build a multi-party signing flow, preview and execute against the `anchoredRequest` that `useChainAnchor` returns rather than the request you passed in. Re-resolving a request factory produces a different transaction, and any builder that creates an output note draws a fresh serial number, so the anchor would pin a request nobody executes. See [Transaction Changes](./transaction-changes#collecting-signatures-across-clients-requires-a-chainanchor). --- -## (Web) `idxdb-store`: `committedNoteIds` → `committedNoteTagSources` +## (CLI) `send` renamed to `transfer` ### Summary -On `miden-idxdb-store`'s `JsStateSyncUpdate`, the `committedNoteIds` field was renamed to `committedNoteTagSources` and now carries details‑commitment hex rather than note‑id hex. Anyone driving the IndexedDB store's sync apply path directly (or stubbing `JsStateSyncUpdate` in tests) must rename the field and feed details‑commitment hex. -### Migration Steps -1. Rename `committedNoteIds` → `committedNoteTagSources` everywhere you construct or read `JsStateSyncUpdate`. -2. Supply details‑commitment hex (not note‑id hex) for those entries. +The `send` subcommand is now `transfer`. Nothing else changed — every flag, short form, and default is identical. `send` is **not** kept as an alias, so existing scripts fail with an unknown-subcommand error. ---- +### Affected Code -## (Web) `getAccountProof` rewired; WASM `ClientError` gains a `code` +```bash +# Before (0.15) +miden-client send -s -t -a 100:: -n private -### Summary -Two smaller web changes: -- `RpcClient.getAccountProof(accountId, storageRequirements?, blockNum?, knownVaultCommitment?)` keeps the **same JS signature** but is now wired onto the `0.15` `get_account(GetAccountRequest...)` upstream API. No JS call‑site change is required; it needs a `0.15` node. -- WASM client errors now carry a stable, machine‑readable **`code`** property on the thrown JS error — currently `ACCOUNT_NOT_FOUND_ON_CHAIN` and `ACCOUNT_ALREADY_TRACKED`. This is additive (message‑string matching still works) but branching on `code` is the recommended pattern, and the worker shim forwards it. +# After (0.16) +miden-client transfer -s -t -a 100:: -n private +``` ### Migration Steps -1. Point `getAccountProof` callers at a `0.15` node (the underlying RPC was renamed/reshaped). -2. Where you match on `ClientError` message text for "account not found on chain" or "already tracked", switch to `error.code`. Keep a fallback for errors without a `code` (only those two variants are mapped today). + +Replace `miden-client send` with `miden-client transfer` in scripts, aliases, and CI jobs. Change nothing else. --- -## (Web) `WasmWebClient.proveTransactionWithProver` → `proveTransaction` +## (CLI) `account --with-code` replaced by `account --inspect` ### Summary -On the raw WASM client (`WasmWebClient` — the low-level surface behind `MidenClient`), `proveTransactionWithProver(txResult, prover)` was **renamed `proveTransaction(txResult, prover?)`**, with the prover now an optional second parameter (omitting it uses the local prover). The high-level `MidenClient` resource API is unaffected, but anything driving the raw client directly — worker shims, offscreen/prover documents, `_withInnerWebClient` callbacks — must rename the call. + +`--with-code`, which dumped the account code as one pretty-printed blob, is gone. `account --inspect [:]` lists the procedures an account exposes, split into resolved procedures (name, signature, originating package) and unresolved ones listed by MAST root. ### Affected Code -```typescript -// 0.15 — new API (raw WasmWebClient surface): -const proven = await wasmWebClient.proveTransaction(txResult, prover); -// prover may be omitted to prove locally: -const proven2 = await wasmWebClient.proveTransaction(txResult); + +```bash +# Before (0.15) +miden-client account --show --with-code + +# After (0.16) +miden-client account --inspect # list procedures +miden-client account --inspect --verbose # with MASM disassembly +miden-client account --inspect :receive_asset # a single procedure +miden-client account --inspect -p ./component.masp # resolve names from extra packages ``` ### Migration Steps -1. Rename `proveTransactionWithProver(txResult, prover)` to `proveTransaction(txResult, prover)`. -2. Audit any code that already called a 0.14 `proveTransaction()` *without* a prover — the 0.15 signature is compatible (prover optional), no change needed. + +1. Replace `account --show --with-code` with `account --inspect --verbose`. +2. `--inspect` is mutually exclusive with `--list`, `--show`, and `--default`. +3. `--package` and `--verbose` both require `--inspect`. +4. Expect `` entries for procedures whose package the CLI cannot find; pass `--package` to resolve them. --- -## (Web) `storeIdentifier()` is now async +## (CLI) `call` counts arguments in field elements ### Summary -`MidenClient.storeIdentifier()` now returns a **`Promise`** instead of a plain `string` (the identifier is read from the client behind the worker/async boundary). Call sites that fed the result into `exportStore` / `importStore` or string operations must `await` it. -### Migration Steps -1. `await client.storeIdentifier()` everywhere; un-awaited uses surface as `Promise`-vs-`string` type errors (or `[object Promise]` store names at runtime). +`call` validates argument count against the procedure's signature. In 0.15 it compared against the number of *parameters*; in 0.16 it compares against the total stack width in *field elements*. A procedure taking one `Word` now needs four `--args` values. ---- +This change is not in the changelog. -## (Web) `InputNoteRecord.nullifier()` returns `string | undefined` +### Affected Code -### Summary -Because a 0.15 nullifier folds in the note's metadata (see [Nullifier now includes metadata and attachments commitment](./note-changes#nullifier-now-includes-metadata-and-attachments-commitment)), a **partial (metadata-less) input note record has no computable nullifier** — `InputNoteRecord.nullifier()` now returns `string | undefined`, pairing with `id()`'s `NoteId | undefined`. A record missing either is a partial note that sync has not yet completed. +```bash +# A procedure with signature `set_item(Word) -> ()` -### Migration Steps -1. Guard `record.nullifier()` against `undefined` alongside the existing `record.id()` guard; treat records missing either as not-yet-consumable and skip them from listings. +# Before (0.15): one parameter, one argument +miden-client call :set_item -p component.masp --args 0x1234 ---- +# After (0.16): a Word is four felts wide +miden-client call :set_item -p component.masp --args +``` -## (React) `useCreateWallet` / `useCreateFaucet` drop `"network"` +### Migration Steps -### Summary -Matching the `StorageMode` narrowing, the React `useCreateWallet({ storageMode })` and `useCreateFaucet({ storageMode })` hooks no longer accept `"network"`. The `storageMode` option type is now `"private" | "public"`. +1. Re-check every scripted `call` whose procedure takes or returns anything wider than one field element. +2. Expand each wide argument into one value per field element, in signature order. +3. Read the `Raw Signature:` line the command prints — it is now the authoritative stack layout. -### Migration Steps -1. Replace `storageMode: "network"` with `"public"` or `"private"` in every `createWallet` / `createFaucet` call. -2. Update any local `StorageMode`‑typed state feeding these hooks. +A mismatched count fails with a clear error rather than executing with a mis-shaped stack, so this one fails loudly. --- -## (CLI) `--account-type` accepts only `private` / `public` +## (CLI) `token_symbol_map.toml`: `id` renamed to `address` ### Summary -The `-t` / `--account-type` flag on `new-account` / `new-wallet` now takes only `private` or `public` (account *visibility*); the legacy values (`fungible-faucet`, `non-fungible-faucet`, `regular-account-immutable-code`, `regular-account-updatable-code`), the separate `--mutable` flag, and the standalone `--storage-mode` toggle were removed. Whether an account is a faucet is derived from its components — installing a `FungibleFaucet` component yields a fungible faucet (with an implicit `TokenPolicyManager`). + +The per-symbol entry key changed from `id` to `address`. The value format is unchanged — it was already a bech32 address — so this is a pure key rename. A file still using `id` fails to parse rather than falling back. ### Affected Code -```diff -# 0.15 — new API: -+ miden-client new-wallet --account-type public + +```toml +# Before (0.15) +BTC = { id = "mlcl1qru2e5yvx40ndgqqqzusrryr0ucyd0uj", decimals = 8 } + +# After (0.16) +BTC = { address = "mlcl1qru2e5yvx40ndgqqqzusrryr0ucyd0uj", decimals = 8 } ``` ### Migration Steps -1. Replace `--account-type ` with `--account-type private|public`. -2. Drop `--mutable` and `--storage-mode`; pick the faucet vs. wallet shape via the `-p` package/components. + +1. Rename `id =` to `address =` on every entry. Leave the values alone. +2. The file lives in the `.miden` directory alongside `miden-client.toml`. If you have both a local and a global `.miden` directory, update both. --- -## (CLI) `new-faucet` requires a `[fungible-faucet-metadata]` block +## (CLI) `init` writes a different package set ### Summary -The faucet init‑data file passed via `-i` now uses a typed `[fungible-faucet-metadata]` block (`symbol`, `decimals`, `max_supply`, optional `name`) instead of the old stringly‑typed `["miden::standards::fungible_faucets::metadata"]` section. Faucet accounts created with the previous layout are no longer recognized by `account list` / `account show`. -### Affected Code -```diff -# 0.15 — new API: — init-data TOML -+ [fungible-faucet-metadata] -+ symbol = "BTC" -+ decimals = 10 -+ max_supply = 10000000 +`init` now writes nine bundled `.masp` component packages instead of seven. + +```text +# Added in 0.16 +basic-non-fungible-faucet.masp +auth/guarded-multisig-auth.masp +auth/network-account-auth.masp + +# Removed in 0.16 +auth/acl-auth.masp ``` -### Migration Steps -1. Rename the section to `[fungible-faucet-metadata]` and switch to typed scalars (`decimals` / `max_supply` are integers, not quoted strings). -2. Re‑create existing faucets — the previous component layout is no longer recognized. +The removal is the CLI-side consequence of dropping `AuthSingleSigAcl`, and it is the one most likely to break an existing setup. The changelog mentions only the additions. + +Two error-reporting changes also landed: running `init` where a config already exists now names the configured network and points at `clear-config`, and an unparseable `--remote-prover-endpoint` is a hard error instead of being silently discarded. --- -## (CLI) `address add` takes bech32; new `address encode` +## (CLI) Other changes -### Summary -`address add` now takes ` ` (a pre‑encoded address) instead of ` [TAG_LEN]`. A new `address encode [TAG_LEN]` subcommand produces the bech32 string from the individual fields. +- **`--debug` and `MIDEN_DEBUG` removed.** Passing `--debug` is now a usage error; setting `MIDEN_DEBUG` is silently ignored. +- **The pre-confirmation transaction summary shows absolute values, not deltas** — including a column rename and `Nonce incremented by: N` becoming `New account nonce: N`. This follows from the `AccountPatch` move but changes what users read before approving a transaction. +- **`swap` gained `--payback-note-type `**, defaulting to `private` (0.15 hardcoded private). Note that `pswap` already had this flag in 0.15 with the same default. The tag the command tells you to track also changed, from a swap-specific tag to an account-target tag derived from the sender's account ID. +- **`consume-notes` gained `--start-debug-adapter ` and `--record `; `exec` gained `--record`.** `exec --start-debug-adapter` already existed in 0.15. Both require a build with the `dap` feature, which is not enabled by default. -### Affected Code -```bash -# 0.15 — new API: -miden-client address encode [TAG_LEN] # produces the bech32 string -miden-client address add # stores the pre-encoded address -``` +--- -### Migration Steps -1. Build the bech32 address first with `address encode [TAG_LEN]`. -2. Pass that string to `address add `. +## Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| `Migration error: Attempt to migrate a database with a migration number that is too high` | Pre-0.16 store | Delete and recreate the store. | +| `error: unrecognized subcommand 'send'` | Renamed | Use `transfer`. | +| `error: unexpected argument '--with-code'` | Removed | Use `--inspect`. | +| `Procedure '' expects 4 value(s), got 1` | Arguments counted in field elements | Expand wide arguments. | +| `missing field 'address'` parsing the token map | Key renamed | Rename `id` to `address`. | +| `error: unexpected argument '--debug'` | Removed | Delete the flag. | +| `no method named account_delta` on a transaction result | Renamed | Use `account_patch()`. | +| Node rejects a submission | Mixed client and node versions | Upgrade both to 0.16. | +| A component package is missing after `init` | `auth/acl-auth.masp` was removed | Migrate off `AuthSingleSigAcl`. | diff --git a/docs/builder/migration/08-masm-changes.md b/docs/builder/migration/08-masm-changes.md index 5318e791..e525ed04 100644 --- a/docs/builder/migration/08-masm-changes.md +++ b/docs/builder/migration/08-masm-changes.md @@ -1,147 +1,395 @@ --- sidebar_position: 8 title: "MASM Changes" -description: "Breaking MASM and standard-library changes in v0.15" +description: "The new mod declarations, rewritten import syntax, removal of the debug decorators, and the reorganised protocol procedure surface" --- # MASM Changes :::warning Breaking Change -Several core `miden::protocol::note` procedures were renamed; several kernel procedures dropped redundant outputs that duplicated their inputs; the asset vault key's metadata byte was redefined to encode `AssetComposition`; and the immediate form `adv_push.N` was removed. Custom note scripts and any MASM that consumed the removed outputs or inspected the asset-key metadata must be updated. +Miden Assembly gained an explicit module tree. A `.masm` file is no longer picked up because it sits in the right directory — its parent must declare it with `mod` or `pub mod`, and **an undeclared file is silently dropped from the artifact** rather than silently included. The `use` form was split into module imports and braced item imports, alias syntax changed from `->` to `as`, and the `debug.*` and `trace` decorators were removed. On the protocol side, asset helpers, note creation, and several account procedures moved to new paths. ::: +## Quick Fix + +```masm +# Before (0.15) +use miden::standards::wallets::basic->basic_wallet +pub use miden::core::stark::verifier + +# After (0.16) +use miden::standards::wallets::basic as basic_wallet +pub mod verifier +pub use {verify} from self::verifier +``` + +```masm +# Every directory of .masm files now needs a mod.masm declaring its children +pub mod account +pub mod asset +mod callbacks # private to the parent +``` + +If you encounter errors, continue reading for detailed migration steps. + +--- + +## Summary + +The largest change is structural rather than syntactic. In 0.15 the assembler discovered modules by walking directories; in 0.16 it follows an explicit tree of `mod` declarations rooted at your project's root module. This is why the assembler's directory-based entry points disappeared (see [VM & Assembler Changes](./vm-assembler)) and why `miden-project.toml` now requires an explicit `path` to that root. + +The failure mode is worth internalising: forgetting a `mod` declaration is **not** an error at the declaration site. The module simply is not part of the artifact, and you discover it later as an undefined-symbol error at the call site — or, worse, not at all if nothing calls it. + +Everything else on this page is mechanical: import rewrites, decorator replacements, and renamed protocol procedures. + --- -## Kernel/protocol proc renames: `build_recipient*`, `extract_*_from_metadata` +## Every module must be declared with `mod` / `pub mod` ### Summary -Several core MASM procedures in `miden::protocol::note` were renamed for consistency. These are used by every custom note script that computes a recipient or reads metadata. +A submodule's source is resolved as either `/.masm` or `//mod.masm`, relative to the declaring module's directory. The assembler includes only modules reachable through these declarations ([#3220](https://github.com/0xMiden/miden-vm/pull/3220)). -| 0.14 | 0.15 | -| --- | --- | -| `note::build_recipient_hash` | `note::compute_recipient` | -| `note::build_recipient` | `note::compute_and_store_recipient` | -| `note::extract_sender_from_metadata` | `note::metadata_into_sender` | -| `note::extract_attachment_info_from_metadata` | `note::metadata_into_attachment_schemes` | +### Affected Code + +In 0.15 the protocol's kernel root module was a comment; the directory tree was walked implicitly. In 0.16 it enumerates its children, and every intermediate directory gained its own `mod.masm` — the protocol repo went from 9 `mod.masm` files to 35: + +```masm +# After (0.16) — kernels/transaction-core/src/mod.masm +pub mod account +pub mod account_update +pub mod asset +pub mod asset_vault +mod callbacks # private: not reachable from outside this module +pub mod constants +pub mod epilogue +# … one line per child module +``` + +Declarations may be interleaved with `use` statements and appear anywhere among the top-level forms. Two other top-level forms landed alongside `mod`: an optional `namespace ` declaration that names the module explicitly, and `extern package "@"`. -New convenience helpers were also added: `note::metadata_into_note_type` and `note::metadata_into_tag` (use the latter instead of slicing the header manually). +```masm +# After (0.16) — the full top-level form vocabulary +namespace app::accounts +extern package "miden/base@0.1.0" +mod internal +pub mod api +``` ### Migration Steps -1. Search/replace the four procedure names per the table. -2. Where you manually extracted the tag from the metadata header, switch to `metadata_into_tag`. +1. For every directory of `.masm` files, add a `mod.masm` (or a sibling `.masm`) that declares each child with `pub mod `. Use plain `mod ` for modules that should not be reachable from outside the parent. +2. Walk your project root downward and confirm every `.masm` file is reachable from the root through a chain of declarations. +3. Do not declare a submodule with the same name as its parent, and do not declare the same source file from two different parents — both are hard errors. + +### Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| `invalid submodule declaration '': could not find module sources at '/.masm' or '//mod.masm'` | `mod ` with no matching file | Create the file or remove the declaration. | +| `invalid submodule declaration '': submodules must not have the same name as their parent` | e.g. `mod foo` inside `foo/mod.masm` | Rename the child. | +| `conflicting submodule paths detected: '' can be parsed from either '' and '', but not both` | Both `.masm` and `/mod.masm` exist | Delete one. | +| `invalid submodule declaration '': module source '' is already reachable through another submodule declaration` | Two parents declare the same file | Declare it once. | +| `undefined item ''` on a call that used to work | The callee's module is not declared | Add the missing `mod` declaration. | --- -## Asset vault key & composition {#asset-vault-key-composition} +## Import syntax: item imports, `as` aliases, and global resolution ### Summary -The asset vault key's metadata byte was redefined to encode the new `AssetComposition`. The MASM word is still called `ASSET_KEY` (it was **not** renamed — `AssetVaultKey` is the Rust type name), and its word-level layout is unchanged: +The `use` form was split into two explicitly distinguished shapes, and import resolution became strictly global ([#3220](https://github.com/0xMiden/miden-vm/pull/3220)): + +- **Module import** — `use some::module` or `use some::module as alias`. Brings a module into scope under a local name. **May not be `pub`.** +- **Item import** — `use {item} from some::module` or `use {a, b as c} from some::module`. Brings individual procedures, constants, or types into scope. **May be `pub`**, which is how you re-export. + +Four consequences follow: +1. `pub use ` for re-exporting a *module* is gone. `pub use` is valid only in the braced item form, so **you can no longer re-export a module**, only named items. +2. The alias separator changed from `->` to `as`. +3. An import path may no longer begin with another import's alias — imports resolve in the global namespace, as if every path were absolute. +4. Submodule-relative imports need an explicit `self::` prefix. + +Source-level digest imports (`use 0x->name`) were removed. Direct digest *invocation* targets (`exec.0x…`) still work. + +### Affected Code + +The alias change, from the protocol's own P2IDE note script: + +```diff +- use miden::standards::wallets::basic->basic_wallet ++ use miden::standards::wallets::basic as basic_wallet ``` -ASSET_KEY = [asset_id_suffix, asset_id_prefix, faucet_id_suffix_and_metadata, faucet_id_prefix] + +Re-exporting a procedure from another package: + +```diff +- pub use ::miden::utils::panic ++ pub use {panic} from ::miden::utils ``` -What changed is the asset-metadata packed into the low 8 bits of the third element (`faucet_id_suffix_and_metadata`): +Re-exporting from your own submodule, from the core library's `stark/mod.masm`: -| Bits | Meaning | -| --- | --- | -| 0–1 | `AssetComposition` — `COMPOSITION_NONE` (0), `COMPOSITION_FUNGIBLE` (1), `COMPOSITION_CUSTOM` (2). `Custom` is reserved and currently rejected. | -| 2 | asset-callback flag (**moved from bit 0** in v0.14). | -| 3–7 | reserved; must be zero. | +```masm +# Before (0.15) +use miden::core::stark::verifier +pub use verifier::verify +``` -The `COMPOSITION_NONE` / `COMPOSITION_FUNGIBLE` / `COMPOSITION_CUSTOM` constants are exported from `miden::protocol::asset`. +```masm +# After (0.16) +pub mod verifier +pub use {verify} from self::verifier +``` -### Affected Code +Note both halves of that change: `verifier` is now a declared submodule, and the re-export path is `self::verifier` rather than the bare alias. In 0.15 the second `use` resolved `verifier` through the first — that chaining is exactly what was removed. -New and updated procedures in `miden::protocol::asset` (call as `exec.asset::`): +Plain module imports are unchanged and remain the common case: -| Procedure | Inputs → Outputs | Notes | +```masm +# Identical in 0.15 and 0.16 +use miden::core::crypto::hashes::poseidon2 +use miden::protocol::active_note +``` + +### Migration Steps + +1. Rewrite every `pub use a::b::c` re-export as `pub use {c} from a::b`. +2. Replace every `use path->alias` with `use path as alias`. +3. Rewrite any `use` whose path begins with an alias introduced by an earlier `use` in the same file to use the full global path. +4. To import from a submodule of the current module, prefix with `self::`. You cannot `use` a submodule you declared yourself — it is already in scope via `mod`, so reference it by name. +5. Delete any `use 0x->name` source-level digest imports. + +### Common Errors + +| Error Message | Cause | Solution | | --- | --- | --- | -| `asset::key_to_composition` | `[ASSET_KEY] → [asset_composition, ASSET_KEY]` | **New.** Compare the result against the `COMPOSITION_*` constants. | -| `asset::key_to_callbacks_enabled` | `[ASSET_KEY] → [callbacks_enabled, ASSET_KEY]` | Returns `1` if callbacks are enabled, `0` otherwise (reads bit 2). | -| `asset::key_to_faucet_id` | `[ASSET_KEY] → [faucet_id_suffix, faucet_id_prefix, ASSET_KEY]` | **Not renamed.** Now masks off the metadata internally. | -| `asset::key_into_faucet_id` | `[ASSET_KEY] → [faucet_id_suffix, faucet_id_prefix]` | **Not renamed.** Consumes the key. | -| `asset::key_to_asset_id` / `asset::key_into_asset_id` | `[ASSET_KEY] → [asset_id_suffix, asset_id_prefix(, ASSET_KEY)]` | **Not renamed.** | +| ``` `pub use` is only supported for braced item imports ``` | `pub use some::module` | Use `pub use {item} from some::module`. | +| ``import aliases use `as`; `->` is no longer supported`` | `use foo->bar` | `use foo as bar`. | +| `import target '' cannot be resolved through import ''` | Path starts with another import's alias | Use the full global path. | +| `cannot import submodule '' declared in the same module` | `use` of your own `mod`-declared child | Drop the `use`. | +| `item import target '' resolved to a module` | `use {x} from …` where `x` is a module | Use the module-import form. | +| `digest imports are not supported` | `use 0x1234->entry` | Remove it; use `exec.0x…` directly. | -The transaction kernel also adds `is_fungible_asset_key` (`[ASSET_KEY] → [is_fungible_asset, ASSET_KEY]`), equivalent to `key_to_composition` followed by `eq.COMPOSITION_FUNGIBLE`. The asset constructors (`create_fungible_key`, `create_fungible_asset_unchecked`, `create_non_fungible_asset_unchecked`) keep their v0.14 signatures — they encode the composition internally. +--- -```masm -# Read the callback flag (bit moved 0 → 2; use the helper, don't mask manually) -exec.asset::key_to_callbacks_enabled -# => [callbacks_enabled, ASSET_KEY] +## `debug.*` and `trace` decorators removed + +### Summary + +The `debug.*` decorator family and the `trace` decorator were removed from the language, along with the CLI `--trace` flag and the decorator wire slots in the MAST format. Print-style debugging now goes through the new `miden::core::debug` module, whose procedures are ordinary `emit` events handled host-side ([#3169](https://github.com/0xMiden/miden-vm/issues/3169), [#3201](https://github.com/0xMiden/miden-vm/pull/3201), [#3208](https://github.com/0xMiden/miden-vm/pull/3208)). + +:::danger These print in production +Because they are events rather than decorators, they carry no MAST cost — but unlike `debug.*`, which only fired when the VM ran in debug mode, **they print whenever invoked**. Leaving one in production code will print, and will disclose private values if your program has moved witness data onto the stack or into memory. +::: -# Branch on the asset's composition -exec.asset::key_to_composition -# => [asset_composition, ASSET_KEY] -eq.COMPOSITION_FUNGIBLE +### Affected Code + +| v0.15 decorator | v0.16 replacement | +| --- | --- | +| `debug.stack` | `exec.debug::print_stack` | +| `debug.stack.` | `exec.debug::print_stack` (prints the whole stack; there is no top-`n` form) | +| `debug.mem` | `exec.debug::print_mem_all` | +| `debug.mem.` | `push. exec.debug::print_mem_addr` | +| `debug.mem..` | `push. push. exec.debug::print_mem` — takes `[start, end]`, end-exclusive | +| `debug.local`, `debug.local.`, `debug.local..` | `locaddr. exec.debug::print_mem_addr` | +| `debug.adv_stack.` | `push. push.0 exec.debug::print_adv_stack`, or `exec.debug::print_adv_stack_all` | +| `trace.` | Removed with no replacement. | + +```masm +# After (0.16) +use miden::core::debug + +begin + exec.debug::print_stack # [] -> [] + exec.debug::print_mem_all # [] -> [] + push.16 push.0 exec.debug::print_mem # [start=0, end=16] -> [] + locaddr.0 exec.debug::print_mem_addr # [addr] -> [] + exec.debug::print_adv_stack_all # [] -> [] + exec.debug::print_adv_map_all # [] -> [] + exec.debug::print_adv_map_item # [KEY] -> [] (consumes the key) +end ``` +The full export list of `miden::core::debug` is `print_stack`, `print_mem`, `print_mem_addr`, `print_mem_all`, `print_adv_stack`, `print_adv_stack_all`, `print_adv_map_all`, and `print_adv_map_item`. + +On the Rust side, `DebugOptions`, `Instruction::Debug(..)`, and `Instruction::Trace(..)` no longer exist. + ### Migration Steps -1. If you read the callback flag by masking **bit 0** of the metadata, switch to `asset::key_to_callbacks_enabled` — the flag now lives in **bit 2**. -2. To branch on the asset type, call `asset::key_to_composition` and compare against the `COMPOSITION_*` constants instead of inspecting raw bits. -3. No change is needed for `asset::key_to_faucet_id`, `asset::key_into_faucet_id`, `asset::key_to_asset_id`, or `asset::key_into_asset_id` — their names and stack effects are unchanged. Only code that **hand-decodes** the metadata byte is affected by the bit-layout shift; callers using these helper procs are not. +1. Search your MASM for `debug.` and `trace.` and replace per the table. Remember `print_mem` takes `[start, end]` with `end` exclusive, and both operands are consumed. +2. Add `use miden::core::debug` to any module that now calls these. +3. Remove `--trace` from any `miden-vm` invocation. +4. Register the handlers. `CoreLibrary::handlers()` includes the stack and memory debug handlers by default; the **advice** handlers are opt-in, so extend the handler set with `miden_core_lib::handlers::debug::advice_debug_handlers` to enable `print_adv_stack*` and `print_adv_map*`. +5. Strip these calls from production code. + +--- + +## Core library: the `miden::precompiles` namespace, and removals + +### Summary + +The core MASM package was split into `miden::core` and a new `miden::precompiles` namespace ([#3459](https://github.com/0xMiden/miden-vm/pull/3459), [#3222](https://github.com/0xMiden/miden-vm/pull/3222)). Some procedures that used to live under `miden::core::crypto` are now internal precompile support under `miden::precompiles`. + +`miden::core::crypto::hashes::keccak256` still exists and still exports `hash_bytes`, `hash`, and `merge` — it now delegates to `miden::precompiles::hashes::keccak256`. **Application code should keep calling the `miden::core::…` facade**; reach for `miden::precompiles::*` only if you are writing your own precompile wrapper. -See [Assets, Vault & Faucet](./asset-vault-faucet) for the matching Rust-side `AssetComposition` / `AssetVaultKey` changes. +Several modules and procedures were removed outright. EdDSA and SHA-512 are documented as *temporarily* removed pending precompiles-prover support. + +| Removed in v0.16 | Replacement | +| --- | --- | +| `miden::core::crypto::dsa::eddsa_ed25519` (whole module) | None in this line. | +| `miden::core::crypto::hashes::sha512` (whole module) | None in this line. | +| `miden::core::crypto::dsa::ecdsa_k256_keccak::verify_prehash` | `verify`, or the new `verify_bytes`. | +| `miden::core::sys::log_precompile_request` | `miden::core::sys::build_proof_request_key` — a *different* operation, not a rename. | +| `miden::core::pcs::fri::frie2f4::preprocess` | Test-only helper; no replacement. | + +Additions in the same area: `ecdsa_k256_keccak::verify_bytes`, for verifying a signature over a variable-length Keccak256 message held in VM memory ([#3563](https://github.com/0xMiden/miden-vm/pull/3563)), and `miden::core::math::u256` reaching parity with the `u64` and `u128` modules ([#3167](https://github.com/0xMiden/miden-vm/pull/3167)). + +### Migration Steps + +1. If you verify Ed25519 signatures or hash with SHA-512 in MASM, there is no in-VM path in 0.16. Move that work off-chain or defer the upgrade. +2. Replace `ecdsa_k256_keccak::verify_prehash` with `verify` (word-sized message) or `verify_bytes` (variable-length message in memory) — and see the ABI change below, which you need either way. +3. If you wrote a custom precompile wrapper against `sys::log_precompile_request`, rewrite it against the deferred-DAG helpers in `miden::precompiles`. --- -## Redundant kernel outputs removed +## ECDSA advice and signature ABI changed ### Summary -Six kernel procedures stopped returning values that were identical to (or trivially recoverable from) their inputs. Custom MASM that consumed the now-removed outputs must drop the stale cleanup. +The advice-stack layout consumed by `miden::core::crypto::dsa::ecdsa_k256_keccak::verify` changed from `PK[9] | SIG[17]` — a 33-byte compressed public key and a 65-byte recoverable signature, byte-packed — to `QX[8] | QY[8] | SIG_R[8] | SIG_S[8]`, native little-endian `u32` limbs with **no recovery byte** ([#3222](https://github.com/0xMiden/miden-vm/pull/3222)). The public-key *commitment* preimage changed too; see [Hashing & Crypto Changes](./hashing-crypto). -| Procedure | 0.14 output | 0.15 output | -| --- | --- | --- | -| `active_note::get_assets` (also `input_note`/`output_note`) | `[num_assets, dest_ptr]` | `[num_assets]` | -| `active_note::get_storage` | `[NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr]` | `[num_storage_items]` | -| `faucet::mint` | `[NEW_ASSET_VALUE]` | `[]` | -| `note::write_assets_to_memory` | echoed inputs | trimmed | +### Affected Code + +```masm +# Before (0.15) +#! Operand stack: [PK_COMM, MSG, ...] +#! Advice stack: [PK[9] | SIG[17] | ...] +exec.ecdsa_k256_keccak::verify +``` + +```masm +# After (0.16) +#! Operand stack: [PK_COMM, MSG_WORD, ...] +#! Advice stack: [QX[8] | QY[8] | SIG_R[8] | SIG_S[8] | ...] +exec.ecdsa_k256_keccak::verify + +# New: variable-length message held in memory +#! Operand stack: [PK_COMM, MSG_PTR, MSG_LEN_BYTES, ...] +exec.ecdsa_k256_keccak::verify_bytes +``` + +Two behavioural notes carried in the 0.16 source docs: `verify` **accepts high-`s` signatures**, because it proves that some witness satisfies the ECDSA equation and `(r, s)` and `(r, n-s)` are equivalent witnesses; and it pushes **no result word** — it traps on failure. ### Migration Steps -1. Remove the `drop` / `movup`+`drop` that cleared the echoed `dest_ptr` after `get_assets` / `get_storage`. -2. After `get_storage`, the `NOTE_STORAGE_COMMITMENT` word is no longer on the stack — delete the `dropw` that consumed it. -3. After `faucet::mint`, do not expect `NEW_ASSET_VALUE`. +1. Rewrite the host code that populates the advice stack to emit `QX[8]`, `QY[8]`, `SIG_R[8]`, `SIG_S[8]` as little-endian `u32` limbs. +2. Drop the recovery byte — it is not part of the new ABI. +3. If you rely on canonical Ethereum-style signatures, add your own low-`s` check; `verify` will not reject high-`s`. +4. For messages longer than one word, switch from manual chunking to `verify_bytes`. + +--- + +## `do .. while .. end` loops added + +A tail-controlled loop form was added ([#3232](https://github.com/0xMiden/miden-vm/pull/3232)). This is additive — `while.true` is unchanged and still performs an entry check. + +```masm +# New in 0.16 +do + # always runs at least once +while + # must leave one boolean on top of the stack +end +``` + +Use it wherever you previously wrote `push.1 while.true … end` to force a first iteration. --- -## `adv_push.N` immediate form removed; `adv_pushw` added +## Protocol procedure moves and renames ### Summary -The immediate form of `adv_push` (`adv_push.N`) was removed. `adv_push` now always pops exactly one element from the advice stack. To push N elements, emit N consecutive `adv_push` instructions (or `repeat.N adv_push end`). A new `adv_pushw` instruction pushes a full word (4 elements). On the Rust AST side, `Instruction::AdvPush(ImmU8)` became `Instruction::AdvPush` plus a new `Instruction::AdvPushW`. +The protocol MASM surface was reorganised: asset helpers moved from the protocol library into `miden::standards::assets`, note creation moved behind `miden::standards::note::note_creator`, and several `active_account` procedures moved to `native_account`. ### Affected Code +| v0.15 | v0.16 | +| --- | --- | +| `miden::protocol::asset::*` (build/validate helpers) | `miden::standards::assets::*` | +| `miden::protocol::faucet::create_fungible_asset` / `create_non_fungible_asset` | Removed — use the `miden::standards::assets` builders | +| `miden::protocol::output_note::create` (callable from note scripts) | Account context only; note scripts must call `miden::standards::note::note_creator::create_note` | +| `miden::protocol::active_account::get_initial_*` | `miden::protocol::native_account::get_initial_*` | +| `miden::protocol::active_account::has_non_fungible_asset` | `has_asset` | +| `miden::protocol::active_note::get_assets` | `get_initial_assets`, plus explicit removal procedures | +| `basic_wallet::add_assets_to_account` | `basic_wallet::move_note_assets_to_account` | +| `miden::standards::account::metadata` | `miden::standards::account::inspection` | + ```masm -# Before (0.14) -adv_push.1 -adv_push.4 +# Before (0.15) — note script moving assets into the account +use miden::standards::wallets::basic->basic_wallet -# After (0.15) -adv_push -adv_pushw +@note_script +pub proc main + call.basic_wallet::add_assets_to_account +end +``` + +```masm +# After (0.16) +use miden::standards::wallets::basic as basic_wallet + +@note_script +pub proc main + call.basic_wallet::move_note_assets_to_account +end ``` ### Migration Steps -1. Replace every `adv_push.N` with N `adv_push` instructions (or `repeat.N adv_push end`). -2. Where you previously used `adv_push.4` to fetch a word, consider `adv_pushw`. -3. If you build MASM AST programmatically, replace `Instruction::AdvPush(n)` with N `Instruction::AdvPush` (or `Instruction::AdvPushW`). +1. Update every `use` path in your MASM per the table above. +2. Replace `output_note::create` in note scripts with `note_creator::create_note`. +3. Rename `add_assets_to_account` to `move_note_assets_to_account`. --- -## Internal `_impl` precompile procedures removed +## Input-note assets are now stateful ### Summary -The internal `_impl` precompile helper procedures were removed from the core-lib public surface: `ecdsa_k256_keccak::verify_prehash_impl`, `eddsa_ed25519::verify_prehash_impl`, `keccak256::hash_bytes_impl`, and `sha512::hash_bytes_impl`. The public wrappers (`verify`, `verify_prehash`, `hash_bytes`, …) are unchanged and remain the supported entry points. +In 0.15 a note script read the note's asset list and the kernel reconciled it at the end of execution. In 0.16 the note's *initial* assets are read with `active_note::get_initial_assets`, and assets must be **explicitly removed** as they are consumed. Partially-consumed notes are representable, so the kernel no longer drains the note for you. + +### Affected Code + +The `active_note` asset surface in 0.16, verified from `asm/protocol/src/active_note.masm`: + +```masm +pub proc get_initial_assets # [dest_ptr] -> [num_assets] +pub proc get_initial_assets_info +pub proc get_initial_num_assets +pub proc get_asset +pub proc remove_asset +pub proc remove_all_assets +``` + +`active_note::get_storage` and the new `active_note::write_storage_to_memory` are the storage-side counterparts. ### Migration Steps -1. If you called any `*_impl` precompile helper directly by fully-qualified path, switch to the corresponding public wrapper. +1. Replace `active_note::get_assets` with `active_note::get_initial_assets`. +2. Add an explicit removal call for each asset you move out of the note — `remove_asset` for individual assets, or `remove_all_assets` if you consume the note fully. +3. Do not assume the kernel drains the note. If you leave assets in place, the note is treated as partially consumed. + +--- + +## Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| `undefined item ''` for a procedure that exists on disk | Its module is not declared with `mod` | Add the declaration to the parent module. | +| ``import aliases use `as`; `->` is no longer supported`` | Old alias syntax | Rewrite with `as`. | +| ``` `pub use` is only supported for braced item imports ``` | Re-exporting a module | Re-export named items instead. | +| `undefined instruction debug.stack` | Decorator removed | Use `exec.debug::print_stack`. | +| `undefined item 'add_assets_to_account'` | Procedure renamed | Use `move_note_assets_to_account`. | +| Note consumption fails with assets remaining | Assets are no longer drained implicitly | Call `remove_asset` / `remove_all_assets`. | diff --git a/docs/builder/migration/09-vm-assembler.md b/docs/builder/migration/09-vm-assembler.md index e058b64e..e1f7c6f5 100644 --- a/docs/builder/migration/09-vm-assembler.md +++ b/docs/builder/migration/09-vm-assembler.md @@ -1,132 +1,459 @@ --- sidebar_position: 9 title: "VM & Assembler Changes" -description: "Sync-first execution, separated proving options, stricter assembly resolution, and the MAST/project wire-format bump (0.0.2 → 0.0.3) in Miden v0.15" +description: "Library becomes Package, the MAST and package wire formats change, ExecutionProof is reworked, and miden-project.toml requires an explicit path" --- # VM & Assembler Changes :::warning Breaking Change -Execution and proving are now **sync-first**: the single `Host` trait is split into `BaseHost` / `SyncHost` (plus an async `Host`), and `execute()` / `execute_sync()` return an `ExecutionOutput` instead of an `ExecutionTrace`. Separately, the MAST wire format bumped `0.0.2` → `0.0.3`, so `.masl` / `.masp` packages and serialized `MastForest` blobs produced under `0.22` will **not** load under `0.23` — re-assemble everything from source. +The VM jumps **0.23 → 0.29.1**. `Library` and `KernelLibrary` no longer exist — `Package` is the only artifact type, and the `.masl` format is gone. The MAST wire format moved `0.0.3` → `0.0.4` and the package format `4.0.0` → `6.0.0`, so **no 0.15 artifact or serialized proof loads under 0.16**. Verification now takes a single `ExecutionClaim`, and `miden-project.toml` requires an explicit `path` on every target. ::: +For the MASM language changes that ship with this VM version — the new `mod` declarations, the rewritten `use` syntax, and the removal of the `debug.*` decorators — see [MASM Changes](./masm-changes). For the changed commitment preimages, see [Hashing & Crypto Changes](./hashing-crypto). + +## Quick Fix + +```rust +// Before (0.15) +let mut assembler = Assembler::default(); +assembler.link_dynamic_library(CoreLibrary::default())?; +let program: Program = assembler.assemble_program(source)?; + +// After (0.16) +let mut assembler = Assembler::new(source_manager); +assembler.link_package(CoreLibrary::default().package(), Linkage::Dynamic)?; +let package: Box = assembler.assemble_program("program", source)?; +let program: Program = package.unwrap_program(); +``` + +```diff title="miden-project.toml" + [lib] + namespace = "my::app" ++ path = "mod.masm" +``` + +If you encounter errors, continue reading for detailed migration steps. + --- -## Sync-first execution: `BaseHost`/`SyncHost`; `execute` returns `ExecutionOutput` +## Summary + +The assembler was rebuilt around a single artifact type. In 0.15 there were three — `Program`, `Library`, and `KernelLibrary` — serialized as `.masl` for libraries. In 0.16 everything is a `Package` serialized as `.masp`, linking goes through one `link_package(package, linkage)` method, and the directory-walking `*_from_dir` entry points became `*_from_root` entry points that take the root module file. This follows directly from the new explicit module tree: the assembler no longer discovers modules by walking directories, so a directory is no longer a meaningful input. + +Three wire formats changed at the same time and none are backward compatible, which means every artifact must be rebuilt from source rather than migrated. + +Verification was also reshaped: the free `verify(program_info, stack_inputs, stack_outputs, proof)` function became `verify(proof, claim)` over a single `ExecutionClaim`, and the caller-managed precompile registry disappeared entirely — deferred proofs are now rehydrated and bound automatically. + +--- + +## `Library` → `Package` throughout the assembler ### Summary -Execution and proving became **sync-first with runtime-free async compatibility**. The single `Host` trait from `0.22` is split into three: `BaseHost` (shared source/label resolution + event-name lookup), `SyncHost: BaseHost` (synchronous `get_mast_forest` / `on_event`), and `Host: BaseHost` (the async variant). A blanket impl makes every `SyncHost` automatically a `Host`. The sync entry points (`execute_sync`, `prove_sync`, `FastProcessor::execute_sync`/`execute_mut_sync`) require `SyncHost`. Both `execute()` and `execute_sync()` now return **`ExecutionOutput`** instead of `ExecutionTrace` — trace building is explicit via `execute_trace_inputs*()` + `trace::build_trace()`. The deprecated `execute_sync_mut()` / `execute_for_trace*()` aliases and the unbound `TraceBuildInputs::new()` / `from_program()` constructors were removed. +`Library` and `KernelLibrary` were deleted. Every entry point that produced or consumed a `Library` now produces or consumes a `Package`, the `link_*_library` family collapsed into `link_package(package, linkage)`, and `assemble_program` returns a `Box` rather than a `Program`. Every assemble entry point now takes a package name ([#3216](https://github.com/0xMiden/miden-vm/pull/3216), [#3220](https://github.com/0xMiden/miden-vm/pull/3220)). ### Affected Code ```rust -// After (0.23): implement BaseHost + SyncHost; you get Host for free via the blanket impl. -// (Before: a single async `impl Host for MyHost` with async get_mast_forest / on_event.) -use miden_processor::{BaseHost, SyncHost, AdviceMutation, host::handlers::EventError, ProcessorState}; -impl BaseHost for MyHost { - fn get_label_and_source_file(&self, location: &Location) -> (SourceSpan, Option>) { /* ... */ } +// Before (0.15) +use miden_assembly::Assembler; +use miden_core_lib::CoreLibrary; + +let mut assembler = Assembler::default(); +assembler.link_dynamic_library(CoreLibrary::default())?; +let program: Program = assembler.assemble_program(source)?; +``` + +```rust +// After (0.16) +use miden_assembly::{Assembler, Linkage}; +use miden_core_lib::CoreLibrary; + +let mut assembler = Assembler::new(source_manager); +assembler.link_package(CoreLibrary::default().package(), Linkage::Dynamic)?; +for library in libraries { + assembler.link_package(library, Linkage::Dynamic)?; // Arc } -impl SyncHost for MyHost { - fn get_mast_forest(&self, d: &Word) -> Option> { /* ... */ } - fn on_event(&mut self, p: &ProcessorState<'_>) -> Result, EventError> { /* ... */ } +let package: Box = assembler.assemble_program("program", source)?; +let program: Program = package.unwrap_program(); // or try_into_program() +``` + +The complete mapping: + +| v0.15 | v0.16 | +| --- | --- | +| `Assembler::with_kernel(sm, kernel_lib: KernelLibrary) -> Self` | `Assembler::with_kernel(sm, kernel: Arc) -> Result` | +| `link_library(lib, linkage)` / `link_dynamic_library(lib)` / `link_static_library(lib)` | `link_package(package: Arc, linkage: Linkage)` | +| `with_dynamic_library(lib)` / `with_static_library(lib)` | `with_package(package: Arc, linkage: Linkage)` | +| `compile_and_statically_link_from_dir(dir, namespace)` | `compile_and_statically_link_from_root(root, namespace: Option<&Path>)` | +| `assemble_library(modules) -> Arc` | `assemble_library(name, root, support) -> Box` | +| `assemble_library_from_dir(dir, namespace) -> Arc` | `assemble_library_from_root(root, namespace: Option<&Path>) -> Box` | +| `assemble_kernel(module) -> KernelLibrary` | `assemble_kernel(name, root, support) -> Box` | +| `assemble_kernel_from_dir(sys_path, lib_dir) -> KernelLibrary` | `assemble_kernel_from_root(name, sys_module_path) -> Box` | +| `assemble_program(source) -> Program` | `assemble_program(name, source) -> Box` | +| `kernel() -> &Kernel` | `kernel() -> &KernelDescriptor` | +| — | `with_profile(&miden_project::Profile)` *(new)* | + +Also removed from the `miden_assembly` re-export surface: `Library`, `KernelLibrary`, `Parse`, `ParseOptions`, `LinkLibraryKind`, and the `library` module. Added: `Linkage`, the `module` module, and the project-assembly types (`ProjectSourceProvider`, `MasmSourceProvider`, `ResolvedPackage`, `AssemblyInterrupted`). + +### Migration Steps + +1. Replace every `Library` / `KernelLibrary` binding with `Package` — `Arc` for linking, `Box` from the assemble methods. +2. Collapse `link_dynamic_library(x)` / `link_static_library(x)` / `link_library(x, l)` into `link_package(x, Linkage::Dynamic)` or `Linkage::Static`. +3. Rename `*_from_dir` calls to `*_from_root` and pass the root module file instead of the directory. Their `namespace` parameter is now `Option<&Path>` rather than a required `impl AsRef`. +4. Thread a package name through `assemble_program`, `assemble_library`, and `assemble_kernel`. Any string works; the CLI uses the literal `"program"`. +5. After `assemble_program`, call `.unwrap_program()` (panics on a non-executable package) or `.try_into_program()` to get the `Program` the processor expects. +6. Add `?` to `Assembler::with_kernel` — it is now fallible. + +### Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| `cannot find type Library in miden_assembly` | Type removed | Use `Package`. | +| `no method named link_dynamic_library` | Collapsed into one method | `link_package(pkg, Linkage::Dynamic)`. | +| `expected Program, found Box` | `assemble_program` return type changed | Call `.unwrap_program()` or `.try_into_program()`. | +| `this function takes 2 arguments but 1 was supplied` | Assemble entry points take a package name | Pass a name as the first argument. | + +--- + +## Core package split into `miden::core` + `miden::precompiles` + +### Summary + +The single core MASM package was split into `miden-core` (namespace `miden::core`) and `miden-precompiles` (namespace `miden::precompiles`), freeing the bare `miden` namespace for sibling packages such as `miden-protocol` ([#3459](https://github.com/0xMiden/miden-vm/pull/3459), [#3222](https://github.com/0xMiden/miden-vm/pull/3222)). Core records a dynamic dependency on precompiles. Link both with `CoreLibrary::packages()` during assembly, and load `&CoreLibrary` into the host for execution so its merged MAST forest is available. + +### Affected Code + +```rust +// Before (0.15) +let mut assembler = Assembler::default(); +assembler.link_dynamic_library(CoreLibrary::default())?; +let lib = CoreLibrary::default().library(); // &Library +``` + +```rust +// After (0.16) +let core_lib = CoreLibrary::default(); +let mut assembler = Assembler::new(source_manager); +for package in core_lib.packages() { // [Arc; 2] + assembler.link_package(package, Linkage::Dynamic)?; } -// Calling execute now returns ExecutionOutput (exposes `stack`, `advice`, `memory`): -let output: ExecutionOutput = miden_processor::execute_sync(&program, stack_inputs, advice_inputs, &mut host, options)?; +// Or individually: +let core: Arc = core_lib.package(); +let precompiles: Arc = core_lib.precompiles_package(); +let mast: &Arc = core_lib.mast_forest(); // merged, for execution ``` +`CoreLibrary::SERIALIZED` now holds the `miden-core.masp` bytes and a new `CoreLibrary::PRECOMPILES_SERIALIZED` holds `miden-precompiles.masp`; in 0.15 `SERIALIZED` was `core.masl`. `CoreLibrary::library()` and `CoreLibrary::verifier_registry()` are gone, and `CoreLibrary::recursive_verifier_root()` is new. + ### Migration Steps -1. Split your `Host` impl into a `BaseHost` impl (label/source resolution) plus a `SyncHost` impl with plain (non-async) `get_mast_forest` / `on_event`. -2. If you call the sync entry points, pass a `SyncHost`. -3. Replace destructuring of an `ExecutionTrace` return with `ExecutionOutput` accessors; build a trace explicitly only when needed. -4. Replace `FastProcessor::execute_sync_mut(...)` with `execute_mut_sync(...)`, and `execute_for_trace*` / `TraceBuildInputs::new()` with `execute_trace_inputs_sync()` / `execute_trace_inputs()`. +1. Replace the single link call with a loop over `CoreLibrary::default().packages()`, or link `package()` and `precompiles_package()` explicitly. +2. Replace `CoreLibrary::default().library()` with `.package()`. +3. Drop `CoreLibrary::verifier_registry()`. The deferred-precompile registry now lives in the `miden-precompiles` crate as `miden_precompiles::registry()` and is applied by the verifier automatically. --- -## `prove_sync` takes execution options separately +## MAST wire format `0.0.4`, package format `6.0.0`, and `.masl` removed ### Summary -`ProvingOptions` no longer carries an `ExecutionOptions`. `prove_sync` / `prove` now take execution options and proving options as **two separate parameters** (and the sync path requires a `SyncHost`). The `with_execution_options(...)` / `execution_options()` accessors on `ProvingOptions` are gone. `prove_from_trace_sync()` now takes a `TraceProvingInputs`. +Three artifact-format changes land together, none backward compatible: -```diff -- let options = ProvingOptions::default().with_execution_options(exec_options); -- let (stack_outputs, proof) = prove_sync(&program, stack_inputs, advice_inputs, &mut host, options)?; -+ use miden_processor::ExecutionOptions; -+ let (stack_outputs, proof) = prove_sync( -+ &program, stack_inputs, advice_inputs, &mut host, // must be a SyncHost -+ ExecutionOptions::default(), ProvingOptions::default())?; +- The **MAST wire format** bumped `[0,0,3]` → `[0,0,4]`, removing inline metadata slots. Assembly-op and debug-variable metadata now live in a separate indexed `DebugInfo` section ([#3201](https://github.com/0xMiden/miden-vm/pull/3201), [#3208](https://github.com/0xMiden/miden-vm/pull/3208), [#3221](https://github.com/0xMiden/miden-vm/pull/3221)). The stripped serialization mode was removed ([#3268](https://github.com/0xMiden/miden-vm/pull/3268)). +- The **package (`.masp`) format** bumped `[4,0,0]` → `[6,0,0]`, from consolidating debug sections into `PackageDebugInfo` ([#3398](https://github.com/0xMiden/miden-vm/pull/3398)) and binding dense forest and package digests to stored roots and dependencies ([#3334](https://github.com/0xMiden/miden-vm/pull/3334)). +- The **`.masl` library format no longer exists.** `Library::LIBRARY_EXTENSION` is gone along with the type; `.masp` is the only artifact format. + +### Affected Code + +```rust +// Any 0.15 blob fails to read under 0.16: +let forest = MastForest::read_from_bytes(&old_bytes)?; // Err: unexpected version [0,0,3] +let package = Package::read_from_bytes(&old_masp)?; // Err: unexpected version [4,0,0] +``` + +Package deserialization is now tiered by trust level. In 0.15 `Package` implemented only the plain `Deserializable::read_from`: + +```rust +// After (0.16) — three trust levels +Package::read_from(&mut r)? // untrusted: validates MAST, drops debug sections +Package::read_from_bytes(bytes)? +Package::read_from_trusted(&mut r)? // local cache: validates MAST, keeps debug sections +Package::read_from_bytes_trusted(bytes)? +Package::read_from_unchecked(&mut r)? // skips MAST validation; only for self-produced bytes +Package::read_from_bytes_unchecked(bytes)? ``` ### Migration Steps -1. Stop calling `ProvingOptions::with_execution_options(...)`; pass `ExecutionOptions` as its own argument. -2. Ensure the host you pass to `prove_sync` implements `SyncHost`. -3. If you drove `prove_from_trace_sync()`, build a `TraceProvingInputs` from post-execution trace inputs. +1. Re-assemble every `.masp` package from source under 0.16, and re-serialize every cached `MastForest` blob. Invalidate on-disk and database-persisted copies. +2. Delete `.masl` artifacts and any code that reads them. +3. Discard serialized proofs from 0.15 — the proof envelope changed too. +4. Choose the reader that matches your trust boundary: `read_from_bytes` for anything from a registry, the network, or a user; `read_from_bytes_trusted` for your own build cache when you want debug info retained. --- -## Live advice map bounded by total field elements +## `ExecutionProof` reworked; `Verifier` replaces the free `verify_*` functions ### Summary -The live advice map is now bounded by total field-element count during execution. Advice-provider setup returns an error when the **initial** advice already exceeds the limit, and writes that would push the live map past the limit fail. `AdviceMap` gained a `total_element_count()` accessor. +`ExecutionProof` was restructured from `{ proof, hash_fn, pc_requests }` into two envelopes, `StarkProof` and `DeferredProof`, and **proof serialization changed** ([#3222](https://github.com/0xMiden/miden-vm/pull/3222)). The legacy proof-bound precompile request model was replaced by the deferred-DAG framework in `miden_core::deferred`. + +On the native side, `verify(program_info, stack_inputs, stack_outputs, proof)` and `verify_with_precompiles(..)` were replaced by a `Verifier` type and a free `verify(proof, claim)` taking a single `ExecutionClaim` ([#3422](https://github.com/0xMiden/miden-vm/pull/3422), [#3447](https://github.com/0xMiden/miden-vm/pull/3447)). Recursive MASM verification moved from `exec.vm::verify_proof` to `exec.vm::verify_vm_proof`; the new procedure consumes a claim commitment and returns the deferred root plus proof security parameters. Rust callers construct its request-addressed advice with `RecursiveVerifierInputs::for_request`. + +### Affected Code + +```rust +// Before (0.15) +let security_level = miden_verifier::verify( + program_info, stack_inputs, stack_outputs, proof, +)?; +let (level, commitment) = miden_verifier::verify_with_precompiles( + program_info, stack_inputs, stack_outputs, proof, ®istry, +)?; +``` + +```rust +// After (0.16) +use miden_core::program::ExecutionClaim; +use miden_verifier::{Verifier, verify}; + +let claim = ExecutionClaim::from_program_info(program_info, stack_inputs, stack_outputs); + +let security_level: u32 = verify(proof, claim)?; // free fn, default config +let security_level: u32 = Verifier::new().verify(proof, claim)?; // equivalent + +// Partial (delegable) verification returns a #[must_use] obligation: +let (level, unsettled) = Verifier::new() + .with_max_deferred_elements(n) + .verify_partial(proof, claim)?; +let root: Word = unsettled.root(); +``` + +In VM 0.29.1, `ExecutionProof` exposes `miden_proof() -> &StarkProof` and `deferred_proof() -> &DeferredProof`, with constructors `ExecutionProof::new(miden, deferred)` and `from_parts(bytes, hash_fn, deferred)`. The 0.15 public fields, three-argument `new`, `stark_proof()`, `hash_fn()`, `precompile_requests()`, and `into_parts()` are gone. For a non-default partial-verification budget, configure `Verifier::with_max_deferred_elements(n)` before calling `verify_partial`. + +`verify_with_precompiles` and `verify_with_max_deferred_elements` are both removed. Precompile verification is no longer wired up by the caller: the deferred wire is rehydrated under the built-in `miden_precompiles::registry()` and bound to the STARK public inputs automatically. + +`prove` and `prove_sync` keep their 0.15 signatures. New in this line: `prove_partial`, `prove_partial_sync`, and `prove_partial_from_trace_sync`. ### Migration Steps -1. If you seed very large advice maps up front, split the data or stream it in during execution. -2. Handle the new setup-time error from advice-provider construction instead of assuming it always succeeds. +1. Build an `ExecutionClaim` — usually `ExecutionClaim::from_program_info(info, inputs, outputs)` — and pass `(proof, claim)` to `verify`. +2. Delete `PrecompileVerifierRegistry` plumbing and calls to `verify_with_precompiles` / `verify_with_max_deferred_elements`. Use `Verifier::with_max_deferred_elements(n)` if you need a non-default budget. +3. Replace field access on `ExecutionProof` with `miden_proof()` / `deferred_proof()`. +4. Discard serialized proofs from 0.15 — they will not deserialize. +5. If you use `verify_partial`, do not drop the returned `Unsettled`. It is `#[must_use]` and represents a deferred obligation you must settle or re-expose. --- -## Stricter assembly resolution: structured errors replace panics +## `AdviceInputs.stack` replaced by the `AdviceStack` type ### Summary -Several previously-panicking or silently-partial assembly paths now return structured errors: oversized modules are rejected at resolver construction, non-procedure invoke targets are rejected, self-recursive / rootless call graphs return typed cycle errors, and unresolved `pub use -> ` returns a normal assembly error. The linker also rejects non-`syscall` references to exported kernel procedures and rejects empty kernel packages. Code that assembled cleanly under `0.22` continues to assemble; the change is that malformed inputs now surface as recoverable `Report` errors instead of panics. +`AdviceInputs`'s public `stack: Vec` field was replaced by a private `AdviceStack`, and the `with_stack` / `with_stack_values` / `extend_stack` helpers were removed in favour of `with_advice_stack(AdviceStack)` and the `advice_stack()` accessor ([#3423](https://github.com/0xMiden/miden-vm/pull/3423)). + +### Affected Code + +```rust +// Before (0.15) +let advice = AdviceInputs::default() + .with_stack(vec![a, b, c]) + .with_stack_values([1u64, 2, 3])? + .with_map(entries); +let raw: &Vec = &advice.stack; +``` + +```rust +// After (0.16) +use miden_core::advice::{AdviceInputs, AdviceStack}; + +let mut stack = AdviceStack::new(); +stack.append_word(word).append_elements([a, b, c]); +// or, from raw u64s, validating each against the field modulus: +let stack = AdviceStack::try_from_values([1u64, 2, 3])?; + +let advice = AdviceInputs::default() + .with_advice_stack(stack) + .with_map(entries); + +let stack: AdviceStack = advice.advice_stack(); // clone of the stack +let (stack, map, store) = advice.into_parts(); // new in 0.16 +``` + +`AdviceStack` distinguishes append (bottom) from prepend/push (top) and names the MASM instruction each targets: `append_element`, `append_elements`, `append_word`, `append_dword`, `append_for_adv_push`, `append_for_adv_pipe`, `prepend_elements`, `prepend_word`, `prepend_stack`, `push_element`, plus `consume_element` / `consume_word` / `consume_dword` and `into_elements`. `AdviceInputs::map` and `AdviceInputs::store` remain public fields. ### Migration Steps -1. If you wrapped assembly in panic-catching logic for malformed inputs, replace it with normal `Result`/`Report` error handling. -2. Fix any MASM that referenced an exported kernel procedure via `exec`/`call` instead of `syscall` — that is now a hard error. +1. Replace `with_stack(iter)` with `with_advice_stack(AdviceStack::…)`, building the stack with the append/prepend methods. +2. Replace `with_stack_values(u64s)?` with `AdviceStack::try_from_values(u64s)?`. +3. Replace direct reads of `advice_inputs.stack` with `advice_inputs.advice_stack()`, or destructure with `into_parts()`. +4. Mind the ordering vocabulary: `append_*` adds below (consumed later), `prepend_*` and `push_element` add on top (consumed first). + +:::info New resource bounds +The live advice map is now bounded by total field-element count and the advice Merkle store by internal node count, both during setup and execution ([#3264](https://github.com/0xMiden/miden-vm/pull/3264)). `FastProcessor` memory growth is bounded by a configurable `ExecutionOptions::max_memory_elements` ([#3226](https://github.com/0xMiden/miden-vm/pull/3226)). If you seed very large advice inputs, expect a setup-time error rather than silent success. +::: --- -## Post-last-operation decorators deprecated +## `ModuleInfo` → `ModuleDescriptor`, `Kernel` → `KernelDescriptor` ### Summary -Operation-indexed decorators placed *after* the last operation of a basic block are now rejected in both block assembly and serialized MAST forests. Decorators that should run after a block exits must use the `after_exit` slot instead. This only affects code that builds `MastForest`s programmatically — ordinary MASM source is unaffected. +The module and kernel metadata types were renamed and relocated ([#3356](https://github.com/0xMiden/miden-vm/pull/3356)). + +### Affected Code + +```diff +- use miden_core::program::Kernel; +- use miden_assembly::library::ModuleInfo; +- let k: &Kernel = assembler.kernel(); ++ use miden_core::program::KernelDescriptor; ++ use miden_assembly::module::ModuleDescriptor; ++ let k: &KernelDescriptor = assembler.kernel(); +``` + +The module path moved as well: the `library` module is gone from `miden_assembly`'s re-exports, and `ModuleDescriptor` lives under `module`. ### Migration Steps -1. If you attach decorators programmatically, move any decorator targeting the post-last-op index to the block's `after_exit` decorator list. +1. Rename `Kernel` → `KernelDescriptor` and `ModuleInfo` → `ModuleDescriptor` at every import and binding. +2. Update the import path from `miden_assembly::library` to `miden_assembly::module`. +3. Re-check descriptor method names against the new type — several were renamed alongside it. --- -## Project File Format +## `miden-project.toml`: `path` is mandatory on every target ### Summary -The MAST forest serialization format was refactored around fixed-layout **full**, **stripped**, and **hashless** sections, with stable node IDs and stricter validation of untrusted forests. The wire-format version constant bumped from `[0, 0, 2]` to `[0, 0, 3]`. Serialized `.masl` / `.masp` / `MastForest` blobs produced under `0.22` will not deserialize under `0.23`. Deserialization of serialized libraries and kernel libraries is now treated as **untrusted** by default, rejecting spoofed/inconsistent node digests rather than trusting the bytes. +The `path` key on `[lib]` and `[[bin]]` targets changed from optional to required. It may point at files with extensions other than `.masm` — a Rust project's source root, for example — which is why the implicit default was dropped ([#3216](https://github.com/0xMiden/miden-vm/pull/3216)). In the Rust AST, `LibTarget::path` and `BinTarget::path` moved from `Option>` to `Span`. + +A project with neither a `[lib]` nor any `[[bin]]` still gets an implicit library target defaulting to `mod.masm`; that inference is unchanged. ### Affected Code -```rust -// Any blob serialized under 0.22 (VERSION = [0, 0, 2]) fails to read under 0.23: -let forest = MastForest::read_from_bytes(&old_bytes)?; // Err: unexpected version +```diff title="miden-project.toml" + [lib] + namespace = "miden::protocol" ++ path = "mod.masm" + + [[bin]] + name = "entry" ++ path = "bin/main.masm" ``` ### Migration Steps -1. Re-assemble every `.masl` / `.masp` package and re-serialize any cached `MastForest` blobs from source under `0.23`. -2. If you persisted MAST forests or packages to disk or a database, invalidate and regenerate them. -3. If you deserialize forests from an untrusted source, expect stricter validation — malformed or spoofed-digest forests now return an error instead of loading. +1. Add an explicit `path` to every `[lib]` and `[[bin]]` in every `miden-project.toml`. +2. If you construct `LibTarget` / `BinTarget` in Rust, drop the `Some(..)` wrapper around `path`. --- -:::tip -For the full VM changelog, see the [miden-vm releases](https://github.com/0xMiden/miden-vm/releases). +## `miden-vm bundle` reworked + +### Summary + +`miden-vm bundle` now takes the path to a **root `.masm` module** instead of a directory, `--kernel` is a boolean flag instead of taking a path, and the output is a `.masp` package instead of a `.masl` library. With `--kernel` set, the kernel's support modules are derived from the explicit `mod` declarations in the root module ([#3216](https://github.com/0xMiden/miden-vm/pull/3216), [#3220](https://github.com/0xMiden/miden-vm/pull/3220)). + +### Affected Code + +```bash +# Before (0.15) +miden-vm bundle --namespace mylib ./src # directory -> out.masl +miden-vm bundle --kernel ./kernel.masm ./src # --kernel takes a path + +# After (0.16) +miden-vm bundle --namespace mylib ./src/mod.masm # root module -> out.masp +miden-vm bundle --kernel ./kernel/mod.masm # --kernel is a flag +``` + +`--namespace` is now optional in the non-kernel case: if omitted, the assembler expects a `namespace` declaration in the root module, where 0.15 fell back to the directory name. For `--kernel` the namespace defaults to `$kernel`. A new `-r` / `--release` flag disables debug symbols. + +### Migration Steps + +1. Change the positional argument from a directory to the root module file. +2. Change `--kernel ` to a bare `--kernel` with the kernel's root module as the positional argument. +3. Update the expected output filename from `out.masl` to `out.masp`. +4. Ensure the root module declares its submodules with `mod` / `pub mod` — that is now how support modules are discovered. +5. Either pass `--namespace` or add a `namespace` declaration to the root module. + +--- + +## `ProjectAssembler::assemble_with_sources` removed + +### Summary + +`ProjectAssembler::assemble_with_sources(target, profile, sources)` was removed — projects must be assembled from the filesystem ([#3216](https://github.com/0xMiden/miden-vm/pull/3216)). In its place, project assembly is extensible through the `ProjectSourceProvider` trait, which lets non-MASM source languages participate ([#3375](https://github.com/0xMiden/miden-vm/pull/3375), [#3383](https://github.com/0xMiden/miden-vm/pull/3383)). + +### Affected Code + +```rust +// Before (0.15) +let pkg = project_assembler.assemble_with_sources(target, profile, sources)?; +``` + +```rust +// After (0.16) +let pkg: Arc = project_assembler.assemble(target_selector, profile_name)?; + +// Register a provider for a non-MASM source language: +let mut pa = Assembler::new(sm) + .for_project_at_path_with_providers(manifest_path, &mut store, [my_provider])?; + +// A provider can interrupt assembly: +match pa.assemble_interruptible(target_selector, profile_name)? { + ControlFlow::Continue(pkg) => { /* … */ }, + ControlFlow::Break(interrupted) => { /* … */ }, +} +``` + +`ProjectAssembler::assemble(target_selector, profile_name)` keeps its 0.15 signature. + +### Migration Steps + +1. Drop `assemble_with_sources`; write your sources to disk and use `assemble`, or implement a `ProjectSourceProvider`. +2. If you need to react to a provider interrupting assembly, use `assemble_interruptible` and match on the `ControlFlow`. + +:::note Changelog correction +The 0.25.4 changelog names the new method `ProjectAssembler::assemble_source_project`. The method that actually exists in the released code is **`assemble_source_package`**. ::: + +--- + +## Smaller Rust API removals + +These are lower-impact, but each will break a build if you touch it. + +| Removed / changed | PR | +| --- | --- | +| `MastForest::compact` removed — deduplicate through builders or explicit `MastForest::merge` | [#3318](https://github.com/0xMiden/miden-vm/pull/3318) | +| Stripped `MastForest` serialization mode removed | [#3268](https://github.com/0xMiden/miden-vm/pull/3268) | +| Dense forest construction moved to `DenseMastForestBuilder`; non-canonical dense payloads rejected | [#3334](https://github.com/0xMiden/miden-vm/pull/3334) | +| `MastForestBuilder` simplified around builder-local refs and immutable finalized forests | [#3139](https://github.com/0xMiden/miden-vm/pull/3139) | +| `prettier::pretty_print_csv`, `MastNodeId::from_usize_safe`, `DecoratorId::from_u32_bounded`, `OpBatch::end_indices` removed | [#3197](https://github.com/0xMiden/miden-vm/pull/3197) | +| `Processor` trait methods moved into their sub-interfaces | [#3202](https://github.com/0xMiden/miden-vm/pull/3202) | +| `ExecutionOptions::with_overlapped_trace_build` added, on by default | [#3407](https://github.com/0xMiden/miden-vm/pull/3407) | +| `miden-vm run` / `miden-vm prove` now fail when the inferred `.inputs` file is missing instead of proceeding | [#3236](https://github.com/0xMiden/miden-vm/pull/3236) | +| `ResumeContext` exposes its debug info outside `miden-processor` and can be built from a `Package` | [#3355](https://github.com/0xMiden/miden-vm/pull/3355) | +| Proof serialization switched from `bincode` to `wincode`; verifier-side STARK proof deserialization bounded to 64 MiB | [#3148](https://github.com/0xMiden/miden-vm/pull/3148) | +| `AeadPoseidon2::key_from_bytes` restored to canonical-`Felt` decoding; keys persisted under the brief SHA-256 KDF contract must be re-derived | [#3366](https://github.com/0xMiden/miden-vm/pull/3366) | +| `Felt::from_{u8,u16,u32}` are now `const`; `Felt::MAX` added | [crypto#1081](https://github.com/0xMiden/crypto/pull/1081) | +| Assembling a procedure with more locals than the frame pointer can represent is a diagnostic error rather than a panic | [#3332](https://github.com/0xMiden/miden-vm/pull/3332) | + +The `miden-crypto` 0.26 and 0.27 breaking changes are almost entirely in `LargeSmt` / `LargeSmtForest` storage backends and prover internals, which application code does not call. The one exception worth knowing: the RustCrypto and dalek stack (`k256`, `sha2`, `sha3`, `curve25519-dalek`, `ed25519-dalek`, `x25519-dalek`, `hkdf`, `der`) was upgraded ([crypto#1045](https://github.com/0xMiden/crypto/pull/1045)) and `rand` moved to 0.10 ([crypto#995](https://github.com/0xMiden/crypto/pull/995)). Expect version-unification pressure if you depend on those crates directly. + +--- + +## Common Errors + +| Error Message | Cause | Solution | +| --- | --- | --- | +| `unexpected version [0,0,3]` reading a `MastForest` | MAST wire format is now `0.0.4` | Re-assemble from source. | +| `unexpected version [4,0,0]` reading a package | Package format is now `6.0.0` | Rebuild the `.masp`. | +| Cannot open a `.masl` file | Format removed entirely | Rebuild as `.masp`. | +| `cannot find function verify_with_precompiles` | Replaced by automatic deferred verification | Use `verify(proof, claim)`. | +| `no field stack on type AdviceInputs` | Field is now private | Use `advice_stack()` or `into_parts()`. | +| `missing field path` parsing `miden-project.toml` | `path` is mandatory | Add it to every `[lib]` and `[[bin]]`. | +| Serialized proof fails to deserialize | Proof envelope and serialization changed | Regenerate the proof. | diff --git a/docs/builder/migration/10-rust-sdk-compiler.md b/docs/builder/migration/10-rust-sdk-compiler.md index 3e240859..75f9a02a 100644 --- a/docs/builder/migration/10-rust-sdk-compiler.md +++ b/docs/builder/migration/10-rust-sdk-compiler.md @@ -1,324 +1,169 @@ --- sidebar_position: 10 -title: "Rust SDK & Compiler Changes" -description: "miden SDK 0.12 → 0.13 (the protocol-v0.15-aligned release): #[component] becomes a trait + storage struct, a required miden-project.toml manifest, explicit #[account(...)] declarations, and tx-kernel binding changes" +title: "Rust Contract SDK & Compiler" +description: "Changes to the miden crate and midenc for developers writing smart contracts in Rust" --- -# Rust SDK & Compiler Changes +# Rust Contract SDK & Compiler -This section covers the `miden` Rust SDK and compiler (the `miden` crate and `midenc`), used to write Miden smart contracts, notes, and transaction scripts in Rust. The relevant step is the SDK's **`0.12` → `0.13`** release, which is the line aligned with VM `0.23` / protocol `0.15`. (The `miden` SDK carries its own version number, distinct from the protocol and client crate versions used elsewhere in this guide.) +:::info Which "Rust SDK"? +Two different things get called the Rust SDK. This page is about the **`miden` crate and `midenc`**, used to write account components, notes, and transaction scripts *in Rust* and compile them to MASM. The `miden-client` library — used to build applications that talk to a Miden node — is covered in [Client Changes](./client-changes). +::: :::warning Breaking Change -The SDK macros were reworked: `#[component]` is now a **trait + a storage struct**, a `miden-project.toml` manifest is **required**, accounts must be declared explicitly with `#[account(...)]`, and the tx-kernel bindings were aligned with protocol v0.15. The macro changes touch **every account component, every authentication component, and every note/tx-script that references an account.** Work through the sections in order: rewrite the component (1), add the project manifest (2), update account references (3), then the bindings (4). +Component trait methods must now be marked `#[account_procedure]` to be part of the account interface, and `#[account(..)]` generates one trait per interface instead of inherent methods. Note also that the contract toolchain **lags the rest of the 0.16 line**: it builds against protocol `0.16.0-alpha.4` and VM `0.25`, not the protocol `0.16.0-rc` and VM `0.29.1` that the client and node use. ::: ---- - -## `#[component]` is now a trait + a storage struct - -### Summary - -`#[component]` no longer applies to a `struct` or an inherent `impl`. An account component is now three pieces: - -1. a `#[component_storage]` struct holding the `#[storage(...)]` fields, -2. a `#[component]` **trait** declaring the API (the trait name yields the WIT interface), and -3. a `#[component] impl Trait for Storage` block providing the behavior. - -Method receivers (`&self` / `&mut self`) and method bodies are unchanged. - -### Affected Code - -Before (`0.12`): +## Quick Fix ```rust -use miden::{component, felt, Felt, StorageMap, Word}; - +// Before #[component] -struct CounterContract { - #[storage(description = "counter contract storage map")] - count_map: StorageMap, +trait BasicWallet { + fn receive_asset(&mut self, asset: Asset); } +// After #[component] -impl CounterContract { - pub fn get_count(&self) -> Felt { - let key = Word::new([felt!(0), felt!(0), felt!(0), felt!(1)]); - self.count_map.get(key) - } - - pub fn increment_count(&mut self) -> Felt { - let key = Word::new([felt!(0), felt!(0), felt!(0), felt!(1)]); - let new_value = self.count_map.get(key) + felt!(1); - self.count_map.set(key, new_value); - new_value - } +trait BasicWallet { + #[account_procedure] + fn receive_asset(&mut self, asset: Asset); } ``` -After (`0.13`): - -```rust -use miden::{component, component_storage, felt, Felt, StorageMap, Word}; - -// 1. storage fields move to a `#[component_storage]` struct -#[component_storage] -struct CounterContractStorage { - #[storage(description = "counter contract storage map")] - count_map: StorageMap, -} +If you encounter errors, continue reading for detailed migration steps. -// 2. the API becomes a `#[component]` trait (its name is the WIT interface) -#[component] -trait CounterContract { - fn get_count(&self) -> Felt; - fn increment_count(&mut self) -> Felt; -} - -// 3. the behavior is a `#[component] impl Trait for Storage` block -#[component] -impl CounterContract for CounterContractStorage { - fn get_count(&self) -> Felt { - let key = Word::new([felt!(0), felt!(0), felt!(0), felt!(1)]); - self.count_map.get(key) - } - - fn increment_count(&mut self) -> Felt { - let key = Word::new([felt!(0), felt!(0), felt!(0), felt!(1)]); - let new_value = self.count_map.get(key) + felt!(1); - self.count_map.set(key, new_value); - new_value - } -} -``` +--- -**Authentication components migrate the same way.** `#[auth_script]` was already required in `0.12`; in `0.13` it simply moves onto the trait method declaration (the `impl` method no longer repeats it): +## Versions -```rust -// before (0.12): inherent impl -#[component] -struct AuthComponent; -#[component] -impl AuthComponent { - #[auth_script] - pub fn auth_procedure(&mut self, _arg: Word) { /* ... */ } -} +The contract toolchain versions independently of the rest of the stack, and in this release it is genuinely behind. -// after (0.13): trait + storage, `#[auth_script]` on the trait method -#[component_storage] -struct AuthComponentStorage; -#[component] -trait AuthComponent { - #[auth_script] - fn auth_procedure(&mut self, _arg: Word); -} -#[component] -impl AuthComponent for AuthComponentStorage { - fn auth_procedure(&mut self, _arg: Word) { /* ... */ } -} -``` +| Component | Version | +| --- | --- | +| `midenc` / compiler workspace | 0.10.0 | +| `miden` contract SDK crate (and `miden-base-sys`, `miden-stdlib-sys`, `miden-sdk-alloc`) | 0.14.0 | +| Protocol it builds against | `0.16.0-alpha.4` | +| VM crates it builds against | 0.25 | +| MSRV | 1.97 (plus a nightly toolchain) | -### Migration Steps +Two consequences worth planning around: -1. Move each component's `#[storage(...)]` fields into a `#[component_storage]` struct. -2. Declare the API as a `#[component] trait` (the trait name becomes the WIT interface). -3. Provide the behavior in a `#[component] impl Trait for Storage` block; drop the `pub` on the method bodies. -4. For auth components, move `#[auth_script]` from the `impl` method onto the trait method declaration. +- The MSRV is **1.97**, higher than the 1.96 the rest of the stack requires. Your toolchain must satisfy the highest of the two. +- Because the toolchain pins protocol `0.16.0-alpha.4` and VM `0.25`, contract code compiled with it sees an earlier snapshot of the 0.16 protocol surface than your client does. The MAST and package wire formats are compatible across VM 0.25 and 0.29.1, so artifacts still load; the skew is in the protocol API surface, not serialization. --- -## `miden-project.toml` is now a required file +## Component methods must be marked `#[account_procedure]` ### Summary -`0.13` introduces a dedicated project manifest, `miden-project.toml`, placed next to `Cargo.toml` at the crate root. The Miden-specific configuration that previously lived in `Cargo.toml` `[package.metadata.*]` now lives here, and the proc-macros read it to resolve the WIT interface name, the project kind, and any FPI/sibling dependencies. **Building a `0.13` project without it fails** (for components, with an undefined `::init` link error). - -### Affected Code - -Create `miden-project.toml` like this (account component without dependencies): +A `#[component]` trait's methods are no longer implicitly part of the account interface. Every method that must be callable from a note script, a transaction script, a foreign procedure invocation, or a sibling component now needs `#[account_procedure]` **on the trait declaration**, not on the `impl`. -```toml title="miden-project.toml" -[package] -name = "counter-contract" # crate name; kebab-case -version = "0.1.0" # project version; supplies the WIT `@version` +`#[auth_script]` and `#[account_procedure]` cannot be combined in one component. An authentication component keeps using `#[auth_script]` alone; mixing them is a compile error. Like `#[auth_script]`, `#[account_procedure]` is recognised by the enclosing `#[component]` macro and needs no import. -[lib] -kind = "account-component" # project kind: "account-component" | "note" | "tx-script" -# Full WIT id: miden:/@ -# = the kebab-cased [package].name -# = the kebab-cased `#[component]` trait name (here: `CounterContract`) -# = the [package].version above -namespace = "miden:counter-contract/counter-contract@0.1.0" +### Affected Code -[dependencies] -miden-core = "*" -miden-protocol = "*" +```rust +// Before +use miden::{Asset, NoteIdx, component, component_storage, output_note}; -# account components only: which account types may host this component -[package.metadata.miden] -supported-types = ["RegularAccountUpdatableCode"] +#[component] +trait BasicWallet { + fn receive_asset(&mut self, asset: Asset); + fn move_asset_to_note(&mut self, asset: Asset, note_idx: NoteIdx); +} ``` -Walking through the fields: - -- **`[package]`** — `name` and `version`. The version feeds the `@version` suffix of the WIT id, so bumping it changes the component's interface id. -- **`[lib].kind`** — the project kind: `account-component`, `note`, or `tx-script`. -- **`[lib].namespace`** — the full `miden:/@` WIT id. The **interface segment must equal the kebab-cased `#[component]` trait name**; a mismatch fails to link with an undefined `::init`. (For `note`/`tx-script` projects, the interface segment is the project's own name rather than a component trait.) -- **`[dependencies]`** — the Miden crates the project links against (`miden-core`, `miden-protocol`), plus any FPI/sibling dependency packages by `path` (see the next section). -- **`[package.metadata.miden].supported-types`** — account components only. - -:::caution Storage-slot caution -Storage slot names derive from the `[lib].namespace` interface segment (which mirrors the component trait name), and slot names feed `StorageSlotId` derivation. Renaming the component trait (and updating `[lib].namespace` to match) **re-keys the storage slot ids of an already-deployed component**. Keep the trait name stable across upgrades of a live component. -::: +```rust +// After +use miden::{Asset, NoteIdx, NoteType, Recipient, Tag, component, component_storage, output_note}; -For a project that calls another account/component (FPI or sibling), add the dependency in both `[dependencies]` (the package) and `[package.metadata.miden.dependencies]` (its generated WIT): +#[component] +trait BasicWallet { + #[account_procedure] + fn receive_asset(&mut self, asset: Asset); -```toml title="miden-project.toml" -[dependencies] -miden-core = "*" -miden-protocol = "*" -basic-wallet = { path = "../basic-wallet" } + #[account_procedure] + fn move_asset_to_note(&mut self, asset: Asset, note_idx: NoteIdx); -# the dependency's generated WIT, used to generate the call bindings -[package.metadata.miden.dependencies] -basic-wallet = { wit = "../basic-wallet/target/generated-wit/" } + #[account_procedure] + fn create_note(&mut self, tag: Tag, note_type: NoteType, recipient: Recipient) -> NoteIdx; +} ``` -The `[package.metadata.miden.dependencies]..wit` entry is what the macros read to generate the typed call bindings, and it is the **same entry used by note scripts, by account components doing FPI, and by sibling component calls**. +The `impl` block is unchanged — the attribute is not repeated there. ### Migration Steps -1. Add a `miden-project.toml` next to `Cargo.toml` with `[package]`, `[lib].kind`, and `[lib].namespace`. -2. Set the `[lib].namespace` interface segment to the kebab-cased `#[component]` trait name. -3. Move any `[package.metadata.*]` Miden config out of `Cargo.toml` into this file. -4. For account components, list `supported-types`. For FPI/sibling calls, add the dependency in `[dependencies]` and its generated WIT under `[package.metadata.miden.dependencies]`. +1. For each `#[component] trait`, add `#[account_procedure]` above every method called from a note, a transaction script, FPI, or a sibling component. +2. Leave authentication components alone. They keep `#[auth_script]` and must not gain `#[account_procedure]`. +3. Purely internal helper methods can stay unmarked. + +:::caution The shipped templates disagree with this rule +The `cargo miden new` account template declares its method without `#[account_procedure]` while the sibling note and tx-script templates call it, and the full-project scaffold has the same gap. The repository's own `examples/counter-contract` does mark them. The template tests only build, never execute, so the gap is not caught by CI. If you scaffold a new project, add the attribute yourself rather than trusting the generated code. +::: --- -## Accounts: declare `#[account(...)]` explicitly with an interface +## `#[account(..)]` generates one trait per interface ### Summary -The auto-generated `crate::bindings::Account` struct is gone. Declare the account explicitly with `#[account(...)]` and use that type as the note/tx-script entrypoint account parameter. The dependency reference now **requires the exported WIT interface** (kebab-cased and validated): write `#[account(basic_wallet::BasicWallet)]`, not `#[account(basic_wallet)]`. - -### Affected Code - -Before (`0.12`): +`#[account(..)]` used to generate the referenced component's methods as **inherent** methods on the wrapper struct. It now generates **one trait per referenced interface**, named after the interface, and implements it for the wrapper. This lets two components exporting the same method name coexist on one account. -```rust -use miden::{active_note, note, AccountId, Word}; -use crate::bindings::Account; // auto-generated +Most single-component call sites are unchanged, but two situations break. -#[note] -struct P2idNote { - target_account_id: AccountId, -} - -#[note] -impl P2idNote { - #[note_script] - pub fn script(self, _arg: Word, account: &mut Account) { - for asset in active_note::get_assets() { - account.receive_asset(asset); - } - } -} -``` +### Affected Code -After (`0.13`): +The wrapper struct may no longer share its name with a generated trait: ```rust -use miden::{account, active_note, note, AccountId, Word}; - -// declare the native account explicitly; pick the package's WIT interface -#[account(basic_wallet::BasicWallet)] -pub struct Wallet; - -#[note] -struct P2idNote { - target_account_id: AccountId, -} +// Before — compiled +#[account(counter_contract::CounterContract)] +struct CounterContract; -#[note] -impl P2idNote { - #[note_script] - pub fn script(self, _arg: Word, account: &mut Wallet) { - for asset in active_note::get_assets() { - account.receive_asset(asset); - } - } -} -``` - -The same `#[account(...)]` type serves two roles. Passed to a `#[note]`/`#[tx_script]` entrypoint it is the transaction's native (active) account. Constructed with `new(account_id)` it is a **foreign account caller**, whose method calls are routed through `execute_foreign_procedure` (FPI): +// After — rename the wrapper +#[account(counter_contract::CounterContract)] +struct Counter; -```rust -let counter = CounterContract::new(counter_account_id); +let counter = Counter::new(counter_account_id); let count = counter.get_count(); ``` -**FPI is not limited to note/tx scripts — an account component can call another account through FPI too.** Declare the `#[account(...)]` wrapper in the component crate (and the dependency in `miden-project.toml`) and use it from inside the `#[component] impl`: +Cross-module call sites need the generated trait in scope. A `#[note]` or `#[tx_script]` entrypoint in the same module sees it automatically; a call site in a different module needs to import the trait, which is named after the interface: ```rust -#[account(callee_account::CounterContract)] -struct CalleeAccount; - -#[component] -impl CallerAccount for CallerAccountStorage { - fn read_foreign_count(&self, callee_account_id: AccountId) -> Felt { - let callee = CalleeAccount::new(callee_account_id); - callee.get_count(key) - } -} +use crate::BasicWallet; // the generated trait, not the wrapper struct ``` ### Migration Steps -1. Remove `use crate::bindings::Account;` and any reliance on the auto-generated `Account`. -2. Declare each account explicitly with `#[account(package::Interface)]` (kebab-cased exported interface, not just the package name). -3. Use that type as the `&mut` account parameter of your note/tx-script entrypoints. -4. For FPI, construct the same type with `new(account_id)` and add the callee as a dependency in `miden-project.toml`. +1. Rename any wrapper struct that collides with its interface name. +2. Import the generated trait at cross-module call sites. --- -## Tx-kernel bindings: protocol v0.15 - -### Summary - -The SDK bindings were aligned with VM `0.23` / protocol `0.15` (`miden-field` bumped to `^0.25`). +## Other changes -- **`Felt::new` is now fallible** — it returns `Result` instead of `Felt`. Replace `Felt::new(x)` with `Felt::new(x).unwrap()` (or handle the error). The `felt!(x)` macro is unchanged and remains the preferred constructor for literals. -- **`asset::{create_fungible_asset, create_non_fungible_asset}`** now take a trailing `enable_callbacks: bool` argument. -- **`active_account::{get_balance, get_initial_balance}`** (and the corresponding `ActiveAccount` trait methods) now take an asset key `Word` instead of a faucet `AccountId`. -- **`faucet::{mint, burn}`** no longer return an `Asset`; the `faucet::{mint_value, burn_value}` helpers were removed. Use the returned value-free API to match the tx kernel. -- **`output_note::set_attachment` was removed.** The attachment shape is selected by function instead of a runtime `attachment_kind` argument. +- **Transaction-kernel bindings were renamed and moved** to track the protocol 0.16 surface, and several were removed. +- **Kernel scalars are typed** rather than raw `Felt`, so values that used to be interchangeable now need explicit conversion. +- **`AssetAmount`** is a validated fungible-amount type, matching the protocol and client surfaces. +- **`miden-project.toml` requires an explicit `path`** on `[lib]` and every `[[bin]]`. See [VM & Assembler Changes](./vm-assembler#miden-projecttoml-path-is-mandatory-on-every-target). +- **`#[note]` reserves `get_entrypoint_root`**, so a note struct cannot define a method with that name, and note structs now implement `ToFeltRepr`. +- **`cargo miden new` fetches templates from a release bundle** rather than embedding them. -### Affected Code - -```rust -// before: output_note::set_attachment(note_idx, scheme, kind, attachment); -// after, for a single word: -output_note::add_word_attachment(note_idx, scheme, attachment); -// or `add_attachment` for a commitment, `add_attachment_from_memory` for multiple words. -``` - -:::note Storage encoding note -Scalar `Felt` values stored in `StorageValue` / `StorageMap<_, Felt>` are now packed into the low word limb (`[v, 0, 0, 0]`) instead of the high limb (`[0, 0, 0, v]`), matching protocol v0.15. This is transparent when you recompile and redeploy, but state written by `0.12` code is read back differently by `0.13` code. -::: - -### Migration Steps - -1. Wrap `Felt::new(x)` calls in `.unwrap()` (or handle the `Result`); keep using `felt!(x)` for literals. -2. Add the trailing `enable_callbacks` argument to `asset::create_fungible_asset` / `create_non_fungible_asset`. -3. Pass an asset key `Word` to `active_account::get_balance` / `get_initial_balance` instead of a faucet `AccountId`. -4. Drop the return values of `faucet::mint` / `burn`; remove uses of `mint_value` / `burn_value`. -5. Replace `output_note::set_attachment` with `add_word_attachment` / `add_attachment` / `add_attachment_from_memory`. -6. Re-deploy contracts that persist scalar `Felt` storage — the low-limb packing means `0.12` state is not read back identically. +Additive in this line: typed transaction-script arguments, note constructors, and `println!`-style formatting. --- -## New in 0.13 (no migration required) - -These are additive and do not require changes to existing code: +## Common Errors -- **Sibling component calls** — `#[component(package::Interface, ...)]` on the component trait lets one component call another component deployed on the same account. -- **`println!`** — a `println!` macro (and `debug::println`) for emitting a debug message during execution. +| Error Message | Cause | Solution | +| --- | --- | --- | +| A component method is not callable from a note or script | Missing `#[account_procedure]` | Add it to the trait method declaration. | +| Compile error combining auth and account attributes | They are mutually exclusive | Auth components keep `#[auth_script]` only. | +| Name collision between a wrapper struct and a trait | `#[account(..)]` now generates traits | Rename the wrapper. | +| `no method named ..` at a cross-module call site | The generated trait is not in scope | Import the trait named after the interface. | +| `missing field path` in `miden-project.toml` | Now mandatory | Add `path` to every target. | +| Toolchain version error | MSRV is 1.97 here | Use the higher of the stack's requirements. | diff --git a/docs/builder/migration/index.md b/docs/builder/migration/index.md index f8324370..db60528f 100644 --- a/docs/builder/migration/index.md +++ b/docs/builder/migration/index.md @@ -1,12 +1,12 @@ --- -title: "v0.15 Migration Guide" -description: "Complete guide for upgrading from Miden v0.14 to v0.15" +title: "v0.16 Migration Guide" +description: "Complete guide for upgrading from Miden v0.15 to v0.16" pagination_prev: null --- -# Miden Testnet 0.15.0 +# Miden Testnet 0.16.0 -This guide covers all breaking changes you need to migrate an application to Miden 0.15.0. Like the 0.14 guide, it is intentionally user-facing: you do not need to know or care which internal crate (VM, protocol, client) a change came from. If you are: +This guide covers all breaking changes you need to migrate an application to Miden 0.16.0. Like the 0.15 guide, it is intentionally user-facing: you do not need to know or care which internal crate (VM, protocol, client) a change came from. If you are: - building accounts, notes, or transactions - running a client, web client or React SDK @@ -14,7 +14,7 @@ This guide covers all breaking changes you need to migrate an application to Mid - writing Rust smart contracts with the `miden` SDK - interacting with storage, auth, or RPCs -this document is for you. It folds together the breaking changes from the protocol crates (`miden-base`, `0.14` → `0.15.3`), the VM crates (`miden-vm`, `0.22` → `0.23`), `miden-client` (`0.14` → `0.15`), the Web SDK (`@miden-sdk/*` `0.14` → `0.15`), and the `miden` Rust SDK / compiler (`0.12` → `0.13`). Because `miden-client` and the Web SDK still ship from unified-in-progress `main`/`next` branches, this guide unions the breaking surface from both. +this document is for you. It folds together the breaking changes from the protocol crates (`0.15.3` → `0.16.0`), the VM crates (`miden-vm`, `0.23` → `0.29.1`), `miden-client` (`0.15` → `0.16.0`), the Web SDK (`@miden-sdk/*` `0.15` → `0.16.0`), and the `miden` Rust contract SDK / compiler (`0.13` → `0.14`). --- @@ -24,37 +24,38 @@ Try upgrading first — most projects can start with a dependency update: ```toml title="Cargo.toml" # Replace these -miden-client = "0.14" -miden-client-sqlite-store = "0.14" -miden-protocol = "0.14" -miden-standards = "0.14" -miden-tx = "0.14" -miden-assembly = "0.22" -miden-core = "0.22" -miden-core-lib = "0.22" -miden-processor = "0.22" -miden-prover = "0.22" -miden-crypto = "0.23" - -# With these miden-client = "0.15" miden-client-sqlite-store = "0.15" miden-protocol = "0.15.3" miden-standards = "0.15.3" miden-tx = "0.15.3" +miden-tx-batch-prover = "0.15.3" miden-assembly = "0.23" miden-core = "0.23" miden-core-lib = "0.23" miden-processor = "0.23" miden-prover = "0.23" miden-crypto = "0.25" + +# With these +miden-client = "0.16.0-rc.1" +miden-client-sqlite-store = "0.16.0-rc.1" +miden-protocol = "0.16.0-rc.6" +miden-standards = "0.16.0-rc.6" +miden-tx = "0.16.0-rc.6" +miden-tx-batch = "0.16.0-rc.6" # renamed from miden-tx-batch-prover +miden-assembly = "0.29.1" +miden-core = "0.29.1" +miden-core-lib = "0.29.1" +miden-processor = "0.29.1" +miden-prover = "0.29.1" +miden-crypto = "0.29.1" ``` ```json title="package.json (Web SDK)" { - "@miden-sdk/miden-sdk": "^0.15.0", - "@miden-sdk/react": "^0.15.0", - "miden-idxdb-store": "^0.15.0" + "@miden-sdk/miden-sdk": "0.16.0-rc.2", + "@miden-sdk/react": "0.16.0-rc.2" } ``` @@ -66,41 +67,47 @@ cargo update && cargo build If you encounter errors, continue reading for detailed migration steps. -:::warning 0.14 artifacts do not round-trip -Because the native hash and the MAST/serialization formats changed upstream, **0.14 artifacts (accounts, notes, proofs, serialized stores, `.masl`/`.masp` packages) do not round-trip.** Re-assemble from source and re-sync into a fresh store. +:::warning 0.15 artifacts do not round-trip +The MAST wire format moved `0.0.3` → `0.0.4`, the package format `4.0.0` → `6.0.0`, and the `.masl` library format was removed entirely. Several commitment preimages changed as well. **Re-assemble every package from source and re-sync into a fresh store.** +::: + +:::danger Your local store must be recreated, and your node must be upgraded with your client +Every pre-0.16 SQLite store is rejected — there is no migration path. Browser applications reset their IndexedDB store automatically. Separately, 0.16 clients seal (encrypt) transaction inputs before submission, so a 0.16 client cannot talk to an older node and vice versa. ::: --- :::info Who should read this? This guide is for: -- **Rust client developers** migrating from v0.14 → v0.15 +- **Rust client developers** migrating from v0.15 → v0.16 - **Web SDK developers** using the JavaScript/TypeScript SDK - **Smart contract authors** writing MASM or using protocol APIs - **App developers** using the protocol, standards, or client crates -If you're starting fresh on v0.15, you can skip this guide and go directly to the [Get Started guide](../get-started). +If you're starting fresh on v0.16, you can skip this guide and go directly to the [Get Started guide](../get-started). ::: --- ## At a Glance -Big themes in 0.15: +Big themes in 0.16: | Change | Summary | |--------|---------| -| **Account IDs simplified** | The account ID no longer encodes faucet/regular, mutability, or network mode. The old `AccountType` enum is gone; `AccountStorageMode` is **renamed `AccountType`** (`{ Private, Public }`). Faucet/network-ness now comes from components; the ID version is renamed `0` → `1`. | -| **Note identity split** | The old `NoteId` (recipient + assets) becomes **`NoteDetailsCommitment`**; the new `NoteId` also commits to metadata, and nullifiers now fold in metadata + the attachments commitment — none roundtrip with 0.14. | -| **Multiple attachments per note** | `NoteMetadata` → `PartialNoteMetadata`, `NoteMetadataHeader` → `NoteMetadata`; attachments live on the note/record as a `NoteAttachments` collection (≤ 4). `NoteType` is now 1-bit, default `Private`. | -| **Faucets unified** | `BasicFungibleFaucet` + `NetworkFungibleFaucet` → one **`FungibleFaucet`** (`bon` builder) + `FungibleTokenMetadata` + a `TokenPolicyManager` for mint/burn policies. Amounts are a validated **`AssetAmount`** newtype. | -| **Typed roots everywhere** | `NoteScript::root()` → `NoteScriptRoot`, `TransactionScript::root()` → `TransactionScriptRoot`, `procedure_digest!` → `procedure_root!`, plus `AccountComponentName`. | -| **VM 0.23 / crypto 0.25 are digest-changing** | SMT leaf hashing gains a Poseidon2 domain separator, the MAST wire format bumped `0.0.2` → `0.0.3` (old `.masl`/`.masp` won't load), execution is **sync-first** (`BaseHost`/`SyncHost`; `execute` → `ExecutionOutput`), and `adv_push.N` was removed. | -| **Client RPC rebuilt around `GetAccount`** | `get_account_proof`/`get_account_details` reshaped, `check_nullifiers` removed (use `sync_nullifiers`), most sync methods now require an explicit `block_to`. | -| **Web SDK on the 0.15 protocol surface** | `"network"` storage mode is gone, the WASM `AccountType` narrowed to `{ Private, Public }`, attachments are word-vector-shaped, `Felt`/`Word` throw on overflow, several methods return `undefined`/`string`, `proveTransactionWithProver` is renamed `proveTransaction`, and `storeIdentifier()` went async. | -| **Rust SDK macros reworked** | `#[component]` is now a **trait + a `#[component_storage]` struct**, a `miden-project.toml` manifest is **required**, accounts are declared explicitly with `#[account(package::Interface)]`, and the tx-kernel bindings changed (`Felt::new` fallible, `create_*_asset` takes `enable_callbacks`, `get_balance` takes an asset key `Word`, `set_attachment` removed). | - -If you only skim a few sections, skim **Account Changes**, **Note Changes**, **Assets, Vault & Faucet**, **Hashing, SMT & Crypto Changes**, and **Client Changes**. +| **Fees moved into the auth procedure** | The kernel no longer burns the fee automatically. The auth procedure reads `FeeConversionInfo` from the transaction's auth args and emits a `TX_FEE` note. On a fee-charging chain, requests signed by `AuthSingleSig`/`AuthMultisig` must call `TransactionRequestBuilder::fee_conversion_info(info, salt)`. | +| **MASM gained an explicit module tree** | A `.masm` file is only included if its parent declares it with `mod`/`pub mod` — an undeclared file is *silently dropped*. `use` split into module imports and braced item imports, aliases moved from `->` to `as`, and imports resolve globally. | +| **Account updates became absolute** | `AccountDelta` → **`AccountPatch`** for account updates (`ExecutedTransaction`, `AccountUpdateDetails`, client results). `TransactionSummary::account_delta()` deliberately stays relative. | +| **Signed summaries bind their reference block** | A summary now only authorizes an execution at the block it was derived at, so multisig and offline co-signing flows break silently — every party derives a different summary at its own sync height. Capture a **`ChainAnchor`** and have all of them execute against it. Nothing fails to compile. | +| **Auth is no longer a special builder slot** | `AccountBuilder::with_auth_component` is gone; auth components pass through `with_component(s)` and are found by their `@auth_script` attribute. Keys are wrapped in a new **`Approver`** / `ApproverSet`. `AuthMethod` and `AuthSingleSigAcl` are removed. | +| **Asset identity renamed one level down** | `AssetVaultKey` → **`AssetId`**, and the old `AssetId` → **`AssetClass`**. Because `AssetId` survives with a new meaning, careless renaming compiles and is wrong. | +| **`Library` is gone; `Package` is the only artifact** | `Library`/`KernelLibrary` were deleted, `link_*_library` collapsed into `link_package`, `*_from_dir` became `*_from_root`, and `.masl` no longer exists. MAST `0.0.4` / package `6.0.0` are not backward compatible. | +| **Notes use typed builders, and carry fewer assets** | `XNote::create(..)` → `XNote::builder()…build()?` + `.into()`. **`MAX_ASSETS_PER_NOTE` dropped 64 → 16.** Mint and burn scripts were unified across faucet kinds, changing their roots. | +| **Debug decorators removed** | `debug.*` and `trace` are gone from the language, replaced by `miden::core::debug` procedures — which, unlike the decorators, **print unconditionally**. The client and CLI debug-mode toggles were removed with them. | +| **Commitment preimages changed** | ECDSA public-key commitments, MMR peak commitments, and domain-separated empty-input hashes all changed value. Nothing fails to compile; stored values simply stop matching. | +| **Store and node compatibility both break** | Every pre-0.16 SQLite store must be recreated, and transaction inputs are now sealed, so client and node must be upgraded together. | + +If you only skim a few sections, skim **Transaction Changes**, **Account Changes**, **MASM Changes**, and **Client Changes**. --- @@ -108,18 +115,24 @@ If you only skim a few sections, skim **Account Changes**, **Note Changes**, **A | Component | Required | Tested With | |-----------|----------|-------------| -| Miden VM crates | 0.23+ | 0.23.0 | -| miden-crypto | 0.25+ | 0.25.0 | -| miden-protocol | 0.15+ | 0.15.3 | -| miden-standards | 0.15+ | 0.15.3 | -| miden-client | 0.15+ | 0.15.0 | -| Web SDK (`@miden-sdk/*`) | 0.15+ | 0.15.0 | -| `miden` SDK / compiler | 0.13+ | 0.13.0 | -| Rust (client) | 1.93+ | 1.93.0 | -| Rust (base crates) | 1.90+ | 1.90.0 | - -:::note `miden-prover`, not `miden-prove` -The prover crate is **`miden-prover`** in this line — it is *not* `miden-prove`. Keep depending on `miden-prover`. +| Miden VM crates | 0.29+ | 0.29.1 | +| miden-crypto | 0.29+ | 0.29.1 | +| miden-protocol | 0.16+ | 0.16.0-rc.6 | +| miden-standards | 0.16+ | 0.16.0-rc.6 | +| miden-client | 0.16+ | 0.16.0-rc.1 | +| Web SDK (`@miden-sdk/*`) | 0.16+ | 0.16.0-rc.2 | +| `miden` contract SDK | 0.14+ | 0.14.0-rc.1 | +| `midenc` compiler | 0.10+ | 0.10.0-rc.1 | +| Rust (client) | 1.96+ | 1.96 | +| Rust (protocol / VM) | 1.96.1+ | 1.96.1 | +| Rust (contract SDK / compiler) | 1.97+ | 1.97 | + +:::note Pin the exact pre-release version +The 0.16 protocol and client crates currently publish as `0.16.0-rc.N`. Cargo does not match a pre-release against a plain `"0.16"` requirement, so pin the exact string until the final release is published. +::: + +:::note The contract toolchain lags the rest of the line +`midenc` and the `miden` contract SDK build against protocol `0.16.0-alpha.4` and VM `0.25`, not the protocol `0.16.0-rc` and VM `0.29.1` used by the client and node. Artifacts still load — the MAST and package formats are compatible across those VM versions — but the protocol API surface the compiler sees is an earlier snapshot. Its MSRV is also higher, at 1.97. ::: --- @@ -130,16 +143,16 @@ Work through these sections in order for a complete migration: | Section | Topics | |---------|--------| -| [1. Imports & Dependencies](./imports-dependencies) | Crate bumps, package.json, MSRV 1.93, no round-trip of 0.14 artifacts | -| [2. Hashing, SMT & Crypto Changes](./hashing-stack) | Poseidon2-domain-separated SMT leaves, `miden-crypto` 0.25 renames, `PartialSmt` / `LargeSmt` / 0.24 API breaks | -| [3. Account Changes](./account-changes) | `AccountType` removed/renamed, network-account allowlist, `procedure_root!`, typed roots | -| [4. Note Changes](./note-changes) | `NoteDetailsCommitment`, `PartialNoteMetadata`, multiple attachments, 1-bit `NoteType`, nullifier change, PSWAP | -| [5. Assets, Vault & Faucet](./asset-vault-faucet) | `AssetAmount`, unified `FungibleFaucet`, `AssetVaultKey`, `AssetComposition` | -| [6. Transaction Changes](./transaction-changes) | `fee_faucet_id`, `TransactionScriptRoot`, `ProvenBatch::new_unchecked` | -| [7. Client Changes](./client-changes) | `GetAccount` surface, `sync_nullifiers`, `TokenPolicyManager`, Web/React/CLI changes | -| [8. MASM Changes](./masm-changes) | `metadata_into_*` renames, trimmed kernel outputs, `adv_push.N` removed | -| [9. VM & Assembler Changes](./vm-assembler) | Sync-first execution, `prove_sync`, stricter assembly, MAST wire format `0.0.3` | -| [10. Rust SDK & Compiler Changes](./rust-sdk-compiler) | `#[component]` trait + storage struct, required `miden-project.toml`, explicit `#[account(...)]`, v0.15 tx-kernel bindings | +| [1. Imports & Dependencies](./imports-dependencies) | Crate bumps, VM 0.23 → 0.29.1, MSRV 1.96, artifacts that must be rebuilt | +| [2. Hashing & Crypto Changes](./hashing-crypto) | ECDSA public-key commitments, MMR peaks binding the leaf count, empty domain-separated hashing | +| [3. Account Changes](./account-changes) | `with_auth_component` removed, `Approver`/`ApproverSet`, component name changes, `AccountPatch` | +| [4. Note Changes](./note-changes) | Typed note builders, `MAX_ASSETS_PER_NOTE` 64 → 16, unified mint/burn scripts | +| [5. Assets, Vault & Faucet](./asset-vault-faucet) | `AssetVaultKey` → `AssetId`, old `AssetId` → `AssetClass`, split faucet factories | +| [6. Transaction Changes](./transaction-changes) | Fees paid by the auth procedure, sealed transaction inputs, `TransactionSummary`, `ChainAnchor` | +| [7. Client Changes](./client-changes) | Store recreation, node compatibility, chain-anchored execution, Rust/Web/React/CLI changes | +| [8. MASM Changes](./masm-changes) | `mod` declarations, new import syntax, debug decorators removed, protocol procedure moves | +| [9. VM & Assembler Changes](./vm-assembler) | `Library` → `Package`, MAST `0.0.4`, `ExecutionClaim`, `miden-project.toml` | +| [10. Rust Contract SDK & Compiler](./rust-sdk-compiler) | `#[account_procedure]`, `#[account(..)]` generating traits, toolchain version skew | --- @@ -147,27 +160,33 @@ Work through these sections in order for a complete migration: Complete these steps to verify your migration: -- [ ] Bump all Miden crate versions in `Cargo.toml` per section 1 (and `@miden-sdk/*` to `^0.15.0` together) -- [ ] Update the client toolchain to Rust 1.93+ -- [ ] Re-assemble all `.masl` and `.masp` files from source (MAST wire format `0.0.3`) -- [ ] Re-sync into a fresh store; discard cached commitments, note IDs, nullifiers, and proofs from 0.14 -- [ ] Re-derive persisted SMT roots / leaf digests / `PartialSmt` values under `miden-crypto` 0.25 -- [ ] *(If you implement a custom `LargeSmt` storage backend)* move reads to `SmtStorageReader` and add `type Reader` + `reader()` to your `SmtStorage` impl -- [ ] *(If you use `miden-crypto` directly)* apply the 0.24 API breaks (`WORD_SIZE*` → `Word::NUM_ELEMENTS` / `Word::SERIALIZED_SIZE`, `LexicographicWord` → `Word`, `Felt` deref removed, `StarkProof` log trace heights + `air_order`) -- [ ] Replace the old `AccountType` / `AccountStorageMode` usage with the new `AccountType` (`Private`/`Public`) -- [ ] Rename note "ids without metadata" to `NoteDetailsCommitment`; recompute note IDs and nullifiers -- [ ] Move to `PartialNoteMetadata` + `NoteAttachments`; audit `NoteType` (now 1-bit, default `Private`) -- [ ] Switch faucets to `FungibleFaucet::builder()` + `TokenPolicyManager`; wrap amounts in `AssetAmount` -- [ ] Replace `get_account_proof` with `get_account(GetAccountRequest…)` and `check_nullifiers` with `sync_nullifiers` -- [ ] Pass explicit `block_to` to the sync methods that now require it -- [ ] Web: drop `"network"` storage, move faucet checks onto `Account`, reshape attachments, guard `Felt`/`Word` construction -- [ ] Split your `Host` impl into `BaseHost` + `SyncHost`; handle `ExecutionOutput` -- [ ] *(If you write Rust contracts with the `miden` SDK)* rewrite components as `#[component_storage]` + `#[component] trait` + `#[component] impl`; add a `miden-project.toml`; declare accounts with `#[account(package::Interface)]`; update the v0.15 tx-kernel bindings (`Felt::new().unwrap()`, `enable_callbacks`, asset-key `get_balance`, `add_*_attachment`) +- [ ] Bump all Miden crate versions per section 1, pinning the exact `0.16.0-rc.N` strings, and rename `miden-tx-batch-prover` to `miden-tx-batch` +- [ ] Bump `@miden-sdk/miden-sdk` and `@miden-sdk/react` together; drop any `miden-idxdb-store` dependency +- [ ] Update the toolchain to Rust 1.96 (1.97 if you also build Rust contracts) +- [ ] Re-assemble every `.masp` from source and delete cached `MastForest` blobs; `.masl` no longer exists +- [ ] **Delete and recreate your local store**, then re-sync — export private note files first +- [ ] **Upgrade your node together with your client** — sealed and plaintext submissions are mutually incompatible +- [ ] Add `mod` / `pub mod` declarations so every `.masm` file is reachable from your project root +- [ ] Rewrite `pub use a::b::c` as `pub use {c} from a::b`, and `use x->y` as `use x as y` +- [ ] Replace `debug.*` / `trace` decorators with `miden::core::debug` procedures, and strip them from production code +- [ ] Declare fee conversion info on transactions if your chain charges a fee, and fund the paying account with the fee asset +- [ ] Move auth components out of `with_auth_component` and wrap keys in `Approver` / `ApproverSet` +- [ ] Rename `AssetId` → `AssetClass` **first**, then `AssetVaultKey` → `AssetId` +- [ ] Replace `account_delta()` with `account_patch()` — but leave `TransactionSummary::account_delta()` alone +- [ ] If you collect signatures over a summary across clients, capture a `ChainAnchor` and derive, verify, and execute the transaction against it +- [ ] Rewrite `XNote::create(..)` calls as builders, and cap notes at 16 assets +- [ ] Recompute stored ECDSA public-key commitments, MMR peak commitments, and empty domain-separated hashes +- [ ] Replace `Library`/`KernelLibrary` with `Package`, and `link_*_library` with `link_package` +- [ ] Add an explicit `path` to every `[lib]` and `[[bin]]` in `miden-project.toml` +- [ ] Build an `ExecutionClaim` and call `verify(proof, claim)`; discard proofs serialized under 0.15 +- [ ] CLI: rename `send` to `transfer`, `--with-code` to `--inspect`, and `id` to `address` in `token_symbol_map.toml` +- [ ] CLI: re-check every `call` invocation — arguments are now counted in field elements +- [ ] *(If you write Rust contracts)* mark component trait methods with `#[account_procedure]` and import the traits generated by `#[account(..)]` - [ ] Run `cargo build` — **no errors** - [ ] Run `cargo test` — **all tests pass** :::tip You're done! -If your project builds and all tests pass, you've successfully migrated to v0.15. +If your project builds and all tests pass, you've successfully migrated to v0.16. ::: --- @@ -176,5 +195,5 @@ If your project builds and all tests pass, you've successfully migrated to v0.15 - **Telegram:** [Build on Miden](https://t.me/BuildOnMiden) — technical discussion and support. - **Forum:** [Miden discussions](https://github.com/0xMiden/miden-node/discussions) — longer-form questions and design discussion. -- **GitHub issues:** file against the relevant repo — [`miden-client`](https://github.com/0xMiden/miden-client/issues), [`web-sdk`](https://github.com/0xMiden/web-sdk/issues), [`protocol`](https://github.com/0xMiden/protocol/issues), or [`miden-vm`](https://github.com/0xMiden/miden-vm/issues). +- **GitHub issues:** file against the relevant repo — [`rust-sdk`](https://github.com/0xMiden/rust-sdk/issues), [`web-sdk`](https://github.com/0xMiden/web-sdk/issues), [`protocol`](https://github.com/0xMiden/protocol/issues), [`miden-vm`](https://github.com/0xMiden/miden-vm/issues), or [`compiler`](https://github.com/0xMiden/compiler/issues). - **Changelogs:** the per-repo `CHANGELOG.md` files carry the full list of changes, including non-breaking features and fixes omitted from this guide. diff --git a/sidebars.ts b/sidebars.ts index 8cb1deac..7da14b72 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -320,7 +320,7 @@ const sidebars: SidebarsConfig = { link: { type: "doc", id: "builder/migration/index" }, items: [ "builder/migration/imports-dependencies", - "builder/migration/hashing-stack", + "builder/migration/hashing-crypto", "builder/migration/account-changes", "builder/migration/note-changes", "builder/migration/asset-vault-faucet",