diff --git a/.github/actions/workspace-release/action.yml b/.github/actions/workspace-release/action.yml new file mode 100644 index 0000000000..f5e16fab5a --- /dev/null +++ b/.github/actions/workspace-release/action.yml @@ -0,0 +1,107 @@ +name: workspace-release +description: "Dry-run or publish Rust workspace crates using cargo" + +inputs: + mode: + description: "Release mode: dry-run or publish" + required: true + default: "dry-run" + verify-main-head: + description: "If true, ensure the triggering SHA matches main's HEAD" + required: false + default: "false" + +runs: + using: "composite" + steps: + # Optional: guard that release happens from latest main + - name: Verify tag matches main HEAD + if: ${{ inputs.verify-main-head == 'true' }} + shell: bash + run: | + git fetch origin main --depth=1 + main_sha="$(git rev-parse origin/main)" + tag_sha="$(git rev-parse HEAD)" + + echo "main_sha=$main_sha" + echo "tag_sha=$tag_sha" + + if [ "$main_sha" != "$tag_sha" ]; then + echo "::error::The release/tag commit does not match origin/main HEAD. Aborting." + exit 1 + fi + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry and git index + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Cleanup large tools for build space + uses: ./.github/actions/cleanup-runner + + # Install cargo-msrv for MSRV checks + # Using binstall with --force to avoid stale cached binaries (see PR #2234) + - name: Install cargo-binstall + uses: taiki-e/install-action@v2 + with: + tool: cargo-binstall + + - name: Install cargo-msrv + shell: bash + run: cargo binstall --no-confirm --force cargo-msrv + + # Keep your existing MSRV check + # PATH export required for check-msrv.sh subprocess (see PR #2234) + - name: Check MSRV + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + chmod +x scripts/check-msrv.sh + ./scripts/check-msrv.sh + + # Clean packaging directory to avoid stale/corrupted tmp-registry state + - name: Clean packaging directory + shell: bash + run: | + echo "Cleaning target/package directory to ensure fresh state" + rm -rf target/package + + # Clear cargo registry to ensure fresh resolution during verification. + # This prevents issues where cached metadata from previous runs + # might interfere with workspace dependency feature resolution + # during the verification step (related to cargo#14283, cargo#14789). + # Specifically, this ensures the temp registry used during workspace + # publish verification doesn't conflict with cached crates.io data. + - name: Clear cargo registry for fresh resolution + if: ${{ inputs.mode == 'dry-run' }} + shell: bash + run: | + echo "Clearing cargo registry for fresh resolution" + rm -rf ~/.cargo/registry/cache + rm -rf ~/.cargo/registry/index + rm -rf ~/.cargo/registry/src + + # Dry-run vs real publish + - name: Dry-run workspace publish + if: ${{ inputs.mode == 'dry-run' }} + shell: bash + run: | + echo "Running cargo publish --workspace --dry-run" + cargo publish --workspace --dry-run + + - name: Publish workspace crates + if: ${{ inputs.mode == 'publish' }} + shell: bash + env: + CARGO_REGISTRY_TOKEN: ${{ env.CARGO_REGISTRY_TOKEN }} + run: | + echo "Publishing workspace crates to crates.io" + cargo publish --workspace diff --git a/.github/workflows/release-plz-dry-run.yml b/.github/workflows/release-plz-dry-run.yml deleted file mode 100644 index be787b1270..0000000000 --- a/.github/workflows/release-plz-dry-run.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Release-plz (dry-run) - -permissions: - contents: read - -on: - push: - branches: [main, next] - -concurrency: - group: "${{ github.workflow }} @ ${{ github.ref }}" - cancel-in-progress: true - -jobs: - release-plz-dry-run-release: - name: Release-plz dry-run - runs-on: ubuntu-latest - if: ${{ github.repository_owner == '0xMiden' }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Cleanup large tools for build space - uses: ./.github/actions/cleanup-runner - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y jq - - name: Update Rust toolchain - run: rustup update --no-self-update - - uses: Swatinem/rust-cache@v2 - - uses: taiki-e/install-action@v2 - with: - tool: cargo-binstall - - name: Install cargo-msrv - run: cargo binstall --no-confirm --force cargo-msrv - - name: Check MSRV for each workspace member - run: | - export PATH="$HOME/.cargo/bin:$PATH" - ./scripts/check-msrv.sh - - name: Run release-plz - uses: release-plz/action@v0.5 - with: - command: release --dry-run - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml deleted file mode 100644 index a9de6bdeb8..0000000000 --- a/.github/workflows/release-plz.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Release-plz (main) - -permissions: - contents: read - -on: - release: - types: [published] - -jobs: - release-plz-release: - name: Release-plz release - runs-on: ubuntu-latest - if: ${{ github.repository_owner == '0xMiden' }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: main - # Ensure the release tag refers to the latest commit on main. - # Compare the commit SHA that triggered the workflow with the HEAD of the branch we just - # checked out (main). - - name: Verify release was triggered from main HEAD - run: | - tag_sha="${{ github.sha }}" - main_sha="$(git rev-parse HEAD)" - - echo "Tag points to: $tag_sha" - echo "Current main HEAD is: $main_sha" - - if [ "$tag_sha" != "$main_sha" ]; then - echo "::error::The release tag was not created from the latest commit on main. Aborting." - exit 1 - fi - echo "Release tag matches main HEAD — continuing." - - name: Cleanup large tools for build space - uses: ./.github/actions/cleanup-runner - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y jq - - name: Update Rust toolchain - run: rustup update --no-self-update - - uses: Swatinem/rust-cache@v2 - - uses: taiki-e/install-action@v2 - with: - tool: cargo-binstall - - name: Install cargo-msrv - run: cargo binstall --no-confirm --force cargo-msrv - - name: Check MSRV for each workspace member - run: | - export PATH="$HOME/.cargo/bin:$PATH" - ./scripts/check-msrv.sh - - name: Run release-plz - uses: release-plz/action@v0.5 - with: - command: release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/workspace-dry-run.yml b/.github/workflows/workspace-dry-run.yml new file mode 100644 index 0000000000..874417bd91 --- /dev/null +++ b/.github/workflows/workspace-dry-run.yml @@ -0,0 +1,33 @@ +name: Workspace release dry-run + +on: + push: + branches: + - main + - next + +permissions: + contents: read + id-token: write # Required for OIDC token exchange + +concurrency: + group: "${{ github.workflow }} @ ${{ github.ref }}" + cancel-in-progress: true + +jobs: + release-dry-run: + if: ${{ github.repository_owner == '0xMiden' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Dry-run workspace release + uses: ./.github/actions/workspace-release + with: + mode: "dry-run" + verify-main-head: "false" + # ref left blank: uses the pushed ref diff --git a/.github/workflows/workspace-publish.yml b/.github/workflows/workspace-publish.yml new file mode 100644 index 0000000000..27d2737ff2 --- /dev/null +++ b/.github/workflows/workspace-publish.yml @@ -0,0 +1,34 @@ +name: Publish workspace to crates.io + +on: + release: + types: [published] + +permissions: + contents: read + id-token: write # Required for OIDC token exchange + +jobs: + publish: + if: ${{ github.repository_owner == '0xMiden' }} + runs-on: ubuntu-latest + environment: release # Optional: for enhanced security + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: main + + - name: Authenticate with crates.io + uses: rust-lang/crates-io-auth-action@v1 + id: auth + + - name: Publish workspace crates + uses: ./.github/actions/workspace-release + with: + mode: "publish" + verify-main-head: "true" + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} diff --git a/CHANGELOG.md b/CHANGELOG.md index c0d0fa0d02..5025737639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.14.0 (TBD) + +### Features + +- Enable `CodeBuilder` to add advice map entries to compiled scripts ([#2275](https://github.com/0xMiden/miden-base/pull/2275)). +- Added `BlockNumber::MAX` constant to represent the maximum block number ([#2324](https://github.com/0xMiden/miden-base/pull/2324)). +- Added single-word `Array` standard ([#2203](https://github.com/0xMiden/miden-base/pull/2203)). + +### Changes + +- [BREAKING] Renamed `WellKnownComponent` to `StandardAccountComponent`, `WellKnownNote` to `StandardNote`, and `WellKnownNoteAttachment` to `StandardNoteAttachment` ([#2332](https://github.com/0xMiden/miden-base/pull/2332)). +- Skip requests to the `DataStore` for asset vault witnesses which are already in transaction inputs ([#2298](https://github.com/0xMiden/miden-base/pull/2298)). +- [BREAKING] refactored `TransactionAuthenticator::get_public_key()` method to return `Arc `instead of `&PublicKey` ([#2304](https://github.com/0xMiden/miden-base/pull/2304)). +- [BREAKING] Renamed `NoteInputs` to `NoteStorage` to better reflect that values are stored data associated with a note rather than inputs ([#1662](https://github.com/0xMiden/miden-base/issues/1662), [#2316](https://github.com/0xMiden/miden-base/issues/2316)). +- Removed `NoteType::Encrypted` ([#2315](https://github.com/0xMiden/miden-base/pull/2315)). + +# 0.13.3 (TBD) + +- Added standards for working with `NetworkAccountTarget` attachments ([#2338](https://github.com/0xMiden/miden-base/pull/2338)). + ## 0.13.2 (2026-01-21) - Make transaction executor respect debug mode settings ([#2327](https://github.com/0xMiden/miden-base/pull/2327)). diff --git a/Cargo.lock b/Cargo.lock index ea395e01a4..d1d589c5df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1381,7 +1381,7 @@ dependencies = [ [[package]] name = "miden-agglayer" -version = "0.13.2" +version = "0.14.0" dependencies = [ "fs-err", "miden-agglayer", @@ -1449,7 +1449,7 @@ dependencies = [ [[package]] name = "miden-block-prover" -version = "0.13.2" +version = "0.14.0" dependencies = [ "miden-protocol", "thiserror", @@ -1641,7 +1641,7 @@ dependencies = [ [[package]] name = "miden-protocol" -version = "0.13.2" +version = "0.14.0" dependencies = [ "anyhow", "assert_matches", @@ -1680,7 +1680,7 @@ dependencies = [ [[package]] name = "miden-protocol-macros" -version = "0.13.2" +version = "0.14.0" dependencies = [ "miden-protocol", "proc-macro2", @@ -1704,7 +1704,7 @@ dependencies = [ [[package]] name = "miden-standards" -version = "0.13.2" +version = "0.14.0" dependencies = [ "anyhow", "assert_matches", @@ -1718,12 +1718,13 @@ dependencies = [ "rand", "regex", "thiserror", + "tokio", "walkdir", ] [[package]] name = "miden-testing" -version = "0.13.2" +version = "0.14.0" dependencies = [ "anyhow", "assert_matches", @@ -1743,6 +1744,7 @@ dependencies = [ "rand", "rand_chacha", "rstest", + "thiserror", "tokio", "winter-rand-utils", "winterfell", @@ -1750,7 +1752,7 @@ dependencies = [ [[package]] name = "miden-tx" -version = "0.13.2" +version = "0.14.0" dependencies = [ "anyhow", "assert_matches", @@ -1767,7 +1769,7 @@ dependencies = [ [[package]] name = "miden-tx-batch-prover" -version = "0.13.2" +version = "0.14.0" dependencies = [ "miden-protocol", "miden-tx", diff --git a/Cargo.toml b/Cargo.toml index d174bc9089..b38eedb331 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ homepage = "https://miden.xyz" license = "MIT" repository = "https://github.com/0xMiden/miden-base" rust-version = "1.90" -version = "0.13.2" +version = "0.14.0" [profile.release] codegen-units = 1 @@ -42,14 +42,14 @@ lto = true [workspace.dependencies] # Workspace crates -miden-agglayer = { default-features = false, path = "crates/miden-agglayer", version = "0.13" } -miden-block-prover = { default-features = false, path = "crates/miden-block-prover", version = "0.13" } -miden-protocol = { default-features = false, path = "crates/miden-protocol", version = "0.13" } -miden-protocol-macros = { default-features = false, path = "crates/miden-protocol-macros", version = "0.13" } -miden-standards = { default-features = false, path = "crates/miden-standards", version = "0.13" } -miden-testing = { default-features = false, path = "crates/miden-testing", version = "0.13" } -miden-tx = { default-features = false, path = "crates/miden-tx", version = "0.13" } -miden-tx-batch-prover = { default-features = false, path = "crates/miden-tx-batch-prover", version = "0.13" } +miden-agglayer = { default-features = false, path = "crates/miden-agglayer", version = "0.14" } +miden-block-prover = { default-features = false, path = "crates/miden-block-prover", version = "0.14" } +miden-protocol = { default-features = false, path = "crates/miden-protocol", version = "0.14" } +miden-protocol-macros = { default-features = false, path = "crates/miden-protocol-macros", version = "0.14" } +miden-standards = { default-features = false, path = "crates/miden-standards", version = "0.14" } +miden-testing = { default-features = false, path = "crates/miden-testing", version = "0.14" } +miden-tx = { default-features = false, path = "crates/miden-tx", version = "0.14" } +miden-tx-batch-prover = { default-features = false, path = "crates/miden-tx-batch-prover", version = "0.14" } # Miden dependencies miden-air = { default-features = false, version = "0.20" } @@ -67,9 +67,11 @@ miden-verifier = { default-features = false, version = "0.20" } # External dependencies anyhow = { default-features = false, features = ["backtrace", "std"], version = "1.0" } assert_matches = { default-features = false, version = "1.5" } +fs-err = { default-features = false, version = "3" } primitive-types = { default-features = false, version = "0.14" } rand = { default-features = false, version = "0.9" } rand_chacha = { default-features = false, version = "0.9" } rstest = { version = "0.26" } +serde = { default-features = false, version = "1.0" } thiserror = { default-features = false, version = "2.0" } tokio = { default-features = false, features = ["sync"], version = "1" } diff --git a/README.md b/README.md index df78fec0f4..2f84bd027d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Miden is a zero-knowledge rollup for high-throughput and private applications. M If you want to join the technical discussion or learn more about the project, please check out -- the [Documentation](https://0xMiden.github.io/miden-docs). +- the [Documentation](https://docs.miden.xyz/miden-base/). - the [Telegram](https://t.me/BuildOnMiden) - the [Repo](https://github.com/0xMiden) - the [Roadmap](https://miden.xyz/roadmap) @@ -71,7 +71,7 @@ Some of the functions in this project are computationally intensive and may take ## Documentation -The documentation in the `docs/` folder is built using Docusaurus and is automatically absorbed into the main [miden-docs](https://github.com/0xMiden/miden-docs) repository for the main documentation website. Changes to the `next` branch trigger an automated deployment workflow. The docs folder requires npm packages to be installed before building. +The documentation in the `docs/` folder is built using Docusaurus and is automatically absorbed into the main [miden-docs](https://docs.miden.xyz/miden-base/) repository for the main documentation website. Changes to the `next` branch trigger an automated deployment workflow. The docs folder requires npm packages to be installed before building. ## License diff --git a/bin/bench-note-checker/Cargo.toml b/bin/bench-note-checker/Cargo.toml index 630d1b9189..09f0485573 100644 --- a/bin/bench-note-checker/Cargo.toml +++ b/bin/bench-note-checker/Cargo.toml @@ -19,8 +19,8 @@ miden-tx = { workspace = true } # External dependencies anyhow = { workspace = true } -serde = { features = ["derive"], version = "1.0" } -tokio = { features = ["macros", "rt"], version = "1.0" } +serde = { features = ["derive"], workspace = true } +tokio = { features = ["macros", "rt"], workspace = true } [dev-dependencies] criterion = { features = ["async_tokio", "html_reports"], version = "0.6" } diff --git a/bin/bench-transaction/Cargo.toml b/bin/bench-transaction/Cargo.toml index 9b07fccd84..e932ad28fa 100644 --- a/bin/bench-transaction/Cargo.toml +++ b/bin/bench-transaction/Cargo.toml @@ -24,7 +24,7 @@ miden-tx = { workspace = true } # External dependencies anyhow = { workspace = true } -serde = { features = ["derive"], version = "1.0" } +serde = { features = ["derive"], workspace = true } serde_json = { features = ["preserve_order"], package = "serde_json", version = "1.0" } tokio = { features = ["macros", "rt"], workspace = true } diff --git a/crates/miden-agglayer/Cargo.toml b/crates/miden-agglayer/Cargo.toml index 0bae5deba1..019379d7fb 100644 --- a/crates/miden-agglayer/Cargo.toml +++ b/crates/miden-agglayer/Cargo.toml @@ -31,7 +31,7 @@ miden-utils-sync = { workspace = true } miden-agglayer = { features = ["testing"], path = "." } [build-dependencies] -fs-err = { version = "3" } +fs-err = { workspace = true } miden-assembly = { workspace = true } miden-core = { workspace = true } miden-core-lib = { workspace = true } diff --git a/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm b/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm index 4c12783065..a0822cb4bf 100644 --- a/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm +++ b/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm @@ -36,7 +36,7 @@ const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_1 = 552 # P2ID output note constants const P2ID_SCRIPT_ROOT = [13362761878458161062, 15090726097241769395, 444910447169617901, 3558201871398422326] -const P2ID_NOTE_NUM_INPUTS = 2 +const P2ID_NOTE_NUM_STORAGE_ITEMS = 2 const OUTPUT_NOTE_TYPE_PUBLIC = 1 const EXECUTION_HINT_ALWAYS = 1 const OUTPUT_NOTE_AUX = 0 @@ -146,17 +146,17 @@ proc build_p2id_output_note swapw mem_loadw_be.OUTPUT_NOTE_SERIAL_NUM_MEM_ADDR # => [SERIAL_NUM, SCRIPT_ROOT] - push.P2ID_NOTE_NUM_INPUTS - # => [num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT] + push.P2ID_NOTE_NUM_STORAGE_ITEMS + # => [note_num_storage_items, SERIAL_NUM, SCRIPT_ROOT] exec.get_destination_account_id - # => [account_id_prefix, account_id_suffix, num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT] + # => [account_id_prefix, account_id_suffix, note_num_storage_items, SERIAL_NUM, SCRIPT_ROOT] mem_store.0 mem_store.1 - # => [num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT] + # => [note_num_storage_items, SERIAL_NUM, SCRIPT_ROOT] push.OUTPUT_NOTE_INPUTS_MEM_ADDR - # => [inputs_ptr = 0, num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT] + # => [storage_ptr = 0, note_num_storage_items, SERIAL_NUM, SCRIPT_ROOT] exec.note::build_recipient # => [RECIPIENT] diff --git a/crates/miden-agglayer/asm/bridge/bridge_out.masm b/crates/miden-agglayer/asm/bridge/bridge_out.masm index 3c53b62763..449955ce6c 100644 --- a/crates/miden-agglayer/asm/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/bridge/bridge_out.masm @@ -13,7 +13,7 @@ const LOCAL_EXIT_TREE_SLOT=word("miden::agglayer::let") const BURN_NOTE_ROOT = [15615638671708113717, 1774623749760042586, 2028263167268363492, 12931944505143778072] const PUBLIC_NOTE=1 -const NUM_BURN_NOTE_INPUTS=0 +const BURN_NOTE_NUM_STORAGE_ITEMS=0 const BURN_ASSET_MEM_PTR=24 #! Computes the SERIAL_NUM of the outputted BURN note. @@ -68,8 +68,8 @@ proc create_burn_note push.BURN_NOTE_ROOT swapw # => [SERIAL_NUM, SCRIPT_ROOT] - push.NUM_BURN_NOTE_INPUTS push.0 - # => [inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + push.BURN_NOTE_NUM_STORAGE_ITEMS push.0 + # => [storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT] exec.note::build_recipient # => [RECIPIENT] diff --git a/crates/miden-agglayer/asm/bridge/crypto_utils.masm b/crates/miden-agglayer/asm/bridge/crypto_utils.masm index 7796c1f94f..829a90674d 100644 --- a/crates/miden-agglayer/asm/bridge/crypto_utils.masm +++ b/crates/miden-agglayer/asm/bridge/crypto_utils.masm @@ -59,7 +59,7 @@ end #! Operand stack: [is_valid] #! #! Where: -#! - RPO_CLAIM_NOTE_INPUTS_COMMITMENT is the RPO hash commitment of all claim note inputs +#! - RPO_CLAIM_NOTE_STORAGE_COMMITMENT is the RPO hash commitment of all claim note storage #! - leafType is the leaf type: [0] transfer Ether / ERC20 tokens, [1] message #! - originNetwork is the origin network identifier (u32 as Felt) #! - originAddress is the origin address (5 felts representing address) diff --git a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm index 80bdcfbb7f..79075a2a09 100644 --- a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm +++ b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm @@ -7,13 +7,13 @@ use miden::standards::wallets::basic->basic_wallet # CONSTANTS # ================================================================================================= -const B2AGG_NOTE_INPUTS_COUNT=6 +const B2AGG_NOTE_NUM_STORAGE_ITEMS=6 # ERRORS # ================================================================================================= const ERR_B2AGG_WRONG_NUMBER_OF_ASSETS="B2AGG script requires exactly 1 note asset" -const ERR_B2AGG_WRONG_NUMBER_OF_INPUTS="B2AGG script expects exactly 6 note inputs" +const ERR_B2AGG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS="B2AGG script expects exactly 6 note storage items" #! Bridge-to-AggLayer (B2AGG) note script: bridges assets from Miden to an AggLayer-connected chain. #! @@ -26,7 +26,7 @@ const ERR_B2AGG_WRONG_NUMBER_OF_INPUTS="B2AGG script expects exactly 6 note inpu #! Inputs: [] #! Outputs: [] #! -#! Note inputs are assumed to be as follows: +#! Note storage is assumed to be as follows: #! - destination_network: u32 value representing the target chain ID #! - destination_address: split into 5 u32 values representing a 20-byte Ethereum address: #! - destination_address_0: bytes 0-3 @@ -58,11 +58,11 @@ begin exec.basic_wallet::add_assets_to_account # => [pad(16)] else - # Store note inputs -> mem[8..14] - push.8 exec.active_note::get_inputs - # => [num_inputs, dest_ptr, pad(16)] + # Store note storage -> mem[8..14] + push.8 exec.active_note::get_storage + # => [num_storage_items, dest_ptr, pad(16)] - push.B2AGG_NOTE_INPUTS_COUNT assert_eq.err=ERR_B2AGG_WRONG_NUMBER_OF_INPUTS drop + push.B2AGG_NOTE_NUM_STORAGE_ITEMS assert_eq.err=ERR_B2AGG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS drop # => [pad(16)] # Store note assets -> mem[0..4] @@ -73,7 +73,7 @@ begin push.1 assert_eq.err=ERR_B2AGG_WRONG_NUMBER_OF_ASSETS drop # => [pad(16)] - # load the 6 B2AGG note input felts as two words + # load the 6 B2AGG felts from note storage as two words mem_loadw_be.12 swapw.2 mem_loadw_be.8 swapw # => [EMPTY_WORD, dest_network, dest_address(5), pad(6)] diff --git a/crates/miden-agglayer/asm/note_scripts/CLAIM.masm b/crates/miden-agglayer/asm/note_scripts/CLAIM.masm index 83c41a65bc..57c356ada2 100644 --- a/crates/miden-agglayer/asm/note_scripts/CLAIM.masm +++ b/crates/miden-agglayer/asm/note_scripts/CLAIM.masm @@ -29,8 +29,8 @@ const ERR_CLAIM_TARGET_ACCT_MISMATCH = "CLAIM's target account address and trans #! Asserts that the consuming account matches the target agglayer faucet account. #! #! This procedure ensures that only the specified agglayer faucet account can consume -#! this CLAIM note. It assumes that the note inputs have already been loaded into memory -#! via active_note::get_inputs. +#! this CLAIM note. It assumes that the note storage has already been loaded into memory +#! via active_note::get_storage. #! #! Inputs: [] #! Output: [] @@ -38,7 +38,7 @@ const ERR_CLAIM_TARGET_ACCT_MISMATCH = "CLAIM's target account address and trans #! Panics if: #! - The consuming account ID does not match the target faucet account ID stored in memory proc assert_aggfaucet_is_consumer - # Load target faucet ID (assumes active_note::get_inputs has been called) + # Load target faucet ID (assumes active_note::get_storage has been called) mem_load.TARGET_FAUCET_SUFFIX_MEM_ADDR mem_load.TARGET_FAUCET_PREFIX_MEM_ADDR # => [target_faucet_prefix, target_faucet_suffix] @@ -137,7 +137,7 @@ end #! Agglayer Faucet CLAIM script: claims assets by calling the agglayer faucet's claim function. #! #! This note can only be consumed by the specific agglayer faucet account whose ID is provided -#! in the note inputs (target_faucet_account_id). Upon consumption, it will create a P2ID note. +#! in the note storage (target_faucet_account_id). Upon consumption, it will create a P2ID note. #! #! Requires that the account exposes: #! - agglayer::agglayer_faucet::claim procedure. @@ -145,7 +145,7 @@ end #! Inputs: [ARGS, pad(12)] #! Outputs: [pad(16)] #! -#! NoteInputs layout (575 felts total): +#! NoteStorage layout (575 felts total): #! - smtProofLocalExitRoot [0..255] : 256 felts #! - smtProofRollupExitRoot [256..511]: 256 felts #! - globalIndex [512..519]: 8 felts @@ -191,8 +191,8 @@ begin dropw # => [pad(16)] - # Load CLAIM note inputs into memory, starting at address 0 - push.0 exec.active_note::get_inputs drop drop + # Load CLAIM note storage into memory, starting at address 0 + push.0 exec.active_note::get_storage drop drop # => [pad(16)] # Check consuming account == aggfaucet diff --git a/crates/miden-agglayer/src/errors/agglayer.rs b/crates/miden-agglayer/src/errors/agglayer.rs index efa9275dee..2de157dd1b 100644 --- a/crates/miden-agglayer/src/errors/agglayer.rs +++ b/crates/miden-agglayer/src/errors/agglayer.rs @@ -12,10 +12,10 @@ use miden_protocol::errors::MasmError; /// Error Message: "most-significant 4 bytes (addr4) must be zero" pub const ERR_ADDR4_NONZERO: MasmError = MasmError::from_static_str("most-significant 4 bytes (addr4) must be zero"); +/// Error Message: "B2AGG script expects exactly 6 note storage items" +pub const ERR_B2AGG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS: MasmError = MasmError::from_static_str("B2AGG script expects exactly 6 note storage items"); /// Error Message: "B2AGG script requires exactly 1 note asset" pub const ERR_B2AGG_WRONG_NUMBER_OF_ASSETS: MasmError = MasmError::from_static_str("B2AGG script requires exactly 1 note asset"); -/// Error Message: "B2AGG script expects exactly 6 note inputs" -pub const ERR_B2AGG_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("B2AGG script expects exactly 6 note inputs"); /// Error Message: "CLAIM's target account address and transaction address do not match" pub const ERR_CLAIM_TARGET_ACCT_MISMATCH: MasmError = MasmError::from_static_str("CLAIM's target account address and transaction address do not match"); diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 7020a384a4..1895c471f6 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -24,10 +24,10 @@ use miden_protocol::errors::NoteError; use miden_protocol::note::{ Note, NoteAssets, - NoteInputs, NoteMetadata, NoteRecipient, NoteScript, + NoteStorage, NoteTag, NoteType, }; @@ -400,36 +400,36 @@ pub fn create_claim_note(params: ClaimNoteParams<'_, R>) -> Result(params: ClaimNoteParams<'_, R>) -> Result [SERIAL_NUMBER, pad(12)] end -#! Returns the inputs commitment and length of the specified input note. +#! Returns the storage commitment and length of the specified input note. #! #! Inputs: [is_active_note, note_index, pad(14)] -#! Outputs: [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11)] +#! Outputs: [NOTE_STORAGE_COMMITMENT, num_storage_items, pad(11)] #! #! Where: -#! - is_active_note is the boolean flag indicating whether we should return the inputs commitment +#! - is_active_note is the boolean flag indicating whether we should return the storage commitment #! and length from the active note or from the note with the specified index. #! - note_index is the index of the input note whose data should be returned. Notice that if #! is_active_note is 1, note_index is ignored. -#! - NOTE_INPUTS_COMMITMENT is the inputs commitment of the specified input note. -#! - num_inputs is the number of inputs of the specified input note. +#! - NOTE_STORAGE_COMMITMENT is the storage commitment of the specified input note. +#! - num_storage_items is the number of storage items of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. -#! - is_active_note is 1 and no input note is not being processed (attempted to access note inputs +#! - is_active_note is 1 and no input note is not being processed (attempted to access note storage #! from incorrect context). #! #! Invocation: dynexec -pub proc input_note_get_inputs_info +pub proc input_note_get_storage_info # get the input note pointer depending on whether the requested note is current or it was # requested by index. exec.get_requested_note_ptr @@ -1083,22 +1083,22 @@ pub proc input_note_get_inputs_info # assert the pointer is not zero - this would suggest the procedure has been called from an # incorrect context - dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_WHILE_NO_NOTE_BEING_PROCESSED + dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_STORAGE_WHILE_NO_NOTE_BEING_PROCESSED # => [input_note_ptr, pad(15)] - # get the note inputs length - dup exec.memory::get_input_note_num_inputs swap - # => [input_note_ptr, num_inputs, pad(16)] + # get the note's number of storage items + dup exec.memory::get_input_note_num_storage_items swap + # => [input_note_ptr, num_storage_items, pad(16)] - # get the inputs commitment - exec.memory::get_input_note_inputs_commitment - # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(16)] + # get the storage commitment + exec.memory::get_input_note_storage_commitment + # => [NOTE_STORAGE_COMMITMENT, num_storage_items, pad(16)] # truncate the stack repeat.5 movup.5 drop end - # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11)] + # => [NOTE_STORAGE_COMMITMENT, num_storage_items, pad(11)] end #! Returns the script root of the specified input note. @@ -1107,7 +1107,7 @@ end #! Outputs: [SCRIPT_ROOT, pad(12)] #! #! Where: -#! - is_active_note is the boolean flag indicating whether we should return the inputs commitment +#! - is_active_note is the boolean flag indicating whether we should return the storage commitment #! and length from the active note or from the note with the specified index. #! - note_index is the index of the input note whose data should be returned. Notice that if #! is_active_note is 1, note_index is ignored. @@ -1115,7 +1115,7 @@ end #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. -#! - is_active_note is 1 and no input note is not being processed (attempted to access note inputs +#! - is_active_note is 1 and no input note is not being processed (attempted to access note storage #! from incorrect context). #! #! Invocation: dynexec @@ -1251,7 +1251,7 @@ end #! #! Where: #! - note_index is the index of the output note whose recipient should be returned. -#! - RECIPIENT is the commitment to the output note's script, inputs, the serial number. +#! - RECIPIENT is the commitment to the output note's script, storage, the serial number. #! #! Panics if: #! - the note index is greater or equal to the total number of output notes. diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/constants.masm b/crates/miden-protocol/asm/kernels/transaction/lib/constants.masm index 5c58398d3c..869a7a818a 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/constants.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/constants.masm @@ -4,8 +4,8 @@ # The number of elements in a Word pub const WORD_SIZE = 4 -# The maximum number of input values associated with a single note. -pub const MAX_INPUTS_PER_NOTE = 1024 +# The maximum number of storage items associated with a single note. +pub const MAX_NOTE_STORAGE_ITEMS = 1024 # The maximum number of assets that can be stored in a single note. pub const MAX_ASSETS_PER_NOTE = 256 diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm b/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm index 2d9e11ef30..52d4ba7c8b 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm @@ -221,13 +221,13 @@ const INPUT_NOTE_ID_OFFSET=0 const INPUT_NOTE_CORE_DATA_OFFSET=4 const INPUT_NOTE_SERIAL_NUM_OFFSET=4 const INPUT_NOTE_SCRIPT_ROOT_OFFSET=8 -const INPUT_NOTE_INPUTS_COMMITMENT_OFFSET=12 +const INPUT_NOTE_STORAGE_COMMITMENT_OFFSET=12 const INPUT_NOTE_ASSETS_COMMITMENT_OFFSET=16 const INPUT_NOTE_RECIPIENT_OFFSET=20 const INPUT_NOTE_METADATA_HEADER_OFFSET=24 const INPUT_NOTE_ATTACHMENT_OFFSET=28 const INPUT_NOTE_ARGS_OFFSET=32 -const INPUT_NOTE_NUM_INPUTS_OFFSET=36 +const INPUT_NOTE_NUM_STORAGE_ITEMS_OFFSET=36 const INPUT_NOTE_NUM_ASSETS_OFFSET=40 const INPUT_NOTE_ASSETS_OFFSET=44 @@ -1596,14 +1596,14 @@ end #! Returns the inputs commitment of an input note located at the specified memory address. #! #! Inputs: [note_ptr] -#! Outputs: [INPUTS_COMMITMENT] +#! Outputs: [STORAGE_COMMITMENT] #! #! Where: #! - note_ptr is the memory address at which the input note data begins. -#! - INPUTS_COMMITMENT is the inputs commitment of the input note. -pub proc get_input_note_inputs_commitment +#! - STORAGE_COMMITMENT is the inputs commitment of the input note. +pub proc get_input_note_storage_commitment padw - movup.4 add.INPUT_NOTE_INPUTS_COMMITMENT_OFFSET + movup.4 add.INPUT_NOTE_STORAGE_COMMITMENT_OFFSET mem_loadw_be end @@ -1691,26 +1691,26 @@ end #! Returns the number of inputs of the note located at the specified memory address. #! #! Inputs: [note_ptr] -#! Outputs: [num_inputs] +#! Outputs: [num_storage_items] #! #! Where: #! - note_ptr is the memory address at which the input note data begins. -#! - num_inputs is the number of inputs in in the input note. -pub proc get_input_note_num_inputs - add.INPUT_NOTE_NUM_INPUTS_OFFSET +#! - num_storage_items is the number of storage items of the input note. +pub proc get_input_note_num_storage_items + add.INPUT_NOTE_NUM_STORAGE_ITEMS_OFFSET mem_load end #! Sets the number of inputs for an input note located at the specified memory address. #! -#! Inputs: [note_ptr, num_inputs] +#! Inputs: [note_ptr, num_storage_items] #! Outputs: [] #! #! Where: #! - note_ptr is the memory address at which the input note data begins. -#! - num_inputs is the number of inputs in the input note. -pub proc set_input_note_num_inputs - add.INPUT_NOTE_NUM_INPUTS_OFFSET +#! - num_storage_items is the number of storage items of the input note. +pub proc set_input_note_num_storage_items + add.INPUT_NOTE_NUM_STORAGE_ITEMS_OFFSET mem_store end @@ -1760,7 +1760,7 @@ end #! #! Where: #! - note_ptr is the memory address at which the input note data begins. -#! - RECIPIENT is the commitment to the note's script, inputs and the serial number. +#! - RECIPIENT is the commitment to the note's script, storage and the serial number. pub proc get_input_note_recipient padw movup.4 add.INPUT_NOTE_RECIPIENT_OFFSET @@ -1774,7 +1774,7 @@ end #! #! Where: #! - note_ptr is the memory address at which the output note data begins. -#! - RECIPIENT is the commitment to the note's script, inputs and the serial number. +#! - RECIPIENT is the commitment to the note's script, storage and the serial number. pub proc set_input_note_recipient add.INPUT_NOTE_RECIPIENT_OFFSET mem_storew_be @@ -1842,7 +1842,7 @@ end #! #! Where: #! - note_ptr is the memory address at which the output note data begins. -#! - RECIPIENT is the commitment to the note's script, inputs and the serial number. +#! - RECIPIENT is the commitment to the note's script, storage and the serial number. pub proc get_output_note_recipient padw movup.4 add.OUTPUT_NOTE_RECIPIENT_OFFSET @@ -1856,7 +1856,7 @@ end #! #! Where: #! - note_ptr is the memory address at which the output note data begins. -#! - RECIPIENT is the commitment to the note's script, inputs and the serial number. +#! - RECIPIENT is the commitment to the note's script, storage and the serial number. pub proc set_output_note_recipient add.OUTPUT_NOTE_RECIPIENT_OFFSET mem_storew_be diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/note.masm b/crates/miden-protocol/asm/kernels/transaction/lib/note.masm index 581d6598d7..8acbc7ab8b 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/note.masm @@ -166,7 +166,7 @@ end #! #! The note ID is computed as follows: #! - we define, recipient = -#! hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment) +#! hash(hash(hash(serial_num, [0; 4]), script_root), storage_commitment) #! - we then compute the output note ID as: #! hash(recipient, assets_commitment) #! diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm index 654e8ae6a3..f4cef9d9a8 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm @@ -3,6 +3,9 @@ use $kernel::memory use $kernel::note use $kernel::asset use $kernel::constants::MAX_OUTPUT_NOTES_PER_TX +use $kernel::util::note::ATTACHMENT_KIND_NONE +use $kernel::util::note::ATTACHMENT_KIND_WORD +use $kernel::util::note::ATTACHMENT_KIND_ARRAY use miden::core::mem use miden::core::word @@ -12,12 +15,6 @@ use miden::core::word # Constants for different note types const PUBLIC_NOTE=1 # 0b01 const PRIVATE_NOTE=2 # 0b10 -const ENCRYPTED_NOTE=3 # 0b11 - -# Constants for note attachment kinds -const ATTACHMENT_KIND_NONE=0 -const ATTACHMENT_KIND_WORD=1 -const ATTACHMENT_KIND_ARRAY=2 # The default value of the felt at index 3 in the note metadata header when a new note is created. # All zeros sets the attachment kind to None and the user-defined attachment scheme to "none". diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm index bb0ce6e9d5..bcc7e16e8f 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm @@ -10,7 +10,7 @@ use $kernel::asset_vault use $kernel::constants::EMPTY_SMT_ROOT use $kernel::constants::MAX_ASSETS_PER_NOTE use $kernel::constants::MAX_INPUT_NOTES_PER_TX -use $kernel::constants::MAX_INPUTS_PER_NOTE +use $kernel::constants::MAX_NOTE_STORAGE_ITEMS use $kernel::constants::NOTE_TREE_DEPTH use $kernel::constants::STORAGE_SLOT_TYPE_MAP use $kernel::constants::STORAGE_SLOT_TYPE_VALUE @@ -67,7 +67,7 @@ const ERR_PROLOGUE_INPUT_NOTES_COMMITMENT_MISMATCH="note commitment computed fro const ERR_PROLOGUE_NEW_ACCOUNT_NONCE_MUST_BE_ZERO="new account must have a zero nonce" -const ERR_PROLOGUE_NUMBER_OF_NOTE_INPUTS_EXCEEDED_LIMIT="number of note inputs exceeded the maximum limit of 1024" +const ERR_PROLOGUE_NUMBER_OF_NOTE_STORAGE_ITEMS_EXCEEDED_LIMIT="number of note storage items exceeded the maximum limit of 1024" const ERR_PROLOGUE_NOTE_AUTHENTICATION_FAILED="failed to authenticate note inclusion in block" @@ -584,7 +584,7 @@ end #! Advice stack: [ #! SERIAL_NUMBER, #! SCRIPT_ROOT, -#! INPUTS_COMMITMENT, +#! STORAGE_COMMITMENT, #! ASSETS_COMMITMENT, #! ] #! Outputs: @@ -595,10 +595,10 @@ end #! - note_ptr is the memory location for the input note. #! - SERIAL_NUMBER is the note's serial. #! - SCRIPT_ROOT is the note's script root. -#! - INPUTS_COMMITMENT is the sequential hash of the padded note's inputs. +#! - STORAGE_COMMITMENT is the sequential hash of the padded note's storage. #! - ASSETS_COMMITMENT is the sequential hash of the padded note's assets. #! - NULLIFIER is the result of -#! `hash(SERIAL_NUMBER || SCRIPT_ROOT || INPUTS_COMMITMENT || ASSETS_COMMITMENT)`. +#! `hash(SERIAL_NUMBER || SCRIPT_ROOT || STORAGE_COMMITMENT || ASSETS_COMMITMENT)`. proc process_input_note_details exec.memory::get_input_note_core_ptr # => [note_data_ptr] @@ -649,30 +649,30 @@ proc process_note_args_and_metadata # => [NOTE_ATTACHMENT, NOTE_METADATA_HEADER] end -#! Checks that the number of note inputs is within limit and stores it to memory. +#! Checks that the number of note storage is within limit and stores it to memory. #! #! Inputs: #! Operand stack: [note_ptr] -#! Advice stack: [inputs_len] +#! Advice stack: [num_storage_items] #! Outputs: #! Operand stack: [note_ptr] #! Advice stack: [] #! #! Where: #! - note_ptr is the memory location for the input note. -#! - inputs_len is the note's input count. -proc process_note_inputs_length - # move the inputs length from the advice stack to the operand stack +#! - num_storage_items is the note's number of storage items. +proc process_note_num_storage_items + # move the number of storage items from the advice stack to the operand stack adv_push.1 - # => [inputs_len, note_ptr] + # => [num_storage_items, note_ptr] - # validate the input length - dup push.MAX_INPUTS_PER_NOTE lte - assert.err=ERR_PROLOGUE_NUMBER_OF_NOTE_INPUTS_EXCEEDED_LIMIT - # => [inputs_len, note_ptr] + # validate the number of storage items + dup push.MAX_NOTE_STORAGE_ITEMS lte + assert.err=ERR_PROLOGUE_NUMBER_OF_NOTE_STORAGE_ITEMS_EXCEEDED_LIMIT + # => [num_storage_items, note_ptr] - # store the inputs length into the memory - dup.1 exec.memory::set_input_note_num_inputs + # store the number of storage items into the memory + dup.1 exec.memory::set_input_note_num_storage_items # => [note_ptr] end @@ -828,8 +828,8 @@ proc compute_input_note_id dup.4 exec.memory::get_input_note_script_root exec.rpo256::merge # => [MERGE_SCRIPT, note_ptr] - # compute RECIPIENT: hash(MERGE_SCRIPT || INPUT_COMMITMENT) - dup.4 exec.memory::get_input_note_inputs_commitment exec.rpo256::merge + # compute RECIPIENT: hash(MERGE_SCRIPT || STORAGE_COMMITMENT) + dup.4 exec.memory::get_input_note_storage_commitment exec.rpo256::merge # => [RECIPIENT, note_ptr] # store the recipient in memory @@ -853,7 +853,7 @@ end #! Advice stack: [ #! SERIAL_NUMBER, #! SCRIPT_ROOT, -#! INPUTS_COMMITMENT, +#! STORAGE_COMMITMENT, #! ASSETS_COMMITMENT, #! NOTE_ARGS, #! NOTE_METADATA_HEADER, @@ -877,7 +877,7 @@ end #! notes. #! - SERIAL_NUMBER is the note's serial. #! - SCRIPT_ROOT is the note's script root. -#! - INPUTS_COMMITMENT is the sequential hash of the padded note's inputs. +#! - STORAGE_COMMITMENT is the sequential hash of the padded note's storage. #! - ASSETS_COMMITMENT is the sequential hash of the padded note's assets. #! - NOTE_METADATA_HEADER is the note's metadata header. #! - NOTE_ATTACHMENT is the note's attachment. @@ -919,10 +919,10 @@ proc process_input_note movup.4 # => [note_ptr, NOTE_METADATA_COMMITMENT, NULLIFIER, HASHER_CAPACITY] - # note inputs len + # note number of storage items # --------------------------------------------------------------------------------------------- - exec.process_note_inputs_length + exec.process_note_num_storage_items # => [note_ptr, NOTE_METADATA_COMMITMENT, NULLIFIER, HASHER_CAPACITY] # note assets diff --git a/crates/miden-protocol/asm/protocol/active_note.masm b/crates/miden-protocol/asm/protocol/active_note.masm index 77865a19bc..fe6013e8a8 100644 --- a/crates/miden-protocol/asm/protocol/active_note.masm +++ b/crates/miden-protocol/asm/protocol/active_note.masm @@ -9,7 +9,7 @@ use miden::protocol::note const ERR_NOTE_DATA_DOES_NOT_MATCH_COMMITMENT="note data does not match the commitment" -const ERR_NOTE_INVALID_NUMBER_OF_INPUTS="the specified number of note inputs does not match the actual number" +const ERR_NOTE_INVALID_NUMBER_OF_STORAGE_ITEMS="the specified number of note storage items does not match the actual number" # ACTIVE NOTE PROCEDURES # ================================================================================================= @@ -60,7 +60,7 @@ end #! Outputs: [RECIPIENT] #! #! Where: -#! - RECIPIENT is the commitment to the active note's script, inputs, the serial number. +#! - RECIPIENT is the commitment to the active note's script, storage, the serial number. #! #! Panics if: #! - no note is currently active. @@ -86,24 +86,24 @@ pub proc get_recipient # => [RECIPIENT] end -#! Writes the active note's inputs to memory starting at the specified address. +#! Writes the active note's storage to memory starting at the specified address. #! #! Inputs: #! Stack: [dest_ptr] -#! Advice Map: { NOTE_INPUTS_COMMITMENT: [INPUTS] } +#! Advice Map: { NOTE_STORAGE_COMMITMENT: [STORAGE] } #! Outputs: -#! Stack: [num_inputs, dest_ptr] +#! Stack: [num_storage_items, dest_ptr] #! #! Where: -#! - dest_ptr is the memory address to write the note inputs. -#! - NOTE_INPUTS_COMMITMENT is the commitment to the note's inputs. -#! - INPUTS is the data corresponding to the note's inputs. +#! - dest_ptr is the memory address to write the note storage. +#! - NOTE_STORAGE_COMMITMENT is the commitment to the note's storage. +#! - STORAGE is the data corresponding to the note's storage. #! #! Panics if: #! - no note is currently active. #! #! Invocation: exec -pub proc get_inputs +pub proc get_storage # pad the stack padw padw padw push.0.0 # => [pad(14), dest_ptr] @@ -112,20 +112,20 @@ pub proc get_inputs push.1 # => [is_active_note = 1, pad(14), dest_ptr] - exec.kernel_proc_offsets::input_note_get_inputs_info_offset + exec.kernel_proc_offsets::input_note_get_storage_info_offset # => [offset, is_active_note = 1, pad(14), dest_ptr] syscall.exec_kernel_proc - # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11), dest_ptr] + # => [NOTE_STORAGE_COMMITMENT, num_storage_items, pad(11), dest_ptr] # clean the stack swapdw dropw dropw movup.5 drop movup.5 drop movup.5 drop - # => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # => [NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # write the inputs to the memory using the provided destination pointer - exec.write_inputs_to_memory - # => [num_inputs, dest_ptr] + exec.write_storage_to_memory + # => [num_storage_items, dest_ptr] end #! Returns the metadata of the active note. @@ -250,84 +250,84 @@ end # HELPER PROCEDURES # ================================================================================================= -#! Writes the note inputs stored in the advice map to the memory specified by the provided +#! Writes the note storage stored in the advice map to the memory specified by the provided #! destination pointer. #! #! Inputs: -#! Operand stack: [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] +#! Operand stack: [NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] #! Advice map: { -#! NOTE_INPUTS_COMMITMENT: [[INPUT_VALUES]] +#! NOTE_STORAGE_COMMITMENT: [[INPUT_VALUES]] #! } #! Outputs: -#! Operand stack: [num_inputs, dest_ptr] -proc write_inputs_to_memory +#! Operand stack: [num_storage_items, dest_ptr] +proc write_storage_to_memory # load the inputs from the advice map to the advice stack # we pad the number of inputs to the next multiple of 8 so that we can use the # `pipe_double_words_to_memory` instruction. The padded zeros don't affect the commitment # computation. adv.push_mapvaln.8 - # OS => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] - # AS => [advice_num_inputs, [INPUT_VALUES]] + # OS => [NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] + # AS => [advice_num_storage_items, [INPUT_VALUES]] # move the number of inputs obtained from advice map to the operand stack adv_push.1 dup.5 - # OS => [num_inputs, advice_num_inputs, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # OS => [num_storage_items, advice_num_storage_items, NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # AS => [[INPUT_VALUES]] - assert_eq.err=ERR_NOTE_INVALID_NUMBER_OF_INPUTS - # OS => [NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + assert_eq.err=ERR_NOTE_INVALID_NUMBER_OF_STORAGE_ITEMS + # OS => [NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # AS => [[INPUT_VALUES]] # calculate the number of words required to store the inputs dup.4 u32divmod.4 neq.0 add - # OS => [num_words, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # OS => [num_words, NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # AS => [[INPUT_VALUES]] # round up the number of words to the next multiple of 2 dup is_odd add - # OS => [even_num_words, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # OS => [even_num_words, NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # AS => [[INPUT_VALUES]] # compute the end pointer for writing the padded inputs (even_num_words * 4 elements) dup.6 swap mul.4 add - # OS => [end_ptr, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # OS => [end_ptr, NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # AS => [[INPUT_VALUES]] # prepare the stack for the `pipe_double_words_to_memory` procedure. # - # To match `rpo256::hash_elements` (used for NOTE_INPUTS_COMMITMENT), we set the first capacity - # element to `num_inputs % 8`. + # To match `rpo256::hash_elements` (used for NOTE_STORAGE_COMMITMENT), we set the first capacity + # element to `num_storage_items % 8`. dup.6 dup.6 - # OS => [num_inputs, write_ptr, end_ptr, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # OS => [num_storage_items, write_ptr, end_ptr, NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # AS => [[INPUT_VALUES]] u32divmod.8 swap drop - # OS => [num_inputs_mod_8, write_ptr, end_ptr, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # OS => [num_storage_items_mod_8, write_ptr, end_ptr, NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # AS => [[INPUT_VALUES]] push.0.0.0 - # OS => [A, write_ptr, end_ptr, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr], where A = [0, 0, 0, num_inputs_mod_8] + # OS => [A, write_ptr, end_ptr, NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr], where A = [0, 0, 0, num_storage_items_mod_8] # AS => [[INPUT_VALUES]] padw padw - # OS => [PAD, PAD, A, write_ptr, end_ptr, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # OS => [PAD, PAD, A, write_ptr, end_ptr, NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # AS => [[INPUT_VALUES]] # write the inputs from the advice stack into memory exec.mem::pipe_double_words_to_memory - # OS => [PERM, PERM, PERM, end_ptr', NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # OS => [PERM, PERM, PERM, end_ptr', NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # AS => [] # extract the computed commitment from the hasher state exec.rpo256::squeeze_digest - # OS => [COMPUTED_COMMITMENT, end_ptr', NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # OS => [COMPUTED_COMMITMENT, end_ptr', NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # drop end_ptr' movup.4 drop - # OS => [COMPUTED_COMMITMENT, NOTE_INPUTS_COMMITMENT, num_inputs, dest_ptr] + # OS => [COMPUTED_COMMITMENT, NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] # validate that the inputs written to memory match the inputs commitment assert_eqw.err=ERR_NOTE_DATA_DOES_NOT_MATCH_COMMITMENT - # => [num_inputs, dest_ptr] + # => [num_storage_items, dest_ptr] end diff --git a/crates/miden-protocol/asm/protocol/input_note.masm b/crates/miden-protocol/asm/protocol/input_note.masm index a08d5a5dd2..8cd1e93d81 100644 --- a/crates/miden-protocol/asm/protocol/input_note.masm +++ b/crates/miden-protocol/asm/protocol/input_note.masm @@ -88,7 +88,7 @@ end #! #! Where: #! - note_index is the index of the input note whose recipient should be returned. -#! - RECIPIENT is the commitment to the input note's script, inputs, the serial number. +#! - RECIPIENT is the commitment to the input note's script, storage, the serial number. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. @@ -184,18 +184,18 @@ end #! Returns the inputs commitment and length of the input note with the specified index. #! #! Inputs: [note_index] -#! Outputs: [NOTE_INPUTS_COMMITMENT, num_inputs] +#! Outputs: [NOTE_STORAGE_COMMITMENT, num_storage_items] #! #! Where: #! - note_index is the index of the input note whose data should be returned. -#! - NOTE_INPUTS_COMMITMENT is the inputs commitment of the specified input note. -#! - num_inputs is the number of input values of the specified input note. +#! - NOTE_STORAGE_COMMITMENT is the inputs commitment of the specified input note. +#! - num_storage_items is the number of input values of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. #! #! Invocation: exec -pub proc get_inputs_info +pub proc get_storage_info # start padding the stack push.0 swap # => [note_index, 0] @@ -205,7 +205,7 @@ pub proc get_inputs_info push.0 # => [is_active_note = 0, note_index, 0] - exec.kernel_proc_offsets::input_note_get_inputs_info_offset + exec.kernel_proc_offsets::input_note_get_storage_info_offset # => [offset, is_active_note = 0, note_index, 0] # pad the stack @@ -213,14 +213,14 @@ pub proc get_inputs_info # => [offset, is_active_note = 0, note_index, pad(13)] syscall.exec_kernel_proc - # => [NOTE_INPUTS_COMMITMENT, num_inputs, pad(11)] + # => [NOTE_STORAGE_COMMITMENT, num_storage_items, pad(11)] # clean the stack swapdw dropw dropw repeat.3 movup.5 drop end - # => [NOTE_INPUTS_COMMITMENT, num_inputs] + # => [NOTE_STORAGE_COMMITMENT, num_storage_items] end #! Returns the script root of the input note with the specified index. diff --git a/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm b/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm index ae698f2df7..f08635d4c1 100644 --- a/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm +++ b/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm @@ -57,7 +57,7 @@ const FAUCET_IS_NON_FUNGIBLE_ASSET_ISSUED_OFFSET=29 const INPUT_NOTE_GET_METADATA_OFFSET=30 const INPUT_NOTE_GET_ASSETS_INFO_OFFSET=31 const INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=32 -const INPUT_NOTE_GET_INPUTS_INFO_OFFSET=33 +const INPUT_NOTE_GET_STORAGE_INFO_OFFSET=33 const INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=34 const INPUT_NOTE_GET_RECIPIENT_OFFSET=35 @@ -584,16 +584,16 @@ pub proc input_note_get_serial_number_offset push.INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET end -#! Returns the offset of the `input_note_get_inputs_info` kernel procedure. +#! Returns the offset of the `input_note_get_storage_info` kernel procedure. #! #! Inputs: [] #! Outputs: [proc_offset] #! #! Where: -#! - proc_offset is the offset of the `input_note_get_inputs_info` kernel procedure required to get +#! - proc_offset is the offset of the `input_note_get_storage_info` kernel procedure required to get #! the address where this procedure is stored. -pub proc input_note_get_inputs_info_offset - push.INPUT_NOTE_GET_INPUTS_INFO_OFFSET +pub proc input_note_get_storage_info_offset + push.INPUT_NOTE_GET_STORAGE_INFO_OFFSET end #! Returns the offset of the `input_note_get_script_root` kernel procedure. diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 1fe0195e71..daa8e97e5e 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -4,44 +4,44 @@ use miden::core::math::u64 use miden::core::mem # Re-export the max inputs per note constant. -pub use ::miden::protocol::util::note::MAX_INPUTS_PER_NOTE +pub use ::miden::protocol::util::note::MAX_NOTE_STORAGE_ITEMS # ERRORS # ================================================================================================= -const ERR_PROLOGUE_NOTE_INPUTS_LEN_EXCEEDED_LIMIT="number of note inputs exceeded the maximum limit of 1024" +const ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT="number of note storage exceeded the maximum limit of 1024" # NOTE UTILITY PROCEDURES # ================================================================================================= -#! Computes the commitment to the note inputs starting at the specified memory address. +#! Computes the commitment to the note storage starting at the specified memory address. #! -#! This procedure checks that the provided number of note inputs is within limits and then computes +#! This procedure checks that the provided number of note storage items is within limits and then computes #! the commitment. #! -#! If the number of note inputs is 0, procedure returns the empty word: [0, 0, 0, 0]. +#! If the number of note storage items is 0, procedure returns the empty word: [0, 0, 0, 0]. #! -#! Inputs: [inputs_ptr, num_inputs] -#! Outputs: [INPUTS_COMMITMENT] +#! Inputs: [storage_ptr, num_storage_items] +#! Outputs: [STORAGE_COMMITMENT] #! #! Cycles: #! - If number of elements divides by 8: 56 cycles + 3 * words #! - Else: 189 cycles + 3 * words #! #! Panics if: -#! - inputs_ptr is not word-aligned (i.e., is not a multiple of 4). -#! - num_inputs is greater than 1024. +#! - storage_ptr is not word-aligned (i.e., is not a multiple of 4). +#! - num_storage_items is greater than 1024. #! #! Invocation: exec -pub proc compute_inputs_commitment - # check that number of inputs is less than or equal to MAX_INPUTS_PER_NOTE - dup.1 push.MAX_INPUTS_PER_NOTE u32assert2.err=ERR_PROLOGUE_NOTE_INPUTS_LEN_EXCEEDED_LIMIT - u32lte assert.err=ERR_PROLOGUE_NOTE_INPUTS_LEN_EXCEEDED_LIMIT - # => [inputs_ptr, num_inputs] +pub proc compute_storage_commitment + # check that number of storage items is less than or equal to MAX_NOTE_STORAGE_ITEMS + dup.1 push.MAX_NOTE_STORAGE_ITEMS u32assert2.err=ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT + u32lte assert.err=ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT + # => [storage_ptr, num_storage_items] - # compute the inputs commitment (over the unpadded values) + # compute the storage commitment (over the unpadded values) exec.rpo256::hash_elements - # => [INPUTS_COMMITMENT] + # => [STORAGE_COMMITMENT] end #! Writes the assets data stored in the advice map to the memory specified by the provided @@ -76,116 +76,116 @@ pub proc write_assets_to_memory # AS => [] end -#! Builds the recipient hash from note inputs, script root, and serial number. +#! Builds the recipient hash from note storage, script root, and serial number. #! -#! This procedure computes the commitment of the note inputs and then uses it to calculate the note +#! This procedure computes the commitment of the note storage and then uses it to calculate the note #! recipient by hashing this commitment, the provided script root, and the serial number. #! #! Inputs: -#! Operand stack: [inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] +#! Operand stack: [storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT] #! Advice map: { -#! INPUTS_COMMITMENT: [INPUTS], +#! STORAGE_COMMITMENT: [INPUTS], #! } #! Outputs: #! Operand stack: [RECIPIENT] #! Advice map: { -#! INPUTS_COMMITMENT: [INPUTS], -#! RECIPIENT: [SERIAL_SCRIPT_HASH, INPUTS_COMMITMENT], +#! STORAGE_COMMITMENT: [INPUTS], +#! RECIPIENT: [SERIAL_SCRIPT_HASH, STORAGE_COMMITMENT], #! SERIAL_SCRIPT_HASH: [SERIAL_HASH, SCRIPT_ROOT], #! SERIAL_HASH: [SERIAL_NUM, EMPTY_WORD], #! } #! #! Where: -#! - inputs_ptr is the memory address where the note inputs are stored. -#! - num_inputs is the number of input values. +#! - storage_ptr is the memory address where the note storage are stored. +#! - num_storage_items is the number of input values. #! - SCRIPT_ROOT is the script root of the note. #! - SERIAL_NUM is the serial number of the note. -#! - RECIPIENT is the commitment to the input note's script, inputs, and the serial number. +#! - RECIPIENT is the commitment to the input note's script, storage, and the serial number. #! #! Locals: -#! - 0: inputs_ptr -#! - 1: num_inputs +#! - 0: storage_ptr +#! - 1: num_storage_items #! #! Panics if: -#! - inputs_ptr is not word-aligned (i.e., is not a multiple of 4). -#! - num_inputs is greater than 1024. +#! - storage_ptr is not word-aligned (i.e., is not a multiple of 4). +#! - num_storage_items is greater than 1024. #! #! Invocation: exec @locals(1) pub proc build_recipient dup.1 dup.1 - # => [inputs_ptr, num_inputs, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + # => [storage_ptr, num_storage_items, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT] - exec.compute_inputs_commitment - # => [INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + exec.compute_storage_commitment + # => [STORAGE_COMMITMENT, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT] - # store num_inputs into local memory + # store num_storage_items into local memory dup.5 loc_store.0 - # => [INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + # => [STORAGE_COMMITMENT, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT] locaddr.0 add.1 locaddr.0 - # => [num_inputs_start_ptr, num_inputs_end_ptr, INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + # => [num_storage_items_start_ptr, num_storage_items_end_ptr, STORAGE_COMMITMENT, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT] dup.5 dup.5 dup.5 dup.5 - # => [INPUTS_COMMITMENT, num_inputs_start_ptr, num_inputs_end_ptr, - # INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + # => [STORAGE_COMMITMENT, num_storage_items_start_ptr, num_storage_items_end_ptr, + # STORAGE_COMMITMENT, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT] - # compute the advice map key for num_inputs by hashing the inputs commitment + # compute the advice map key for num_storage_items by hashing the storage commitment exec.rpo256::hash - # => [hash(INPUTS_COMMITMENT), num_inputs_start_ptr, num_inputs_end_ptr, - # INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + # => [hash(STORAGE_COMMITMENT), num_storage_items_start_ptr, num_storage_items_end_ptr, + # STORAGE_COMMITMENT, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT] adv.insert_mem dropw drop drop - # => [INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT] + # => [STORAGE_COMMITMENT, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT] movup.5 movup.5 dup movdn.2 - # => [inputs_ptr, num_inputs, inputs_ptr, INPUTS_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT] + # => [storage_ptr, num_storage_items, storage_ptr, STORAGE_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT] add swap - # => [inputs_ptr, end_ptr, INPUTS_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT] + # => [storage_ptr, end_ptr, STORAGE_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT] movdn.5 movdn.5 - # => [INPUTS_COMMITMENT, inputs_ptr, end_ptr, SERIAL_NUM, SCRIPT_ROOT] + # => [STORAGE_COMMITMENT, storage_ptr, end_ptr, SERIAL_NUM, SCRIPT_ROOT] adv.insert_mem - # => [INPUTS_COMMITMENT, inputs_ptr, end_ptr, SERIAL_NUM, SCRIPT_ROOT] + # => [STORAGE_COMMITMENT, storage_ptr, end_ptr, SERIAL_NUM, SCRIPT_ROOT] movup.4 drop movup.4 drop - # => [INPUTS_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT] + # => [STORAGE_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT] movdnw.2 - # => [SERIAL_NUM, SCRIPT_ROOT, INPUTS_COMMITMENT] + # => [SERIAL_NUM, SCRIPT_ROOT, STORAGE_COMMITMENT] padw adv.insert_hdword exec.rpo256::merge - # => [SERIAL_HASH, SCRIPT_ROOT, INPUTS_COMMITMENT] + # => [SERIAL_HASH, SCRIPT_ROOT, STORAGE_COMMITMENT] swapw adv.insert_hdword exec.rpo256::merge - # => [SERIAL_SCRIPT_HASH, INPUTS_COMMITMENT] + # => [SERIAL_SCRIPT_HASH, STORAGE_COMMITMENT] swapw adv.insert_hdword exec.rpo256::merge # => [RECIPIENT] end -#! Returns the RECIPIENT for a specified SERIAL_NUM, SCRIPT_ROOT, and inputs commitment. +#! Returns the RECIPIENT for a specified SERIAL_NUM, SCRIPT_ROOT, and storage commitment. #! -#! Inputs: [SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT] +#! Inputs: [SERIAL_NUM, SCRIPT_ROOT, STORAGE_COMMITMENT] #! Outputs: [RECIPIENT] #! #! Where: #! - SERIAL_NUM is the serial number of the recipient. #! - SCRIPT_ROOT is the commitment of the note script. -#! - INPUT_COMMITMENT is the commitment of the note inputs. +#! - STORAGE_COMMITMENT is the commitment of the note storage. #! - RECIPIENT is the recipient of the note. #! #! Invocation: exec pub proc build_recipient_hash padw exec.rpo256::merge - # => [SERIAL_NUM_HASH, SCRIPT_ROOT, INPUT_COMMITMENT] + # => [SERIAL_NUM_HASH, SCRIPT_ROOT, STORAGE_COMMITMENT] swapw exec.rpo256::merge - # => [MERGE_SCRIPT, INPUT_COMMITMENT] + # => [MERGE_SCRIPT, STORAGE_COMMITMENT] swapw exec.rpo256::merge # [RECIPIENT] @@ -216,6 +216,29 @@ pub proc extract_sender_from_metadata # => [sender_id_prefix, sender_id_suffix] end +#! Extracts the attachment kind and scheme from the provided metadata header. +#! +#! Inputs: [METADATA_HEADER] +#! Outputs: [attachment_kind, attachment_scheme] +#! +#! Where: +#! - METADATA_HEADER is the metadata of a note. +#! - attachment_kind is the attachment kind of the note. +#! - attachment_scheme is the attachment scheme of the note. +#! +#! Invocation: exec +pub proc extract_attachment_info_from_metadata + # => [attachment_kind_scheme, METADATA_HEADER[1..4]] + movdn.3 drop drop drop + # => [attachment_kind_scheme] + + # deconstruct the attachment_kind_scheme to extract the attachment_scheme + # attachment_kind_scheme = [30 zero bits | attachment_kind (2 bits) | attachment_scheme (32 bits)] + # u32split splits into [high, low] where low is attachment_scheme + u32split + # => [attachment_kind, attachment_scheme] +end + #! Computes the tag for a network note for a given network account such that it is #! picked up by the network transaction builder. #! diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index d8898bea68..97f0180a26 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -4,10 +4,10 @@ use miden::protocol::note # CONSTANTS # ================================================================================================= -# Constants for note attachment kinds -pub const ATTACHMENT_KIND_NONE=0 -pub const ATTACHMENT_KIND_WORD=1 -pub const ATTACHMENT_KIND_ARRAY=2 +# Re-export constants for note attachment kinds +pub use ::miden::protocol::util::note::ATTACHMENT_KIND_NONE +pub use ::miden::protocol::util::note::ATTACHMENT_KIND_WORD +pub use ::miden::protocol::util::note::ATTACHMENT_KIND_ARRAY # PROCEDURES # ================================================================================================= @@ -255,7 +255,7 @@ end #! #! Where: #! - note_index is the index of the output note whose recipient should be returned. -#! - RECIPIENT is the commitment to the note note's script, inputs, the serial number. +#! - RECIPIENT is the commitment to the note note's script, storage, the serial number. #! #! Panics if: #! - the note index is greater or equal to the total number of output notes. diff --git a/crates/miden-protocol/asm/shared_utils/util/note.masm b/crates/miden-protocol/asm/shared_utils/util/note.masm index 8c502a8f71..066dfcd2fb 100644 --- a/crates/miden-protocol/asm/shared_utils/util/note.masm +++ b/crates/miden-protocol/asm/shared_utils/util/note.masm @@ -1,5 +1,12 @@ # CONSTANTS # ================================================================================================= -# The maximum number of input values associated with a single note. -pub const MAX_INPUTS_PER_NOTE = 1024 +# The maximum number of storage values associated with a single note. +pub const MAX_NOTE_STORAGE_ITEMS = 1024 + +#! Signals the absence of a note attachment. +pub const ATTACHMENT_KIND_NONE=0 +#! A note attachment consisting of a single Word. +pub const ATTACHMENT_KIND_WORD=1 +#! A note attachment consisting of the commitment to a set of felts. +pub const ATTACHMENT_KIND_ARRAY=2 diff --git a/crates/miden-protocol/src/account/component/code.rs b/crates/miden-protocol/src/account/component/code.rs index 2ddcdfca53..d7c5113a7f 100644 --- a/crates/miden-protocol/src/account/component/code.rs +++ b/crates/miden-protocol/src/account/component/code.rs @@ -1,6 +1,8 @@ use miden_assembly::Library; use miden_processor::MastForest; +use crate::vm::AdviceMap; + // ACCOUNT COMPONENT CODE // ================================================================================================ @@ -23,6 +25,19 @@ impl AccountComponentCode { pub fn into_library(self) -> Library { self.0 } + + /// Returns a new [AccountComponentCode] with the provided advice map entries merged into the + /// underlying [Library]'s [MastForest]. + /// + /// This allows adding advice map entries to an already-compiled account component, + /// which is useful when the entries are determined after compilation. + pub fn with_advice_map(self, advice_map: AdviceMap) -> Self { + if advice_map.is_empty() { + return self; + } + + Self(self.0.with_advice_map(advice_map)) + } } impl AsRef for AccountComponentCode { @@ -45,3 +60,43 @@ impl From for Library { value.into_library() } } + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_core::{Felt, Word}; + + use super::*; + use crate::assembly::Assembler; + + #[test] + fn test_account_component_code_with_advice_map() { + let assembler = Assembler::default(); + let library = assembler + .assemble_library(["pub proc test nop end"]) + .expect("failed to assemble library"); + let component_code = AccountComponentCode::from(library); + + assert!(component_code.mast_forest().advice_map().is_empty()); + + // Empty advice map should be a no-op (digest stays the same) + let cloned = component_code.clone(); + let original_digest = cloned.as_library().digest(); + let component_code = component_code.with_advice_map(AdviceMap::default()); + assert_eq!(original_digest, component_code.as_library().digest()); + + // Non-empty advice map should add entries + let key = Word::from([10u32, 20, 30, 40]); + let value = vec![Felt::new(200)]; + let mut advice_map = AdviceMap::default(); + advice_map.insert(key, value.clone()); + + let component_code = component_code.with_advice_map(advice_map); + + let mast = component_code.mast_forest(); + let stored = mast.advice_map().get(&key).expect("entry should be present"); + assert_eq!(stored.as_ref(), value.as_slice()); + } +} diff --git a/crates/miden-protocol/src/asset/vault/vault_key.rs b/crates/miden-protocol/src/asset/vault/vault_key.rs index 2cff63d04d..1d3d2a6914 100644 --- a/crates/miden-protocol/src/asset/vault/vault_key.rs +++ b/crates/miden-protocol/src/asset/vault/vault_key.rs @@ -82,6 +82,11 @@ impl AssetVaultKey { } } + /// Returns a reference to the inner [Word] of this key. + pub fn as_word(&self) -> &Word { + &self.0 + } + /// Returns `true` if the asset key is for a fungible asset, `false` otherwise. fn is_fungible(&self) -> bool { self.0[0].as_int() == 0 && self.0[1].as_int() == 0 diff --git a/crates/miden-protocol/src/block/block_number.rs b/crates/miden-protocol/src/block/block_number.rs index 33c65c82c3..9660084388 100644 --- a/crates/miden-protocol/src/block/block_number.rs +++ b/crates/miden-protocol/src/block/block_number.rs @@ -30,6 +30,9 @@ impl BlockNumber { /// The block height of the genesis block. pub const GENESIS: Self = Self(0); + /// The maximum block number. + pub const MAX: Self = Self(u32::MAX); + /// Returns the previous block number pub fn parent(self) -> Option { self.checked_sub(1) diff --git a/crates/miden-protocol/src/constants.rs b/crates/miden-protocol/src/constants.rs index bda8e339cc..964025064e 100644 --- a/crates/miden-protocol/src/constants.rs +++ b/crates/miden-protocol/src/constants.rs @@ -9,10 +9,10 @@ pub const ACCOUNT_UPDATE_MAX_SIZE: u32 = 2u32.pow(18); /// The maximum number of assets that can be stored in a single note. pub const MAX_ASSETS_PER_NOTE: usize = 255; -/// The maximum number of inputs that can accompany a single note. +/// The maximum number of storage items that can accompany a single note. /// /// The value is set to 1024 so that it is evenly divisible by 8. -pub const MAX_INPUTS_PER_NOTE: usize = 1024; +pub const MAX_NOTE_STORAGE_ITEMS: usize = 1024; /// The maximum number of notes that can be consumed by a single transaction. pub const MAX_INPUT_NOTES_PER_TX: usize = 1024; diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index 19236a647f..25df83059d 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -48,7 +48,7 @@ use crate::{ MAX_ACCOUNTS_PER_BATCH, MAX_INPUT_NOTES_PER_BATCH, MAX_INPUT_NOTES_PER_TX, - MAX_INPUTS_PER_NOTE, + MAX_NOTE_STORAGE_ITEMS, MAX_OUTPUT_NOTES_PER_TX, }; @@ -570,10 +570,10 @@ pub enum NoteError { NoteExecutionHintAfterBlockCannotBeU32Max, #[error("invalid note execution hint payload {1} for tag {0}")] InvalidNoteExecutionHintPayload(u8, u32), - #[error("note type {0} does not match any of the valid note types {public}, {private} or {encrypted}", - public = NoteType::Public, - private = NoteType::Private, - encrypted = NoteType::Encrypted, + #[error( + "note type {0} does not match any of the valid note types {public} or {private}", + public = NoteType::Public, + private = NoteType::Private, )] UnknownNoteType(Box), #[error("note location index {node_index_in_block} is out of bounds 0..={highest_index}")] @@ -589,8 +589,8 @@ pub enum NoteError { NoteScriptDeserializationError(#[source] DeserializationError), #[error("note contains {0} assets which exceeds the maximum of {max}", max = NoteAssets::MAX_NUM_ASSETS)] TooManyAssets(usize), - #[error("note contains {0} inputs which exceeds the maximum of {max}", max = MAX_INPUTS_PER_NOTE)] - TooManyInputs(usize), + #[error("note contains {0} storage items which exceeds the maximum of {max}", max = MAX_NOTE_STORAGE_ITEMS)] + TooManyStorageItems(usize), #[error("note tag requires a public note but the note is of type {0}")] PublicNoteRequired(NoteType), #[error( @@ -734,7 +734,7 @@ pub enum TransactionInputsExtractionError { MissingMapRoot, #[error("failed to construct SMT proof")] SmtProofError(#[from] SmtProofError), - #[error("failed to construct asset witness")] + #[error("failed to construct an asset")] AssetError(#[from] AssetError), #[error("failed to handle storage map data")] StorageMapError(#[from] StorageMapError), diff --git a/crates/miden-protocol/src/errors/protocol.rs b/crates/miden-protocol/src/errors/protocol.rs index 73b7085d33..3e23f52662 100644 --- a/crates/miden-protocol/src/errors/protocol.rs +++ b/crates/miden-protocol/src/errors/protocol.rs @@ -30,8 +30,8 @@ pub const ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID: MasmError = Masm /// Error Message: "note data does not match the commitment" pub const ERR_NOTE_DATA_DOES_NOT_MATCH_COMMITMENT: MasmError = MasmError::from_static_str("note data does not match the commitment"); -/// Error Message: "the specified number of note inputs does not match the actual number" -pub const ERR_NOTE_INVALID_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("the specified number of note inputs does not match the actual number"); +/// Error Message: "the specified number of note storage items does not match the actual number" +pub const ERR_NOTE_INVALID_NUMBER_OF_STORAGE_ITEMS: MasmError = MasmError::from_static_str("the specified number of note storage items does not match the actual number"); -/// Error Message: "number of note inputs exceeded the maximum limit of 1024" -pub const ERR_PROLOGUE_NOTE_INPUTS_LEN_EXCEEDED_LIMIT: MasmError = MasmError::from_static_str("number of note inputs exceeded the maximum limit of 1024"); +/// Error Message: "number of note storage exceeded the maximum limit of 1024" +pub const ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT: MasmError = MasmError::from_static_str("number of note storage exceeded the maximum limit of 1024"); diff --git a/crates/miden-protocol/src/errors/tx_kernel.rs b/crates/miden-protocol/src/errors/tx_kernel.rs index d267df0216..144d6ba754 100644 --- a/crates/miden-protocol/src/errors/tx_kernel.rs +++ b/crates/miden-protocol/src/errors/tx_kernel.rs @@ -142,8 +142,6 @@ pub const ERR_NON_FUNGIBLE_ASSET_FORMAT_MOST_SIGNIFICANT_BIT_MUST_BE_ZERO: MasmE /// Error Message: "failed to access note assets of active note because no note is currently being processed" pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ASSETS_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note assets of active note because no note is currently being processed"); -/// Error Message: "failed to access note inputs of active note because no note is currently being processed" -pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_INPUTS_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note inputs of active note because no note is currently being processed"); /// Error Message: "failed to access note metadata of active note because no note is currently being processed" pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note metadata of active note because no note is currently being processed"); /// Error Message: "failed to access note recipient of active note because no note is currently being processed" @@ -152,6 +150,8 @@ pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_RECIPIENT_WHILE_NO_NOTE_BEING_PROCESSE pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SCRIPT_ROOT_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note script root of active note because no note is currently being processed"); /// Error Message: "failed to access note serial number of active note because no note is currently being processed" pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SERIAL_NUMBER_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note serial number of active note because no note is currently being processed"); +/// Error Message: "failed to access note storage of active note because no note is currently being processed" +pub const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_STORAGE_WHILE_NO_NOTE_BEING_PROCESSED: MasmError = MasmError::from_static_str("failed to access note storage of active note because no note is currently being processed"); /// Error Message: "adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807" pub const ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED: MasmError = MasmError::from_static_str("adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807"); /// Error Message: "failed to find note at the given index; index must be within [0, num_of_notes]" @@ -208,8 +208,8 @@ pub const ERR_PROLOGUE_NOTE_AUTHENTICATION_FAILED: MasmError = MasmError::from_s pub const ERR_PROLOGUE_NUMBER_OF_INPUT_NOTES_EXCEEDS_LIMIT: MasmError = MasmError::from_static_str("number of input notes exceeds the kernel's maximum limit of 1024"); /// Error Message: "number of note assets exceeds the maximum limit of 256" pub const ERR_PROLOGUE_NUMBER_OF_NOTE_ASSETS_EXCEEDS_LIMIT: MasmError = MasmError::from_static_str("number of note assets exceeds the maximum limit of 256"); -/// Error Message: "number of note inputs exceeded the maximum limit of 1024" -pub const ERR_PROLOGUE_NUMBER_OF_NOTE_INPUTS_EXCEEDED_LIMIT: MasmError = MasmError::from_static_str("number of note inputs exceeded the maximum limit of 1024"); +/// Error Message: "number of note storage items exceeded the maximum limit of 1024" +pub const ERR_PROLOGUE_NUMBER_OF_NOTE_STORAGE_ITEMS_EXCEEDED_LIMIT: MasmError = MasmError::from_static_str("number of note storage items exceeded the maximum limit of 1024"); /// Error Message: "account data provided does not match the commitment recorded on-chain" pub const ERR_PROLOGUE_PROVIDED_ACCOUNT_DATA_DOES_NOT_MATCH_ON_CHAIN_COMMITMENT: MasmError = MasmError::from_static_str("account data provided does not match the commitment recorded on-chain"); /// Error Message: "provided info about assets of an input does not match its commitment" diff --git a/crates/miden-protocol/src/note/details.rs b/crates/miden-protocol/src/note/details.rs index f6ede73dab..f913f6ade6 100644 --- a/crates/miden-protocol/src/note/details.rs +++ b/crates/miden-protocol/src/note/details.rs @@ -1,13 +1,13 @@ use miden_processor::DeserializationError; -use super::{NoteAssets, NoteId, NoteInputs, NoteRecipient, NoteScript, Nullifier}; +use super::{NoteAssets, NoteId, NoteRecipient, NoteScript, NoteStorage, Nullifier}; use crate::Word; use crate::utils::serde::{ByteReader, ByteWriter, Deserializable, Serializable}; // NOTE DETAILS // ================================================================================================ -/// Details of a note consisting of assets, script, inputs, and a serial number. +/// Details of a note consisting of assets, script, storage, and a serial number. /// /// See [super::Note] for more details. #[derive(Clone, Debug, PartialEq, Eq)] @@ -50,9 +50,9 @@ impl NoteDetails { self.recipient.script() } - /// Returns the note's recipient inputs which customizes the script's behavior. - pub fn inputs(&self) -> &NoteInputs { - self.recipient.inputs() + /// Returns the note's recipient storage which customizes the script's behavior. + pub fn storage(&self) -> &NoteStorage { + self.recipient.storage() } /// Returns the note's recipient. diff --git a/crates/miden-protocol/src/note/file.rs b/crates/miden-protocol/src/note/file.rs index 62cd7ba863..7777eeee77 100644 --- a/crates/miden-protocol/src/note/file.rs +++ b/crates/miden-protocol/src/note/file.rs @@ -148,10 +148,10 @@ mod tests { NoteAssets, NoteFile, NoteInclusionProof, - NoteInputs, NoteMetadata, NoteRecipient, NoteScript, + NoteStorage, NoteTag, NoteType, }; @@ -167,8 +167,8 @@ mod tests { let serial_num = Word::from([0, 1, 2, 3u32]); let script = NoteScript::mock(); - let note_inputs = NoteInputs::new(vec![target.prefix().into()]).unwrap(); - let recipient = NoteRecipient::new(serial_num, script, note_inputs); + let note_storage = NoteStorage::new(vec![target.prefix().into()]).unwrap(); + let recipient = NoteRecipient::new(serial_num, script, note_storage); let asset = Asset::Fungible(FungibleAsset::new(faucet, 100).unwrap()); let metadata = NoteMetadata::new(faucet, NoteType::Public, NoteTag::from(123)); diff --git a/crates/miden-protocol/src/note/inputs.rs b/crates/miden-protocol/src/note/inputs.rs deleted file mode 100644 index 7d7b04a0c6..0000000000 --- a/crates/miden-protocol/src/note/inputs.rs +++ /dev/null @@ -1,158 +0,0 @@ -use alloc::vec::Vec; - -use crate::errors::NoteError; -use crate::utils::serde::{ - ByteReader, - ByteWriter, - Deserializable, - DeserializationError, - Serializable, -}; -use crate::{Felt, Hasher, MAX_INPUTS_PER_NOTE, Word}; - -// NOTE INPUTS -// ================================================================================================ - -/// A container for note inputs. -/// -/// A note can be associated with up to 1024 input values. Each value is represented by a single -/// field element. Thus, note input values can contain up to ~8 KB of data. -/// -/// All inputs associated with a note can be reduced to a single commitment which is computed as an -/// RPO256 hash over the input elements. -#[derive(Clone, Debug)] -pub struct NoteInputs { - values: Vec, - commitment: Word, -} - -impl NoteInputs { - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - - /// Returns [NoteInputs] instantiated from the provided values. - /// - /// # Errors - /// Returns an error if the number of provided inputs is greater than 1024. - pub fn new(values: Vec) -> Result { - if values.len() > MAX_INPUTS_PER_NOTE { - return Err(NoteError::TooManyInputs(values.len())); - } - - let commitment = Hasher::hash_elements(&values); - - Ok(Self { values, commitment }) - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns a commitment to these inputs. - pub fn commitment(&self) -> Word { - self.commitment - } - - /// Returns the number of input values. - /// - /// The returned value is guaranteed to be smaller than or equal to 1024. - pub fn num_values(&self) -> u16 { - const _: () = assert!(MAX_INPUTS_PER_NOTE <= u16::MAX as usize); - debug_assert!( - self.values.len() <= MAX_INPUTS_PER_NOTE, - "The constructor should have checked the number of inputs" - ); - self.values.len() as u16 - } - - /// Returns a reference to the input values. - pub fn values(&self) -> &[Felt] { - &self.values - } - - /// Returns the note's input as a vector of field elements. - pub fn to_elements(&self) -> Vec { - self.values.to_vec() - } -} - -impl Default for NoteInputs { - fn default() -> Self { - Self::new(vec![]).expect("empty values should be valid") - } -} - -impl PartialEq for NoteInputs { - fn eq(&self, other: &Self) -> bool { - let NoteInputs { values: inputs, commitment: _ } = self; - inputs == &other.values - } -} - -impl Eq for NoteInputs {} - -// CONVERSION -// ================================================================================================ - -impl From for Vec { - fn from(value: NoteInputs) -> Self { - value.values - } -} - -impl TryFrom> for NoteInputs { - type Error = NoteError; - - fn try_from(value: Vec) -> Result { - NoteInputs::new(value) - } -} - -// SERIALIZATION -// ================================================================================================ - -impl Serializable for NoteInputs { - fn write_into(&self, target: &mut W) { - let NoteInputs { values, commitment: _commitment } = self; - target.write_u16(values.len().try_into().expect("inputs len is not a u16 value")); - target.write_many(values); - } -} - -impl Deserializable for NoteInputs { - fn read_from(source: &mut R) -> Result { - let num_values = source.read_u16()? as usize; - let values = source.read_many::(num_values)?; - Self::new(values).map_err(|v| DeserializationError::InvalidValue(format!("{v}"))) - } -} - -// TESTS -// ================================================================================================ - -#[cfg(test)] -mod tests { - use miden_crypto::utils::Deserializable; - - use super::{Felt, NoteInputs, Serializable}; - - #[test] - fn test_input_ordering() { - // inputs are provided in reverse stack order - let inputs = vec![Felt::new(1), Felt::new(2), Felt::new(3)]; - // we expect the inputs to remain in reverse stack order. - let expected_ordering = vec![Felt::new(1), Felt::new(2), Felt::new(3)]; - - let note_inputs = NoteInputs::new(inputs).expect("note created should succeed"); - assert_eq!(&expected_ordering, ¬e_inputs.values); - } - - #[test] - fn test_input_serialization() { - let inputs = vec![Felt::new(1), Felt::new(2), Felt::new(3)]; - let note_inputs = NoteInputs::new(inputs).unwrap(); - - let bytes = note_inputs.to_bytes(); - let parsed_note_inputs = NoteInputs::read_from_bytes(&bytes).unwrap(); - assert_eq!(note_inputs, parsed_note_inputs); - } -} diff --git a/crates/miden-protocol/src/note/mod.rs b/crates/miden-protocol/src/note/mod.rs index 27aeda9a54..729f651f9e 100644 --- a/crates/miden-protocol/src/note/mod.rs +++ b/crates/miden-protocol/src/note/mod.rs @@ -15,8 +15,8 @@ pub use details::NoteDetails; mod header; pub use header::{NoteHeader, compute_note_commitment}; -mod inputs; -pub use inputs::NoteInputs; +mod storage; +pub use storage::NoteStorage; mod metadata; pub use metadata::NoteMetadata; @@ -68,7 +68,7 @@ pub use file::NoteFile; /// /// Notes consist of note metadata and details. Note metadata is always public, but details may be /// either public, encrypted, or private, depending on the note type. Note details consist of note -/// assets, script, inputs, and a serial number, the three latter grouped into a recipient object. +/// assets, script, storage, and a serial number, the three latter grouped into a recipient object. /// /// Note details can be reduced to two unique identifiers: [NoteId] and [Nullifier]. The former is /// publicly associated with a note, while the latter is known only to entities which have access @@ -78,10 +78,10 @@ pub use file::NoteFile; /// note's script determines the conditions required for the note consumption, i.e. the target /// account of a P2ID or conditions of a SWAP, and the effects of the note. The serial number has /// a double duty of preventing double spend, and providing unlikability to the consumer of a note. -/// The note's inputs allow for customization of its script. +/// The note's storage allows for customization of its script. /// /// To create a note, the kernel does not require all the information above, a user can create a -/// note only with the commitment to the script, inputs, the serial number (i.e., the recipient), +/// note only with the commitment to the script, storage, the serial number (i.e., the recipient), /// and the kernel only verifies the source account has the assets necessary for the note creation. /// See [NoteRecipient] for more details. #[derive(Clone, Debug, PartialEq, Eq)] @@ -140,9 +140,9 @@ impl Note { self.details.script() } - /// Returns the note's recipient inputs which customizes the script's behavior. - pub fn inputs(&self) -> &NoteInputs { - self.details.inputs() + /// Returns the note's recipient storage which customizes the script's behavior. + pub fn storage(&self) -> &NoteStorage { + self.details.storage() } /// Returns the note's recipient. diff --git a/crates/miden-protocol/src/note/note_id.rs b/crates/miden-protocol/src/note/note_id.rs index 054ca5a564..1d522757de 100644 --- a/crates/miden-protocol/src/note/note_id.rs +++ b/crates/miden-protocol/src/note/note_id.rs @@ -24,7 +24,7 @@ use crate::utils::serde::{ /// /// where `recipient` is defined as: /// -/// > hash(hash(hash(serial_num, ZERO), script_root), input_commitment) +/// > hash(hash(hash(serial_num, ZERO), script_root), storage_commitment) /// /// This achieves the following properties: /// - Every note can be reduced to a single unique ID. diff --git a/crates/miden-protocol/src/note/note_type.rs b/crates/miden-protocol/src/note/note_type.rs index f426ea58ab..32a0cec8ce 100644 --- a/crates/miden-protocol/src/note/note_type.rs +++ b/crates/miden-protocol/src/note/note_type.rs @@ -17,7 +17,6 @@ use crate::utils::serde::{ // Keep these masks in sync with `miden-lib/asm/miden/kernels/tx/tx.masm` const PUBLIC: u8 = 0b01; const PRIVATE: u8 = 0b10; -const ENCRYPTED: u8 = 0b11; // NOTE TYPE // ================================================================================================ @@ -28,9 +27,6 @@ pub enum NoteType { /// Notes with this type have only their hash published to the network. Private = PRIVATE, - /// Notes with this type are shared with the network encrypted. - Encrypted = ENCRYPTED, - /// Notes with this type are fully shared with the network. Public = PUBLIC, } @@ -53,7 +49,6 @@ impl TryFrom for NoteType { fn try_from(value: u8) -> Result { match value { PRIVATE => Ok(NoteType::Private), - ENCRYPTED => Ok(NoteType::Encrypted), PUBLIC => Ok(NoteType::Public), _ => Err(NoteError::UnknownNoteType(format!("0b{value:b}").into())), } @@ -101,7 +96,6 @@ impl FromStr for NoteType { fn from_str(s: &str) -> Result { match s { "private" => Ok(NoteType::Private), - "encrypted" => Ok(NoteType::Encrypted), "public" => Ok(NoteType::Public), _ => Err(NoteError::UnknownNoteType(s.into())), } @@ -123,7 +117,6 @@ impl Deserializable for NoteType { let note_type = match discriminant { PRIVATE => NoteType::Private, - ENCRYPTED => NoteType::Encrypted, PUBLIC => NoteType::Public, discriminant => { return Err(DeserializationError::InvalidValue(format!( @@ -143,7 +136,6 @@ impl Display for NoteType { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { NoteType::Private => write!(f, "private"), - NoteType::Encrypted => write!(f, "encrypted"), NoteType::Public => write!(f, "public"), } } @@ -155,7 +147,7 @@ fn test_from_str_note_type() { use crate::alloc::string::ToString; - for string in ["private", "public", "encrypted"] { + for string in ["private", "public"] { let parsed_note_type = NoteType::from_str(string).unwrap(); assert_eq!(parsed_note_type.to_string(), string); } @@ -163,9 +155,6 @@ fn test_from_str_note_type() { let public_type_invalid_err = NoteType::from_str("puBlIc").unwrap_err(); assert_matches!(public_type_invalid_err, NoteError::UnknownNoteType(_)); - let encrypted_type_invalid = NoteType::from_str("eNcrYptEd").unwrap_err(); - assert_matches!(encrypted_type_invalid, NoteError::UnknownNoteType(_)); - let invalid_type = NoteType::from_str("invalid").unwrap_err(); assert_matches!(invalid_type, NoteError::UnknownNoteType(_)); } diff --git a/crates/miden-protocol/src/note/nullifier.rs b/crates/miden-protocol/src/note/nullifier.rs index 26377caa8a..1f3f3f4736 100644 --- a/crates/miden-protocol/src/note/nullifier.rs +++ b/crates/miden-protocol/src/note/nullifier.rs @@ -30,13 +30,13 @@ const NULLIFIER_PREFIX_SHIFT: u8 = 48; /// /// A note's nullifier is computed as: /// -/// > hash(serial_num, script_root, input_commitment, asset_commitment). +/// > hash(serial_num, script_root, storage_commitment, asset_commitment). /// /// This achieves the following properties: /// - Every note can be reduced to a single unique nullifier. /// - We cannot derive a note's commitment from its nullifier, or a note's nullifier from its hash. /// - To compute the nullifier we must know all components of the note: serial_num, script_root, -/// input_commitment and asset_commitment. +/// storage_commitment and asset_commitment. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, WordWrapper)] pub struct Nullifier(Word); @@ -44,14 +44,14 @@ impl Nullifier { /// Returns a new note [Nullifier] instantiated from the provided digest. pub fn new( script_root: Word, - inputs_commitment: Word, + storage_commitment: Word, asset_commitment: Word, serial_num: Word, ) -> Self { let mut elements = [ZERO; 4 * WORD_SIZE]; elements[..4].copy_from_slice(serial_num.as_elements()); elements[4..8].copy_from_slice(script_root.as_elements()); - elements[8..12].copy_from_slice(inputs_commitment.as_elements()); + elements[8..12].copy_from_slice(storage_commitment.as_elements()); elements[12..].copy_from_slice(asset_commitment.as_elements()); Self(Hasher::hash_elements(&elements)) } @@ -103,7 +103,7 @@ impl From<&NoteDetails> for Nullifier { fn from(note: &NoteDetails) -> Self { Self::new( note.script().root(), - note.inputs().commitment(), + note.storage().commitment(), note.assets().commitment(), note.serial_num(), ) diff --git a/crates/miden-protocol/src/note/partial.rs b/crates/miden-protocol/src/note/partial.rs index 03553c3d2e..f7aea1bcc6 100644 --- a/crates/miden-protocol/src/note/partial.rs +++ b/crates/miden-protocol/src/note/partial.rs @@ -18,7 +18,7 @@ use crate::Word; /// /// Partial note consists of [NoteMetadata], [NoteAssets], and a recipient digest (see /// [super::NoteRecipient]). However, it does not contain detailed recipient info, including -/// note script, note inputs, and note's serial number. This means that a partial note is +/// note script, note storage, and note's serial number. This means that a partial note is /// sufficient to compute note ID and note header, but not sufficient to compute note nullifier, /// and generally does not have enough info to execute the note. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/miden-protocol/src/note/recipient.rs b/crates/miden-protocol/src/note/recipient.rs index 78cc247bf3..9948cfeb39 100644 --- a/crates/miden-protocol/src/note/recipient.rs +++ b/crates/miden-protocol/src/note/recipient.rs @@ -6,8 +6,8 @@ use super::{ Deserializable, DeserializationError, Hasher, - NoteInputs, NoteScript, + NoteStorage, Serializable, Word, }; @@ -16,24 +16,24 @@ use super::{ /// /// The recipient is not an account address, instead it is a value that describes when a note /// can be consumed. Because not all notes have predetermined consumer addresses, e.g. swap -/// notes can be consumed by anyone, the recipient is defined as the code and its inputs, that +/// notes can be consumed by anyone, the recipient is defined as the code and its storage, that /// when successfully executed results in the note's consumption. /// /// Recipient is computed as: /// -/// > hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment) +/// > hash(hash(hash(serial_num, [0; 4]), script_root), storage_commitment) #[derive(Clone, Debug, PartialEq, Eq)] pub struct NoteRecipient { serial_num: Word, script: NoteScript, - inputs: NoteInputs, + storage: NoteStorage, digest: Word, } impl NoteRecipient { - pub fn new(serial_num: Word, script: NoteScript, inputs: NoteInputs) -> Self { - let digest = compute_recipient_digest(serial_num, &script, &inputs); - Self { serial_num, script, inputs, digest } + pub fn new(serial_num: Word, script: NoteScript, storage: NoteStorage) -> Self { + let digest = compute_recipient_digest(serial_num, &script, &storage); + Self { serial_num, script, storage, digest } } // PUBLIC ACCESSORS @@ -49,9 +49,9 @@ impl NoteRecipient { &self.script } - /// The recipient's inputs which customizes the script's behavior. - pub fn inputs(&self) -> &NoteInputs { - &self.inputs + /// The recipient's storage which customizes the script's behavior. + pub fn storage(&self) -> &NoteStorage { + &self.storage } /// The recipient's digest, which commits to its details. @@ -62,10 +62,10 @@ impl NoteRecipient { } } -fn compute_recipient_digest(serial_num: Word, script: &NoteScript, inputs: &NoteInputs) -> Word { +fn compute_recipient_digest(serial_num: Word, script: &NoteScript, storage: &NoteStorage) -> Word { let serial_num_hash = Hasher::merge(&[serial_num, Word::empty()]); let merge_script = Hasher::merge(&[serial_num_hash, script.root()]); - Hasher::merge(&[merge_script, inputs.commitment()]) + Hasher::merge(&[merge_script, storage.commitment()]) } // SERIALIZATION @@ -75,7 +75,7 @@ impl Serializable for NoteRecipient { fn write_into(&self, target: &mut W) { let Self { script, - inputs, + storage, serial_num, // These attributes don't have to be serialized, they can be re-computed from the rest @@ -84,7 +84,7 @@ impl Serializable for NoteRecipient { } = self; script.write_into(target); - inputs.write_into(target); + storage.write_into(target); serial_num.write_into(target); } } @@ -92,9 +92,9 @@ impl Serializable for NoteRecipient { impl Deserializable for NoteRecipient { fn read_from(source: &mut R) -> Result { let script = NoteScript::read_from(source)?; - let inputs = NoteInputs::read_from(source)?; + let storage = NoteStorage::read_from(source)?; let serial_num = Word::read_from(source)?; - Ok(Self::new(serial_num, script, inputs)) + Ok(Self::new(serial_num, script, storage)) } } diff --git a/crates/miden-protocol/src/note/script.rs b/crates/miden-protocol/src/note/script.rs index eb11c82e0b..5c1aea31fb 100644 --- a/crates/miden-protocol/src/note/script.rs +++ b/crates/miden-protocol/src/note/script.rs @@ -14,7 +14,7 @@ use crate::utils::serde::{ DeserializationError, Serializable, }; -use crate::vm::Program; +use crate::vm::{AdviceMap, Program}; use crate::{PrettyPrint, Word}; // NOTE SCRIPT @@ -76,6 +76,24 @@ impl NoteScript { pub fn entrypoint(&self) -> MastNodeId { self.entrypoint } + + /// Returns a new [NoteScript] with the provided advice map entries merged into the + /// underlying [MastForest]. + /// + /// This allows adding advice map entries to an already-compiled note script, + /// which is useful when the entries are determined after script compilation. + pub fn with_advice_map(self, advice_map: AdviceMap) -> Self { + if advice_map.is_empty() { + return self; + } + + let mut mast = (*self.mast).clone(); + mast.advice_map_mut().extend(advice_map); + Self { + mast: Arc::new(mast), + entrypoint: self.entrypoint, + } + } } // CONVERSIONS INTO NOTE SCRIPT @@ -217,4 +235,32 @@ mod tests { assert_eq!(note_script, decoded); } + + #[test] + fn test_note_script_with_advice_map() { + use miden_core::{AdviceMap, Word}; + + let assembler = Assembler::default(); + let program = assembler.assemble_program("begin nop end").unwrap(); + let script = NoteScript::new(program); + + assert!(script.mast().advice_map().is_empty()); + + // Empty advice map should be a no-op + let original_root = script.root(); + let script = script.with_advice_map(AdviceMap::default()); + assert_eq!(original_root, script.root()); + + // Non-empty advice map should add entries + let key = Word::from([5u32, 6, 7, 8]); + let value = vec![Felt::new(100)]; + let mut advice_map = AdviceMap::default(); + advice_map.insert(key, value.clone()); + + let script = script.with_advice_map(advice_map); + + let mast = script.mast(); + let stored = mast.advice_map().get(&key).expect("entry should be present"); + assert_eq!(stored.as_ref(), value.as_slice()); + } } diff --git a/crates/miden-protocol/src/note/storage.rs b/crates/miden-protocol/src/note/storage.rs new file mode 100644 index 0000000000..14567f843a --- /dev/null +++ b/crates/miden-protocol/src/note/storage.rs @@ -0,0 +1,163 @@ +use alloc::vec::Vec; + +use crate::errors::NoteError; +use crate::utils::serde::{ + ByteReader, + ByteWriter, + Deserializable, + DeserializationError, + Serializable, +}; +use crate::{Felt, Hasher, MAX_NOTE_STORAGE_ITEMS, Word}; + +// NOTE STORAGE +// ================================================================================================ + +/// A container for note storage items. +/// +/// A note can be associated with up to 1024 storage items. Each item is represented by a single +/// field element. Thus, note storage can contain up to ~8 KB of data. +/// +/// All storage items associated with a note can be reduced to a single commitment which is +/// computed as an RPO256 hash over the storage elements. +#[derive(Clone, Debug)] +pub struct NoteStorage { + items: Vec, + commitment: Word, +} + +impl NoteStorage { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + + /// Returns [NoteStorage] instantiated from the provided items. + /// + /// # Errors + /// Returns an error if the number of provided storage items is greater than 1024. + pub fn new(items: Vec) -> Result { + if items.len() > MAX_NOTE_STORAGE_ITEMS { + return Err(NoteError::TooManyStorageItems(items.len())); + } + + let commitment = Hasher::hash_elements(&items); + + Ok(Self { items, commitment }) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns a commitment to this storage. + pub fn commitment(&self) -> Word { + self.commitment + } + + /// Returns the number of storage items. + /// + /// The returned value is guaranteed to be smaller than or equal to [`MAX_NOTE_STORAGE_ITEMS`]. + pub fn num_items(&self) -> u16 { + const _: () = assert!(MAX_NOTE_STORAGE_ITEMS <= u16::MAX as usize); + debug_assert!( + self.items.len() <= MAX_NOTE_STORAGE_ITEMS, + "The constructor should have checked the number of storage items" + ); + self.items.len() as u16 + } + + /// Returns `true` if the storage has no items. + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Returns a reference to the storage items. + pub fn items(&self) -> &[Felt] { + &self.items + } + + /// Returns the note's storage as a vector of field elements. + pub fn to_elements(&self) -> Vec { + self.items.to_vec() + } +} + +impl Default for NoteStorage { + fn default() -> Self { + Self::new(vec![]).expect("empty storage should be valid") + } +} + +impl PartialEq for NoteStorage { + fn eq(&self, other: &Self) -> bool { + let NoteStorage { items, commitment: _ } = self; + items == &other.items + } +} + +impl Eq for NoteStorage {} + +// CONVERSION +// ================================================================================================ + +impl From for Vec { + fn from(value: NoteStorage) -> Self { + value.items + } +} + +impl TryFrom> for NoteStorage { + type Error = NoteError; + + fn try_from(value: Vec) -> Result { + NoteStorage::new(value) + } +} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for NoteStorage { + fn write_into(&self, target: &mut W) { + let NoteStorage { items, commitment: _commitment } = self; + target.write_u16(items.len().try_into().expect("storage items len is not a u16 value")); + target.write_many(items); + } +} + +impl Deserializable for NoteStorage { + fn read_from(source: &mut R) -> Result { + let len = source.read_u16()? as usize; + let items = source.read_many::(len)?; + Self::new(items).map_err(|v| DeserializationError::InvalidValue(format!("{v}"))) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_crypto::utils::Deserializable; + + use super::{Felt, NoteStorage, Serializable}; + + #[test] + fn test_storage_item_ordering() { + // storage items are provided in reverse stack order + let storage_items = vec![Felt::new(1), Felt::new(2), Felt::new(3)]; + // we expect the storage items to remain in reverse stack order. + let expected_ordering = vec![Felt::new(1), Felt::new(2), Felt::new(3)]; + + let note_storage = NoteStorage::new(storage_items).expect("note created should succeed"); + assert_eq!(&expected_ordering, note_storage.items()); + } + + #[test] + fn test_storage_serialization() { + let storage_items = vec![Felt::new(1), Felt::new(2), Felt::new(3)]; + let note_storage = NoteStorage::new(storage_items).unwrap(); + + let bytes = note_storage.to_bytes(); + let parsed_note_storage = NoteStorage::read_from_bytes(&bytes).unwrap(); + assert_eq!(note_storage, parsed_note_storage); + } +} diff --git a/crates/miden-protocol/src/testing/note.rs b/crates/miden-protocol/src/testing/note.rs index 56f2bedf84..4ce184788f 100644 --- a/crates/miden-protocol/src/testing/note.rs +++ b/crates/miden-protocol/src/testing/note.rs @@ -6,10 +6,10 @@ use crate::asset::FungibleAsset; use crate::note::{ Note, NoteAssets, - NoteInputs, NoteMetadata, NoteRecipient, NoteScript, + NoteStorage, NoteTag, NoteType, }; @@ -29,7 +29,7 @@ impl Note { NoteType::Private, NoteTag::with_account_target(sender_id), ); - let inputs = NoteInputs::new(Vec::new()).unwrap(); + let inputs = NoteStorage::new(Vec::new()).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, inputs); Note::new(assets, metadata, recipient) diff --git a/crates/miden-protocol/src/transaction/inputs/mod.rs b/crates/miden-protocol/src/transaction/inputs/mod.rs index a8122e0908..1e879e660f 100644 --- a/crates/miden-protocol/src/transaction/inputs/mod.rs +++ b/crates/miden-protocol/src/transaction/inputs/mod.rs @@ -3,8 +3,8 @@ use alloc::vec::Vec; use core::fmt::Debug; use miden_core::utils::{Deserializable, Serializable}; -use miden_crypto::merkle::NodeIndex; use miden_crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof}; +use miden_crypto::merkle::{MerkleError, NodeIndex}; use super::PartialBlockchain; use crate::account::{ @@ -19,7 +19,7 @@ use crate::account::{ StorageSlotId, StorageSlotName, }; -use crate::asset::{AssetVaultKey, AssetWitness, PartialVault}; +use crate::asset::{Asset, AssetVaultKey, AssetWitness, PartialVault}; use crate::block::account_tree::{AccountWitness, account_id_to_smt_index}; use crate::block::{BlockHeader, BlockNumber}; use crate::crypto::merkle::SparseMerklePath; @@ -51,8 +51,6 @@ pub struct TransactionInputs { tx_args: TransactionArgs, advice_inputs: AdviceInputs, foreign_account_code: Vec, - /// Pre-fetched asset witnesses for note assets and the fee asset. - asset_witnesses: Vec, /// Storage slot names for foreign accounts. foreign_account_slot_names: BTreeMap, } @@ -110,14 +108,20 @@ impl TransactionInputs { tx_args: TransactionArgs::default(), advice_inputs: AdviceInputs::default(), foreign_account_code: Vec::new(), - asset_witnesses: Vec::new(), foreign_account_slot_names: BTreeMap::new(), }) } /// Replaces the transaction inputs and assigns the given asset witnesses. pub fn with_asset_witnesses(mut self, witnesses: Vec) -> Self { - self.asset_witnesses = witnesses; + for witness in witnesses { + self.advice_inputs.store.extend(witness.authenticated_nodes()); + let smt_proof = SmtProof::from(witness); + self.advice_inputs + .map + .extend([(smt_proof.leaf().hash(), smt_proof.leaf().to_elements())]); + } + self } @@ -210,11 +214,6 @@ impl TransactionInputs { &self.foreign_account_code } - /// Returns the pre-fetched witnesses for note and fee assets. - pub fn asset_witnesses(&self) -> &[AssetWitness] { - &self.asset_witnesses - } - /// Returns the foreign account storage slot names. pub fn foreign_account_slot_names(&self) -> &BTreeMap { &self.foreign_account_slot_names @@ -263,6 +262,12 @@ impl TransactionInputs { } /// Reads the vault asset witnesses for the given account and vault keys. + /// + /// # Errors + /// Returns an error if: + /// - A Merkle tree with the specified root is not present in the advice data of these inputs. + /// - Witnesses for any of the requested assets are not in the specified Merkle tree. + /// - Construction of the Merkle path or the leaf node for the witness fails. pub fn read_vault_asset_witnesses( &self, vault_root: Word, @@ -292,6 +297,64 @@ impl TransactionInputs { Ok(asset_witnesses) } + /// Returns true if the witness for the specified asset key is present in these inputs. + /// + /// Note that this does not verify the witness' validity (i.e., that the witness is for a valid + /// asset). + pub fn has_vault_asset_witness(&self, vault_root: Word, asset_key: &AssetVaultKey) -> bool { + let smt_index: NodeIndex = asset_key.to_leaf_index().into(); + + // make sure the path is in the Merkle store + if !self.advice_inputs.store.has_path(vault_root, smt_index) { + return false; + } + + // make sure the node pre-image is in the Merkle store + match self.advice_inputs.store.get_node(vault_root, smt_index) { + Ok(node) => self.advice_inputs.map.contains_key(&node), + Err(_) => false, + } + } + + /// Reads the asset from the specified vault under the specified key; returns `None` if the + /// specified asset is not present in these inputs. + /// + /// # Errors + /// Returns an error if: + /// - A Merkle tree with the specified root is not present in the advice data of these inputs. + /// - Construction of the leaf node or the asset fails. + pub fn read_vault_asset( + &self, + vault_root: Word, + asset_key: AssetVaultKey, + ) -> Result, TransactionInputsExtractionError> { + // Get the node corresponding to the asset_key; if not found return None + let smt_index = asset_key.to_leaf_index(); + let merkle_node = match self.advice_inputs.store.get_node(vault_root, smt_index.into()) { + Ok(node) => node, + Err(MerkleError::NodeIndexNotFoundInStore(..)) => return Ok(None), + Err(err) => return Err(err.into()), + }; + + // Construct SMT leaf for this asset key + let smt_leaf_elements = self + .advice_inputs + .map + .get(&merkle_node) + .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?; + let smt_leaf = smt_leaf_from_elements(smt_leaf_elements, smt_index)?; + + // Find the asset in the SMT leaf + let asset = smt_leaf + .entries() + .iter() + .find(|(key, _value)| key == asset_key.as_word()) + .map(|(_key, value)| Asset::try_from(value)) + .transpose()?; + + Ok(asset) + } + /// Reads AccountInputs for a foreign account from the advice inputs. /// /// This function reverses the process of [`TransactionAdviceInputs::add_foreign_accounts`] by: @@ -432,7 +495,6 @@ impl Serializable for TransactionInputs { self.tx_args.write_into(target); self.advice_inputs.write_into(target); self.foreign_account_code.write_into(target); - self.asset_witnesses.write_into(target); self.foreign_account_slot_names.write_into(target); } } @@ -448,7 +510,6 @@ impl Deserializable for TransactionInputs { let tx_args = TransactionArgs::read_from(source)?; let advice_inputs = AdviceInputs::read_from(source)?; let foreign_account_code = Vec::::read_from(source)?; - let asset_witnesses = Vec::::read_from(source)?; let foreign_account_slot_names = BTreeMap::::read_from(source)?; @@ -460,7 +521,6 @@ impl Deserializable for TransactionInputs { tx_args, advice_inputs, foreign_account_code, - asset_witnesses, foreign_account_slot_names, }) } diff --git a/crates/miden-protocol/src/transaction/inputs/tests.rs b/crates/miden-protocol/src/transaction/inputs/tests.rs index 09500ef25b..1d5547de7a 100644 --- a/crates/miden-protocol/src/transaction/inputs/tests.rs +++ b/crates/miden-protocol/src/transaction/inputs/tests.rs @@ -55,7 +55,6 @@ fn test_read_foreign_account_inputs_missing_data() { tx_args: crate::transaction::TransactionArgs::default(), advice_inputs: crate::vm::AdviceInputs::default(), foreign_account_code: Vec::new(), - asset_witnesses: Vec::new(), foreign_account_slot_names: BTreeMap::new(), }; @@ -139,7 +138,6 @@ fn test_read_foreign_account_inputs_with_storage_data() { tx_args: crate::transaction::TransactionArgs::default(), advice_inputs, foreign_account_code: vec![code], - asset_witnesses: Vec::new(), foreign_account_slot_names, }; @@ -261,7 +259,6 @@ fn test_read_foreign_account_inputs_with_proper_witness() { tx_args: crate::transaction::TransactionArgs::default(), advice_inputs, foreign_account_code: vec![code], - asset_witnesses: Vec::new(), foreign_account_slot_names: BTreeMap::new(), }; @@ -348,7 +345,6 @@ fn test_transaction_inputs_serialization_with_foreign_slot_names() { tx_args: crate::transaction::TransactionArgs::default(), advice_inputs: crate::vm::AdviceInputs::default(), foreign_account_code: Vec::new(), - asset_witnesses: Vec::new(), foreign_account_slot_names, }; diff --git a/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs b/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs index aec2c04229..65b445bdd6 100644 --- a/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs +++ b/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs @@ -3,11 +3,9 @@ use alloc::vec::Vec; use miden_processor::AdviceMutation; use crate::account::{AccountHeader, AccountId, PartialAccount}; -use crate::asset::AssetWitness; use crate::block::account_tree::AccountWitness; use crate::crypto::SequentialCommit; use crate::crypto::merkle::InnerNodeInfo; -use crate::crypto::merkle::smt::SmtProof; use crate::note::NoteAttachmentContent; use crate::transaction::{ AccountInputs, @@ -75,10 +73,6 @@ impl TransactionAdviceInputs { } } - tx_inputs.asset_witnesses().iter().for_each(|asset_witness| { - inputs.add_asset_witness(asset_witness.clone()); - }); - // Extend with extra user-supplied advice. inputs.extend(tx_inputs.tx_args().advice_inputs().clone()); @@ -309,14 +303,6 @@ impl TransactionAdviceInputs { self.extend_merkle_store(witness.authenticated_nodes()); } - /// Adds an asset witness to the advice inputs. - fn add_asset_witness(&mut self, witness: AssetWitness) { - self.extend_merkle_store(witness.authenticated_nodes()); - - let smt_proof = SmtProof::from(witness); - self.extend_map([(smt_proof.leaf().hash(), smt_proof.leaf().to_elements())]); - } - // NOTE INJECTION // -------------------------------------------------------------------------------------------- @@ -350,8 +336,8 @@ impl TransactionAdviceInputs { let recipient = note.recipient(); let note_arg = tx_inputs.tx_args().get_note_args(note.id()).unwrap_or(&EMPTY_WORD); - // recipient inputs - self.add_map_entry(recipient.inputs().commitment(), recipient.inputs().to_elements()); + // recipient storage + self.add_map_entry(recipient.storage().commitment(), recipient.storage().to_elements()); // assets commitments self.add_map_entry(assets.commitment(), assets.to_padded_assets()); // array attachments @@ -367,12 +353,12 @@ impl TransactionAdviceInputs { // note details / metadata note_data.extend(recipient.serial_num()); note_data.extend(*recipient.script().root()); - note_data.extend(*recipient.inputs().commitment()); + note_data.extend(*recipient.storage().commitment()); note_data.extend(*assets.commitment()); note_data.extend(*note_arg); note_data.extend(note.metadata().to_header_word()); note_data.extend(note.metadata().to_attachment_word()); - note_data.push(recipient.inputs().num_values().into()); + note_data.push(recipient.storage().num_items().into()); note_data.push((assets.num_assets() as u32).into()); note_data.extend(assets.to_padded_assets()); diff --git a/crates/miden-protocol/src/transaction/kernel/memory.rs b/crates/miden-protocol/src/transaction/kernel/memory.rs index 8b33b214ae..d711c2c098 100644 --- a/crates/miden-protocol/src/transaction/kernel/memory.rs +++ b/crates/miden-protocol/src/transaction/kernel/memory.rs @@ -345,22 +345,22 @@ pub const NOTE_MEM_SIZE: MemoryAddress = 2048; // Each nullifier occupies a single word. A data section for each note consists of exactly 2048 // elements and is laid out like so: // -// ┌──────┬────────┬────────┬────────┬────────────┬───────────┬──────────┬────────────┬───────┬────────┬────────┬───────┬─────┬───────┬─────────┬ -// │ NOTE │ SERIAL │ SCRIPT │ INPUTS │ ASSETS | RECIPIENT │ METADATA │ ATTACHMENT │ NOTE │ NUM │ NUM │ ASSET │ ... │ ASSET │ PADDING │ -// │ ID │ NUM │ ROOT │ HASH │ COMMITMENT | │ HEADER │ │ ARGS │ INPUTS │ ASSETS │ 0 │ │ n │ │ -// ├──────┼────────┼────────┼────────┼────────────┼───────────┼──────────┼────────────┼───────┼────────┼────────┼───────┼─────┼───────┼─────────┤ -// 0 4 8 12 16 20 24 28 32 36 40 44 + 4n +// ┌──────┬────────┬────────┬─────────┬────────────┬───────────┬──────────┬────────────┬───────┬─────────┬────────┬───────┬─────┬───────┬─────────┬ +// │ NOTE │ SERIAL │ SCRIPT │ STORAGE │ ASSETS | RECIPIENT │ METADATA │ ATTACHMENT │ NOTE │ STORAGE │ NUM │ ASSET │ ... │ ASSET │ PADDING │ +// │ ID │ NUM │ ROOT │ COMM │ COMMITMENT | │ HEADER │ │ ARGS │ LENGTH │ ASSETS │ 0 │ │ n │ │ +// ├──────┼────────┼────────┼─────────┼────────────┼───────────┼──────────┼────────────┼───────┼─────────┼────────┼───────┼─────┼───────┼─────────┤ +// 0 4 8 12 16 20 24 28 32 36 40 44 + 4n // -// - NUM_INPUTS is encoded as [num_inputs, 0, 0, 0]. +// - NUM_STORAGE_ITEMS is encoded as [num_storage_items, 0, 0, 0]. // - NUM_ASSETS is encoded as [num_assets, 0, 0, 0]. -// - INPUTS_COMMITMENT is the key to look up note inputs in the advice map. +// - STORAGE_COMMITMENT is the key to look up note storage in the advice map. // - ASSETS_COMMITMENT is the key to look up note assets in the advice map. // -// Notice that note input values are not loaded to the memory, only their length. In order to obtain -// the input values the advice map should be used: they are stored there as -// `INPUTS_COMMITMENT -> INPUTS`. +// Notice that note storage values are not loaded to the memory, only their length. In order to obtain +// the storage values the advice map should be used: they are stored there as +// `STORAGE_COMMITMENT -> STORAGE`. // -// As opposed to the asset values, input values are never used in kernel memory, so their presence +// As opposed to the asset values, storage values are never used in kernel memory, so their presence // there is unnecessary. /// The memory address at which the input note section begins. @@ -379,13 +379,13 @@ pub const NUM_INPUT_NOTES_PTR: MemoryAddress = INPUT_NOTE_SECTION_PTR; pub const INPUT_NOTE_ID_OFFSET: MemoryOffset = 0; pub const INPUT_NOTE_SERIAL_NUM_OFFSET: MemoryOffset = 4; pub const INPUT_NOTE_SCRIPT_ROOT_OFFSET: MemoryOffset = 8; -pub const INPUT_NOTE_INPUTS_COMMITMENT_OFFSET: MemoryOffset = 12; +pub const INPUT_NOTE_STORAGE_COMMITMENT_OFFSET: MemoryOffset = 12; pub const INPUT_NOTE_ASSETS_COMMITMENT_OFFSET: MemoryOffset = 16; pub const INPUT_NOTE_RECIPIENT_OFFSET: MemoryOffset = 20; pub const INPUT_NOTE_METADATA_HEADER_OFFSET: MemoryOffset = 24; pub const INPUT_NOTE_ATTACHMENT_OFFSET: MemoryOffset = 28; pub const INPUT_NOTE_ARGS_OFFSET: MemoryOffset = 32; -pub const INPUT_NOTE_NUM_INPUTS_OFFSET: MemoryOffset = 36; +pub const INPUT_NOTE_NUM_STORAGE_ITEMS_OFFSET: MemoryOffset = 36; pub const INPUT_NOTE_NUM_ASSETS_OFFSET: MemoryOffset = 40; pub const INPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 44; diff --git a/crates/miden-protocol/src/transaction/kernel/procedures.rs b/crates/miden-protocol/src/transaction/kernel/procedures.rs index 60c5fb4349..3878d7c51e 100644 --- a/crates/miden-protocol/src/transaction/kernel/procedures.rs +++ b/crates/miden-protocol/src/transaction/kernel/procedures.rs @@ -73,7 +73,7 @@ pub const KERNEL_PROCEDURES: [Word; 53] = [ word!("0xe0817bed99fb61180e705b2c9e5ca8c8f0c62864953247a56acbc65b7d58c2d5"), // input_note_get_script_root word!("0x527036257e58c3a84cf0aa170fb3f219a4553db17d269279355ad164a2b90ac5"), - // input_note_get_inputs_info + // input_note_get_storage_info word!("0xb7f45ec34f7708355551dcf1f82c9c40e2c19252f8d5c98dcf9ef1aa0a3eb878"), // input_note_get_serial_number word!("0x25815e02b7976d8e5c297dde60d372cc142c81f702f424ac0920190528c547ee"), diff --git a/crates/miden-protocol/src/transaction/tx_args.rs b/crates/miden-protocol/src/transaction/tx_args.rs index 598ce1b94b..5e808a3782 100644 --- a/crates/miden-protocol/src/transaction/tx_args.rs +++ b/crates/miden-protocol/src/transaction/tx_args.rs @@ -30,7 +30,7 @@ use crate::{EMPTY_WORD, MastForest, MastNodeId}; /// be used as a default value. If the [AdviceInputs] are propagated with some user defined map /// entries, this script arguments word could be used as a key to access the corresponding value. /// - Note arguments: data put onto the stack right before a note script is executed. These are -/// different from note inputs, as the user executing the transaction can specify arbitrary note +/// different from note storage, as the user executing the transaction can specify arbitrary note /// args. /// - Advice inputs: provides data needed by the runtime, like the details of public output notes. /// - Foreign account inputs: provides foreign account data that will be used during the foreign @@ -155,14 +155,14 @@ impl TransactionArgs { /// Populates the advice inputs with the expected recipient data for creating output notes. /// /// The advice inputs' map is extended with the following entries: - /// - RECIPIENT: [SERIAL_SCRIPT_HASH, INPUTS_COMMITMENT] + /// - RECIPIENT: [SERIAL_SCRIPT_HASH, STORAGE_COMMITMENT] /// - SERIAL_SCRIPT_HASH: [SERIAL_HASH, SCRIPT_ROOT] /// - SERIAL_HASH: [SERIAL_NUM, EMPTY_WORD] - /// - inputs_commitment |-> inputs. + /// - storage_commitment |-> storage_items. /// - script_root |-> script. pub fn add_output_note_recipient>(&mut self, note_recipient: T) { let note_recipient = note_recipient.as_ref(); - let inputs = note_recipient.inputs(); + let storage = note_recipient.storage(); let script = note_recipient.script(); let script_encoded: Vec = script.into(); @@ -173,11 +173,11 @@ impl TransactionArgs { let new_elements = vec![ (sn_hash, concat_words(note_recipient.serial_num(), Word::empty())), (sn_script_hash, concat_words(sn_hash, script.root())), - (note_recipient.digest(), concat_words(sn_script_hash, inputs.commitment())), - (inputs.commitment(), inputs.to_elements()), + (note_recipient.digest(), concat_words(sn_script_hash, storage.commitment())), + (storage.commitment(), storage.to_elements()), ( - Hasher::hash_elements(inputs.commitment().as_elements()), - vec![Felt::from(inputs.num_values())], + Hasher::hash_elements(storage.commitment().as_elements()), + vec![Felt::from(storage.num_items())], ), (script.root(), script_encoded), ]; @@ -207,7 +207,7 @@ impl TransactionArgs { /// The advice inputs' map is extended with the following keys: /// /// - recipient |-> recipient details (inputs_hash, script_root, serial_num). - /// - inputs_commitment |-> inputs. + /// - storage_commitment |-> storage_items. /// - script_root |-> script. pub fn extend_output_note_recipients(&mut self, notes: L) where @@ -324,6 +324,24 @@ impl TransactionScript { pub fn root(&self) -> Word { self.mast[self.entrypoint].digest() } + + /// Returns a new [TransactionScript] with the provided advice map entries merged into the + /// underlying [MastForest]. + /// + /// This allows adding advice map entries to an already-compiled transaction script, + /// which is useful when the entries are determined after script compilation. + pub fn with_advice_map(self, advice_map: AdviceMap) -> Self { + if advice_map.is_empty() { + return self; + } + + let mut mast = (*self.mast).clone(); + mast.advice_map_mut().extend(advice_map); + Self { + mast: Arc::new(mast), + entrypoint: self.entrypoint, + } + } } // SERIALIZATION @@ -360,4 +378,35 @@ mod tests { assert_eq!(tx_args, decoded); } + + #[test] + fn test_transaction_script_with_advice_map() { + use miden_core::{Felt, Word}; + + use super::TransactionScript; + use crate::assembly::Assembler; + + let assembler = Assembler::default(); + let program = assembler.assemble_program("begin nop end").unwrap(); + let script = TransactionScript::new(program); + + assert!(script.mast().advice_map().is_empty()); + + // Empty advice map should be a no-op + let original_root = script.root(); + let script = script.with_advice_map(AdviceMap::default()); + assert_eq!(original_root, script.root()); + + // Non-empty advice map should add entries + let key = Word::from([1u32, 2, 3, 4]); + let value = vec![Felt::new(42), Felt::new(43)]; + let mut advice_map = AdviceMap::default(); + advice_map.insert(key, value.clone()); + + let script = script.with_advice_map(advice_map); + + let mast = script.mast(); + let stored = mast.advice_map().get(&key).expect("entry should be present"); + assert_eq!(stored.as_ref(), value.as_slice()); + } } diff --git a/crates/miden-standards/Cargo.toml b/crates/miden-standards/Cargo.toml index a57fb2814e..38f82d8344 100644 --- a/crates/miden-standards/Cargo.toml +++ b/crates/miden-standards/Cargo.toml @@ -29,7 +29,7 @@ rand = { optional = true, workspace = true } thiserror = { workspace = true } [build-dependencies] -fs-err = { version = "3" } +fs-err = { workspace = true } miden-assembly = { workspace = true } miden-core = { workspace = true } miden-core-lib = { workspace = true } @@ -42,6 +42,7 @@ anyhow = "1.0" assert_matches = { workspace = true } miden-processor = { features = ["testing"], workspace = true } miden-protocol = { features = ["testing"], workspace = true } +tokio = { features = ["macros", "rt"], workspace = true } # When building as a dev-dependency (e.g., `cargo test --workspace` or `cargo check --all-targets`), # enable the `testing` feature. This is a workaround for Cargo's lack of test-specific features. diff --git a/crates/miden-standards/asm/standards/attachments/network_account_target.masm b/crates/miden-standards/asm/standards/attachments/network_account_target.masm new file mode 100644 index 0000000000..9c097162bc --- /dev/null +++ b/crates/miden-standards/asm/standards/attachments/network_account_target.masm @@ -0,0 +1,72 @@ +#! miden::standards::attachments::network_account_target +#! +#! Provides a standardized way to work with network account targets. + +use miden::protocol::active_note +use miden::protocol::note + +# CONSTANTS +# ================================================================================================ + +#! The attachment scheme for NetworkAccountTarget attachments. +#! This is a valid u32 that can be compared against an extracted attachment scheme. +pub const NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME = 1 + +#! The attachment kind for NetworkAccountTarget attachments (Word = 1). +#! This is a valid u32 that can be compared against an extracted attachment kind. +pub const NETWORK_ACCOUNT_TARGET_ATTACHMENT_KIND = 1 + +# ERRORS +# ================================================================================================ +const ERR_ATTACHMENT_SCHEME_MISMATCH = "expected network account target attachment scheme" +const ERR_ATTACHMENT_KIND_MISMATCH = "expected attachment kind to be Word for network account target" + +#! Returns the account ID encoded in the attachment. +#! +#! The attachment is expected to have the following layout: +#! [0, exec_hint_tag, account_id_prefix, account_id_suffix] +#! +#! WARNING: This procedure does not validate that the returned account ID is well-formed. +#! The caller should validate the account ID if needed using `account_id::validate`. +#! +#! Inputs: [attachment_scheme, attachment_kind, NOTE_ATTACHMENT] +#! Outputs: [account_id_prefix, account_id_suffix] +#! +#! Where: +#! - account_id_{prefix,suffix} are the prefix and suffix felts of an account ID. +#! +#! Panics if: +#! - the attachment scheme does not match NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME. +#! +#! Invocation: exec +pub proc get_id + # verify that the attachment scheme and kind are correct + # => [attachment_scheme, attachment_kind, NOTE_ATTACHMENT] + eq.NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME assert.err=ERR_ATTACHMENT_SCHEME_MISMATCH + eq.NETWORK_ACCOUNT_TARGET_ATTACHMENT_KIND assert.err=ERR_ATTACHMENT_KIND_MISMATCH + # => [NOTE_ATTACHMENT] = [0, exec_hint_tag, account_id_prefix, account_id_suffix] + + drop drop + # => [account_id_prefix, account_id_suffix] +end + +#! Creates a new attachment of type NetworkAccountTarget with the following layout: +#! [0, exec_hint_tag, account_id_prefix, account_id_suffix] +#! +#! Inputs: [account_id_prefix, account_id_suffix, exec_hint] +#! Outputs: [attachment_scheme, attachment_kind, NOTE_ATTACHMENT] +#! +#! Where: +#! - account_id_{prefix,suffix} are the prefix and suffix felts of an account ID. +#! - exec_hint is the execution hint for the note. +#! - attachment_kind is the attachment kind (Word = 1) for use with `output_note::set_attachment`. +#! - attachment_scheme is the attachment scheme (1) for use with `output_note::set_attachment`. +#! +#! Invocation: exec +pub proc new + movup.2 + push.0 + push.NETWORK_ACCOUNT_TARGET_ATTACHMENT_KIND + push.NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME + # => [attachment_scheme, attachment_kind, ATTACHMENT] +end diff --git a/crates/miden-standards/asm/standards/data_structures/array.masm b/crates/miden-standards/asm/standards/data_structures/array.masm new file mode 100644 index 0000000000..e5e5123d6b --- /dev/null +++ b/crates/miden-standards/asm/standards/data_structures/array.masm @@ -0,0 +1,78 @@ +# The MASM code for the Array abstraction. +# +# It provides an abstraction layer over a storage map, treating it as an array, +# with "set" and "get" for storing and retrieving words by (slot_id, index). +# The array can store up to 2^64 - 2^32 + 1 elements (indices 0 to 2^64 - 2^32). +# +# Using this Array utility requires that the underlying storage map is already created and +# initialized as part of an account component, under the given slot ID. + +use miden::protocol::active_account +use miden::protocol::native_account + +type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt } + +# PROCEDURES +# ================================================================================================= + +#! Sets a word in the array at the specified index. +#! +#! Inputs: [slot_id_prefix, slot_id_suffix, index, VALUE, pad(9)] +#! Outputs: [OLD_VALUE, pad(12)] +#! +#! Where: +#! - slot_id_{prefix, suffix} are the prefix and suffix felts of the slot identifier. +#! - index is the index at which to store the value (0 to 2^64 - 2^32). +#! - VALUE is the word to store at the specified index. +#! +#! Invocation: call +pub proc set(slot_id_prefix: felt, slot_id_suffix: felt, index: felt, value: BeWord) -> BeWord + # Build KEY = [index, 0, 0, 0] + push.0.0.0 movup.5 + # => [index, 0, 0, 0, slot_id_prefix, slot_id_suffix, VALUE, pad(9)] + + # truncate the stack + movup.10 drop + movup.10 drop + movup.10 drop + # => [index, 0, 0, 0, slot_id_prefix, slot_id_suffix, VALUE, pad(6)] + + movup.5 movup.5 + # => [slot_id_prefix, slot_id_suffix, KEY, VALUE, pad(6)] + + exec.native_account::set_map_item + # => [OLD_VALUE, pad(12)] +end + +#! Gets a word from the array at the specified index. +#! +#! Inputs: [slot_id_prefix, slot_id_suffix, index, pad(13)] +#! Outputs: [VALUE, pad(12)] +#! +#! Where: +#! - slot_id_{prefix, suffix} are the prefix and suffix felts of the slot identifier. +#! - index is the index of the element to retrieve (0 to 2^64 - 2^32). +#! - VALUE is the word stored at the specified index (zero if not set). +#! +#! Invocation: call +pub proc get(slot_id_prefix: felt, slot_id_suffix: felt, index: felt) -> BeWord + # Build KEY = [index, 0, 0, 0] + push.0.0.0 movup.5 + # => [index, 0, 0, 0, slot_id_prefix, slot_id_suffix, pad(13)] + + # truncate the stack + movup.6 drop + movup.6 drop + movup.6 drop + # => [index, 0, 0, 0, slot_id_prefix, slot_id_suffix, pad(10)] + + movup.5 movup.5 + # => [slot_id_prefix, slot_id_suffix, KEY, pad(10)] + + exec.active_account::get_map_item + # => [VALUE, pad(15)] + + # truncate the stack + repeat.3 movup.4 drop end + # => [VALUE, pad(12)] +end diff --git a/crates/miden-standards/asm/standards/faucets/basic_fungible.masm b/crates/miden-standards/asm/standards/faucets/basic_fungible.masm index 11fbb4353b..bce07e087c 100644 --- a/crates/miden-standards/asm/standards/faucets/basic_fungible.masm +++ b/crates/miden-standards/asm/standards/faucets/basic_fungible.masm @@ -36,7 +36,7 @@ const ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS="burn requires exactly 1 no #! - note_type is the type of the note that holds the asset. #! - execution_hint is the execution hint of the note that holds the asset. #! - RECIPIENT is the recipient of the asset, i.e., -#! hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment). +#! hash(hash(hash(serial_num, [0; 4]), script_root), storage_commitment). #! - note_idx is the index of the created note. #! #! Panics if: diff --git a/crates/miden-standards/asm/standards/faucets/mod.masm b/crates/miden-standards/asm/standards/faucets/mod.masm index 68a7d0652e..6315389430 100644 --- a/crates/miden-standards/asm/standards/faucets/mod.masm +++ b/crates/miden-standards/asm/standards/faucets/mod.masm @@ -30,7 +30,7 @@ const METADATA_SLOT=word("miden::standards::fungible_faucets::metadata") #! - tag is the tag to be included in the note. #! - note_type is the type of the note that holds the asset. #! - RECIPIENT is the recipient of the asset, i.e., -#! hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment). +#! hash(hash(hash(serial_num, [0; 4]), script_root), storage_commitment). #! - note_idx is the index of the created note. #! #! Panics if: diff --git a/crates/miden-standards/asm/standards/notes/mint.masm b/crates/miden-standards/asm/standards/notes/mint.masm index 7934306e33..3e74a4c708 100644 --- a/crates/miden-standards/asm/standards/notes/mint.masm +++ b/crates/miden-standards/asm/standards/notes/mint.masm @@ -6,23 +6,23 @@ use miden::standards::faucets::network_fungible->network_faucet # CONSTANTS # ================================================================================================= -const MINT_NOTE_NUM_INPUTS_PRIVATE=12 -const MINT_NOTE_MIN_NUM_INPUTS_PUBLIC=16 +const MINT_NOTE_NUM_STORAGE_ITEMS_PRIVATE=12 +const MINT_NOTE_MIN_NUM_STORAGE_ITEMS_PUBLIC=16 const OUTPUT_NOTE_TYPE_PUBLIC=1 const OUTPUT_NOTE_TYPE_PRIVATE=2 -# Memory Addresses of MINT note inputs -# The attachment is at the same memory address for both private and public inputs. +# Memory Addresses of MINT note storage +# The attachment is at the same memory address for both private and public storage. const ATTACHMENT_KIND_ADDRESS=2 const ATTACHMENT_SCHEME_ADDRESS=3 const ATTACHMENT_ADDRESS=4 -const OUTPUT_PUBLIC_NOTE_INPUTS_ADDR=16 +const OUTPUT_PUBLIC_NOTE_STORAGE_ADDR=16 # ERRORS # ================================================================================================= -const ERR_MINT_WRONG_NUMBER_OF_INPUTS="MINT script expects exactly 12 inputs for private or 16+ inputs for public output notes" +const ERR_MINT_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS="MINT script expects exactly 12 storage items for private or 16+ storage items for public output notes" #! Network Faucet MINT script: mints assets by calling the network faucet's distribute function. #! This note is intended to be executed against a network fungible faucet account. @@ -33,10 +33,10 @@ const ERR_MINT_WRONG_NUMBER_OF_INPUTS="MINT script expects exactly 12 inputs for #! Inputs: [ARGS, pad(12)] #! Outputs: [pad(16)] #! -#! Note inputs support two modes. Depending on the number of note inputs, +#! Note storage supports two modes. Depending on the number of note storage items, #! a private or public note is created on consumption of the MINT note: #! -#! Private mode (12 inputs) - creates a private note: +#! Private mode (12 storage items) - creates a private note: #! - tag: Note tag for the output note #! - amount: The amount to mint #! - attachment_scheme: The user-defined type of the attachment. @@ -44,7 +44,7 @@ const ERR_MINT_WRONG_NUMBER_OF_INPUTS="MINT script expects exactly 12 inputs for #! - ATTACHMENT: The attachment to be set. #! - RECIPIENT: The recipient digest (4 elements) #! -#! Public mode (16+ inputs) - creates a public note with variable-length inputs: +#! Public mode (16+ storage items) - creates a public note with variable-length storage: #! - tag: Note tag for the output note #! - amount: The amount to mint #! - attachment_scheme: The user-defined type of the attachment. @@ -52,45 +52,45 @@ const ERR_MINT_WRONG_NUMBER_OF_INPUTS="MINT script expects exactly 12 inputs for #! - ATTACHMENT: The attachment to be set. #! - SCRIPT_ROOT: Script root of the output note (4 elements) #! - SERIAL_NUM: Serial number of the output note (4 elements) -#! - [INPUTS]: Variable-length inputs for the output note (Vec) -#! The number of output note inputs = num_mint_note_inputs - 16 +#! - [STORAGE]: Variable-length storage for the output note (Vec) +#! The number of output note storage items = num_mint_note_storage_items - 16 #! #! Panics if: #! - account does not expose distribute procedure. -#! - the number of inputs is not exactly 12 for private or less than 16 for public output notes. +#! - the number of storage items is not exactly 12 for private or less than 16 for public output notes. pub proc main dropw # => [pad(16)] - # Load note inputs into memory starting at address 0 - push.0 exec.active_note::get_inputs - # => [total_inputs, inputs_ptr, pad(16)] + # Load note storage into memory starting at address 0 + push.0 exec.active_note::get_storage + # => [num_storage_items, storage_ptr, pad(16)] dup - # => [num_inputs, num_inputs, inputs_ptr, pad(16)] + # => [num_storage_items, num_storage_items, storage_ptr, pad(16)] - u32assert2.err=ERR_MINT_WRONG_NUMBER_OF_INPUTS - u32gte.MINT_NOTE_MIN_NUM_INPUTS_PUBLIC - # => [is_public_output_note, total_inputs, inputs_ptr, pad(16)] + u32assert2.err=ERR_MINT_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS + u32gte.MINT_NOTE_MIN_NUM_STORAGE_ITEMS_PUBLIC + # => [is_public_output_note, num_storage_items, storage_ptr, pad(16)] if.true # public output note creation - # => [total_inputs, inputs_ptr, pad(16)] + # => [num_storage_items, storage_ptr, pad(16)] movdn.9 drop - # => [EMPTY_WORD, EMPTY_WORD, total_inputs, pad(8)] + # => [EMPTY_WORD, EMPTY_WORD, num_storage_items, pad(8)] mem_loadw_be.8 - # => [SCRIPT_ROOT, EMPTY_WORD, total_inputs, pad(8)] + # => [SCRIPT_ROOT, EMPTY_WORD, num_storage_items, pad(8)] swapw mem_loadw_be.12 - # => [SERIAL_NUM, SCRIPT_ROOT, total_inputs, pad(8)] + # => [SERIAL_NUM, SCRIPT_ROOT, num_storage_items, pad(8)] - # compute variable length note inputs for the output note - movup.8 sub.MINT_NOTE_MIN_NUM_INPUTS_PUBLIC - # => [num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT, pad(8)] + # compute variable length note storage for the output note + movup.8 sub.MINT_NOTE_MIN_NUM_STORAGE_ITEMS_PUBLIC + # => [num_output_note_storage, SERIAL_NUM, SCRIPT_ROOT, pad(8)] - push.OUTPUT_PUBLIC_NOTE_INPUTS_ADDR - # => [inputs_ptr, num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT, pad(8)] + push.OUTPUT_PUBLIC_NOTE_STORAGE_ADDR + # => [storage_ptr, num_output_note_storage, SERIAL_NUM, SCRIPT_ROOT, pad(8)] exec.note::build_recipient # => [RECIPIENT, pad(12)] @@ -102,8 +102,8 @@ pub proc main else # private output note creation - eq.MINT_NOTE_NUM_INPUTS_PRIVATE assert.err=ERR_MINT_WRONG_NUMBER_OF_INPUTS drop - # => [inputs_ptr, pad(16)] + eq.MINT_NOTE_NUM_STORAGE_ITEMS_PRIVATE assert.err=ERR_MINT_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS drop + # => [storage_ptr, pad(16)] drop # => [pad(16)] diff --git a/crates/miden-standards/asm/standards/notes/p2id.masm b/crates/miden-standards/asm/standards/notes/p2id.masm index b315ad4e5a..51a50d947e 100644 --- a/crates/miden-standards/asm/standards/notes/p2id.masm +++ b/crates/miden-standards/asm/standards/notes/p2id.masm @@ -6,12 +6,12 @@ use miden::standards::wallets::basic->basic_wallet # ERRORS # ================================================================================================= -const ERR_P2ID_WRONG_NUMBER_OF_INPUTS="P2ID note expects exactly 2 note inputs" +const ERR_P2ID_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS="P2ID note expects exactly 2 note storage items" const ERR_P2ID_TARGET_ACCT_MISMATCH="P2ID's target account address and transaction address do not match" #! Pay-to-ID script: adds all assets from the note to the account, assuming ID of the account -#! matches target account ID specified by the note inputs. +#! matches target account ID specified by the note storage. #! #! Requires that the account exposes: #! - miden::standards::wallets::basic::receive_asset procedure. @@ -19,25 +19,25 @@ const ERR_P2ID_TARGET_ACCT_MISMATCH="P2ID's target account address and transacti #! Inputs: [] #! Outputs: [] #! -#! Note inputs are assumed to be as follows: +#! Note storage is assumed to be as follows: #! - target_account_id is the ID of the account for which the note is intended. #! #! Panics if: #! - Account does not expose miden::standards::wallets::basic::receive_asset procedure. -#! - Account ID of executing account is not equal to the Account ID specified via note inputs. +#! - Account ID of executing account is not equal to the Account ID specified via note storage. #! - The same non-fungible asset already exists in the account. #! - Adding a fungible asset would result in amount overflow, i.e., the total amount would be #! greater than 2^63. pub proc main - # store the note inputs to memory starting at address 0 - padw push.0 exec.active_note::get_inputs - # => [num_inputs, inputs_ptr, EMPTY_WORD] + # store the note storage to memory starting at address 0 + padw push.0 exec.active_note::get_storage + # => [num_storage_items, storage_ptr, EMPTY_WORD] - # make sure the number of inputs is 2 - eq.2 assert.err=ERR_P2ID_WRONG_NUMBER_OF_INPUTS - # => [inputs_ptr, EMPTY_WORD] + # make sure the number of storage items is 2 + eq.2 assert.err=ERR_P2ID_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS + # => [storage_ptr, EMPTY_WORD] - # read the target account ID from the note inputs + # read the target account ID from the note storage mem_loadw_be drop drop # => [target_account_id_prefix, target_account_id_suffix] diff --git a/crates/miden-standards/asm/standards/notes/p2ide.masm b/crates/miden-standards/asm/standards/notes/p2ide.masm index 1345bc4acb..748474b829 100644 --- a/crates/miden-standards/asm/standards/notes/p2ide.masm +++ b/crates/miden-standards/asm/standards/notes/p2ide.masm @@ -7,7 +7,7 @@ use miden::standards::wallets::basic->basic_wallet # ERRORS # ================================================================================================= -const ERR_P2IDE_WRONG_NUMBER_OF_INPUTS="P2IDE note expects exactly 4 note inputs" +const ERR_P2IDE_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS="P2IDE note expects exactly 4 note storage items" const ERR_P2IDE_RECLAIM_ACCT_IS_NOT_SENDER="failed to reclaim P2IDE note because the reclaiming account is not the sender" @@ -84,7 +84,7 @@ end #! Inputs: [] #! Outputs: [] #! -#! Note inputs are assumed to be as follows: +#! Note storage is assumed to be as follows: #! - target_account_id is the ID of the account for which the note is intended. #! - reclaim_block_height is the block height at which the note can be reclaimed by the sender. #! - timelock_block_height is the block height at which the note can be consumed by the target. @@ -101,15 +101,15 @@ end #! - Adding a fungible asset would result in an amount overflow, i.e., the total amount would be #! greater than 2^63. pub proc main - # store the note inputs to memory starting at address 0 - push.0 exec.active_note::get_inputs - # => [num_inputs, inputs_ptr] + # store the note storage to memory starting at address 0 + push.0 exec.active_note::get_storage + # => [num_storage_items, storage_ptr] - # make sure the number of inputs is 4 - eq.4 assert.err=ERR_P2IDE_WRONG_NUMBER_OF_INPUTS - # => [inputs_ptr] + # make sure the number of storage items is 4 + eq.4 assert.err=ERR_P2IDE_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS + # => [storage_ptr] - # read the reclaim block height, timelock_block_height, and target account ID from the note inputs + # read the reclaim block height, timelock_block_height, and target account ID from the note storage mem_loadw_be # => [timelock_block_height, reclaim_block_height, target_account_id_prefix, target_account_id_suffix] diff --git a/crates/miden-standards/asm/standards/notes/swap.masm b/crates/miden-standards/asm/standards/notes/swap.masm index 8c95668cd1..4b27caf7bf 100644 --- a/crates/miden-standards/asm/standards/notes/swap.masm +++ b/crates/miden-standards/asm/standards/notes/swap.masm @@ -5,7 +5,7 @@ use miden::standards::wallets::basic->wallet # CONSTANTS # ================================================================================================= -const SWAP_NOTE_INPUTS_NUMBER=16 +const SWAP_NOTE_NUM_STORAGE_ITEMS=16 const PAYBACK_NOTE_TYPE_ADDRESS=0 const PAYBACK_NOTE_TAG_ADDRESS=1 @@ -18,7 +18,7 @@ const PAYBACK_RECIPIENT_ADDRESS=12 # ERRORS # ================================================================================================= -const ERR_SWAP_WRONG_NUMBER_OF_INPUTS="SWAP script expects exactly 16 note inputs" +const ERR_SWAP_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS="SWAP script expects exactly 16 note storage items" const ERR_SWAP_WRONG_NUMBER_OF_ASSETS="SWAP script requires exactly 1 note asset" @@ -32,7 +32,7 @@ const ERR_SWAP_WRONG_NUMBER_OF_ASSETS="SWAP script requires exactly 1 note asset #! Inputs: [ARGS] #! Outputs: [] #! -#! Note inputs are assumed to be as follows: +#! Note storage is assumed to be as follows: #! - payback_note_type #! - payback_note_tag #! - attachment_kind @@ -56,12 +56,12 @@ pub proc main # --- create a payback note with the requested asset ---------------- - # store note inputs into memory starting at address 0 - push.0 exec.active_note::get_inputs - # => [num_inputs, inputs_ptr] + # store note storage into memory starting at address 0 + push.0 exec.active_note::get_storage + # => [num_storage_items, storage_ptr] - # check number of inputs - eq.SWAP_NOTE_INPUTS_NUMBER assert.err=ERR_SWAP_WRONG_NUMBER_OF_INPUTS + # check number of storage items + eq.SWAP_NOTE_NUM_STORAGE_ITEMS assert.err=ERR_SWAP_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS drop # => [] diff --git a/crates/miden-standards/src/account/auth/ecdsa_k256_keccak_acl.rs b/crates/miden-standards/src/account/auth/ecdsa_k256_keccak_acl.rs index 276a85f1bf..668857eebd 100644 --- a/crates/miden-standards/src/account/auth/ecdsa_k256_keccak_acl.rs +++ b/crates/miden-standards/src/account/auth/ecdsa_k256_keccak_acl.rs @@ -225,7 +225,7 @@ mod tests { use miden_protocol::account::AccountBuilder; use super::*; - use crate::account::components::WellKnownComponent; + use crate::account::components::StandardAccountComponent; use crate::account::wallets::BasicWallet; /// Test configuration for parametrized ACL tests @@ -243,7 +243,8 @@ mod tests { /// Helper function to get the basic wallet procedures for testing fn get_basic_wallet_procedures() -> Vec { // Get the two trigger procedures from BasicWallet: `receive_asset`, `move_asset_to_note`. - let procedures: Vec = WellKnownComponent::BasicWallet.procedure_digests().collect(); + let procedures: Vec = + StandardAccountComponent::BasicWallet.procedure_digests().collect(); assert_eq!(procedures.len(), 2); procedures diff --git a/crates/miden-standards/src/account/auth/falcon_512_rpo_acl.rs b/crates/miden-standards/src/account/auth/falcon_512_rpo_acl.rs index aee82e064d..a00ff4ed2b 100644 --- a/crates/miden-standards/src/account/auth/falcon_512_rpo_acl.rs +++ b/crates/miden-standards/src/account/auth/falcon_512_rpo_acl.rs @@ -226,7 +226,7 @@ mod tests { use miden_protocol::account::AccountBuilder; use super::*; - use crate::account::components::WellKnownComponent; + use crate::account::components::StandardAccountComponent; use crate::account::wallets::BasicWallet; /// Test configuration for parametrized ACL tests @@ -244,7 +244,8 @@ mod tests { /// Helper function to get the basic wallet procedures for testing fn get_basic_wallet_procedures() -> Vec { // Get the two trigger procedures from BasicWallet: `receive_asset`, `move_asset_to_note`. - let procedures: Vec = WellKnownComponent::BasicWallet.procedure_digests().collect(); + let procedures: Vec = + StandardAccountComponent::BasicWallet.procedure_digests().collect(); assert_eq!(procedures.len(), 2); procedures diff --git a/crates/miden-standards/src/account/components/mod.rs b/crates/miden-standards/src/account/components/mod.rs index 3b0f7e2f3f..7a7df0a0d3 100644 --- a/crates/miden-standards/src/account/components/mod.rs +++ b/crates/miden-standards/src/account/components/mod.rs @@ -175,11 +175,12 @@ pub fn falcon_512_rpo_multisig_library() -> Library { FALCON_512_RPO_MULTISIG_LIBRARY.clone() } -// WELL KNOWN COMPONENTS +// STANDARD ACCOUNT COMPONENTS // ================================================================================================ -/// The enum holding the types of basic well-known account components provided by the `miden-lib`. -pub enum WellKnownComponent { +/// The enum holding the types of standard account components defined in the `miden-standards` +/// crate. +pub enum StandardAccountComponent { BasicWallet, BasicFungibleFaucet, NetworkFungibleFaucet, @@ -192,7 +193,7 @@ pub enum WellKnownComponent { AuthNoAuth, } -impl WellKnownComponent { +impl StandardAccountComponent { /// Returns the iterator over digests of all procedures exported from the component. pub fn procedure_digests(&self) -> impl Iterator { let library = match self { @@ -271,9 +272,9 @@ impl WellKnownComponent { } } - /// Gets all well known components which could be constructed from the provided procedures map + /// Gets all standard components which could be constructed from the provided procedures map /// and pushes them to the `component_interface_vec`. - pub fn extract_well_known_components( + pub fn extract_standard_components( procedures_set: &mut BTreeSet, component_interface_vec: &mut Vec, ) { diff --git a/crates/miden-standards/src/account/interface/extension.rs b/crates/miden-standards/src/account/interface/extension.rs index e563408c33..1d3118cc7d 100644 --- a/crates/miden-standards/src/account/interface/extension.rs +++ b/crates/miden-standards/src/account/interface/extension.rs @@ -10,7 +10,7 @@ use miden_protocol::note::{Note, NoteScript}; use crate::AuthScheme; use crate::account::components::{ - WellKnownComponent, + StandardAccountComponent, basic_fungible_faucet_library, basic_wallet_library, ecdsa_k256_keccak_acl_library, @@ -27,7 +27,7 @@ use crate::account::interface::{ AccountInterface, NoteAccountCompatibility, }; -use crate::note::WellKnownNote; +use crate::note::StandardNote; // ACCOUNT INTERFACE EXTENSION TRAIT // ================================================================================================ @@ -75,8 +75,8 @@ impl AccountInterfaceExt for AccountInterface { /// Returns [NoteAccountCompatibility::Maybe] if the provided note is compatible with the /// current [AccountInterface], and [NoteAccountCompatibility::No] otherwise. fn is_compatible_with(&self, note: &Note) -> NoteAccountCompatibility { - if let Some(well_known_note) = WellKnownNote::from_note(note) { - if well_known_note.is_compatible_with(self) { + if let Some(standard_note) = StandardNote::from_note(note) { + if standard_note.is_compatible_with(self) { NoteAccountCompatibility::Maybe } else { NoteAccountCompatibility::No @@ -158,12 +158,12 @@ impl AccountComponentInterfaceExt for AccountComponentInterface { let mut procedures = BTreeSet::from_iter(procedures.iter().copied()); - // Well known component interfaces + // Standard component interfaces // ---------------------------------------------------------------------------------------- - // Get all available well known components which could be constructed from the + // Get all available standard components which could be constructed from the // `procedures` map and push them to the `component_interface_vec` - WellKnownComponent::extract_well_known_components( + StandardAccountComponent::extract_standard_components( &mut procedures, &mut component_interface_vec, ); diff --git a/crates/miden-standards/src/account/interface/mod.rs b/crates/miden-standards/src/account/interface/mod.rs index cdc967759a..61203afa8e 100644 --- a/crates/miden-standards/src/account/interface/mod.rs +++ b/crates/miden-standards/src/account/interface/mod.rs @@ -2,7 +2,7 @@ use alloc::string::String; use alloc::vec::Vec; use miden_protocol::account::{AccountId, AccountIdPrefix, AccountType}; -use miden_protocol::note::PartialNote; +use miden_protocol::note::{NoteAttachmentContent, PartialNote}; use miden_protocol::transaction::TransactionScript; use thiserror::Error; @@ -161,7 +161,17 @@ impl AccountInterface { note_creation_source, ); - let tx_script = CodeBuilder::new() + // Add attachment array entries to the code builder's advice map. + // For NoteAttachmentContent::Array, the commitment (to_word) is used as key + // and the array elements as value. + let mut code_builder = CodeBuilder::new(); + for note in output_notes { + if let NoteAttachmentContent::Array(array) = note.metadata().attachment().content() { + code_builder.add_advice_map_entry(array.commitment(), array.as_slice().to_vec()); + } + } + + let tx_script = code_builder .compile_tx_script(script) .map_err(AccountInterfaceError::InvalidTransactionScript)?; diff --git a/crates/miden-standards/src/account/interface/test.rs b/crates/miden-standards/src/account/interface/test.rs index e6639b32f0..f8492dc47e 100644 --- a/crates/miden-standards/src/account/interface/test.rs +++ b/crates/miden-standards/src/account/interface/test.rs @@ -8,9 +8,9 @@ use miden_protocol::note::{ Note, NoteAssets, NoteAttachment, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, }; @@ -279,7 +279,7 @@ fn test_basic_wallet_custom_notes() { end "; let note_script = CodeBuilder::default().compile_note_script(compatible_source_code).unwrap(); - let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); + let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let compatible_custom_note = Note::new(vault.clone(), metadata.clone(), recipient); assert_eq!( NoteAccountCompatibility::Maybe, @@ -307,7 +307,7 @@ fn test_basic_wallet_custom_notes() { end "; let note_script = CodeBuilder::default().compile_note_script(incompatible_source_code).unwrap(); - let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); + let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let incompatible_custom_note = Note::new(vault, metadata, recipient); assert_eq!( NoteAccountCompatibility::No, @@ -360,7 +360,7 @@ fn test_basic_fungible_faucet_custom_notes() { end "; let note_script = CodeBuilder::default().compile_note_script(compatible_source_code).unwrap(); - let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); + let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let compatible_custom_note = Note::new(vault.clone(), metadata.clone(), recipient); assert_eq!( NoteAccountCompatibility::Maybe, @@ -390,7 +390,7 @@ fn test_basic_fungible_faucet_custom_notes() { end "; let note_script = CodeBuilder::default().compile_note_script(incompatible_source_code).unwrap(); - let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); + let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let incompatible_custom_note = Note::new(vault, metadata, recipient); assert_eq!( NoteAccountCompatibility::No, @@ -466,7 +466,7 @@ fn test_custom_account_custom_notes() { .unwrap() .compile_note_script(compatible_source_code) .unwrap(); - let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); + let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let compatible_custom_note = Note::new(vault.clone(), metadata.clone(), recipient); assert_eq!( NoteAccountCompatibility::Maybe, @@ -493,7 +493,7 @@ fn test_custom_account_custom_notes() { .unwrap() .compile_note_script(incompatible_source_code) .unwrap(); - let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); + let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let incompatible_custom_note = Note::new(vault, metadata, recipient); assert_eq!( NoteAccountCompatibility::No, @@ -576,7 +576,7 @@ fn test_custom_account_multiple_components_custom_notes() { .unwrap() .compile_note_script(compatible_source_code) .unwrap(); - let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); + let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let compatible_custom_note = Note::new(vault.clone(), metadata.clone(), recipient); assert_eq!( NoteAccountCompatibility::Maybe, @@ -614,7 +614,7 @@ fn test_custom_account_multiple_components_custom_notes() { .unwrap() .compile_note_script(incompatible_source_code) .unwrap(); - let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); + let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let incompatible_custom_note = Note::new(vault.clone(), metadata, recipient); assert_eq!( NoteAccountCompatibility::No, diff --git a/crates/miden-standards/src/code_builder/mod.rs b/crates/miden-standards/src/code_builder/mod.rs index 7ea72c9dc6..dfcb4e8e45 100644 --- a/crates/miden-standards/src/code_builder/mod.rs +++ b/crates/miden-standards/src/code_builder/mod.rs @@ -1,4 +1,5 @@ use alloc::sync::Arc; +use alloc::vec::Vec; use miden_protocol::account::AccountComponentCode; use miden_protocol::assembly::{ @@ -12,6 +13,8 @@ use miden_protocol::assembly::{ }; use miden_protocol::note::NoteScript; use miden_protocol::transaction::{TransactionKernel, TransactionScript}; +use miden_protocol::vm::AdviceMap; +use miden_protocol::{Felt, Word}; use crate::errors::CodeBuilderError; use crate::standards_lib::StandardsLib; @@ -81,6 +84,7 @@ use crate::standards_lib::StandardsLib; pub struct CodeBuilder { assembler: Assembler, source_manager: Arc, + advice_map: AdviceMap, } impl CodeBuilder { @@ -100,7 +104,11 @@ impl CodeBuilder { let assembler = TransactionKernel::assembler_with_source_manager(source_manager.clone()) .with_dynamic_library(StandardsLib::default()) .expect("linking std lib should work"); - Self { assembler, source_manager } + Self { + assembler, + source_manager, + advice_map: AdviceMap::default(), + } } // LIBRARY MANAGEMENT @@ -228,6 +236,76 @@ impl CodeBuilder { Ok(self) } + // ADVICE MAP MANAGEMENT + // -------------------------------------------------------------------------------------------- + + /// Adds an entry to the advice map that will be included in compiled scripts. + /// + /// The advice map allows passing non-deterministic inputs to the VM that can be + /// accessed using `adv.push_mapval` instruction. + /// + /// # Arguments + /// * `key` - The key for the advice map entry (a Word) + /// * `value` - The values to associate with this key + pub fn add_advice_map_entry(&mut self, key: Word, value: impl Into>) { + self.advice_map.insert(key, value.into()); + } + + /// Builder-style method to add an advice map entry. + /// + /// # Arguments + /// * `key` - The key for the advice map entry (a Word) + /// * `value` - The values to associate with this key + pub fn with_advice_map_entry(mut self, key: Word, value: impl Into>) -> Self { + self.add_advice_map_entry(key, value); + self + } + + /// Extends the advice map with entries from another advice map. + /// + /// # Arguments + /// * `advice_map` - The advice map to merge into this builder's advice map + pub fn extend_advice_map(&mut self, advice_map: AdviceMap) { + self.advice_map.extend(advice_map); + } + + /// Builder-style method to extend the advice map. + /// + /// # Arguments + /// * `advice_map` - The advice map to merge into this builder's advice map + pub fn with_extended_advice_map(mut self, advice_map: AdviceMap) -> Self { + self.extend_advice_map(advice_map); + self + } + + // PRIVATE HELPERS + // -------------------------------------------------------------------------------------------- + + /// Applies the advice map to a program if it's non-empty. + /// + /// This avoids cloning the MAST forest when there are no advice map entries. + fn apply_advice_map( + advice_map: AdviceMap, + program: miden_protocol::vm::Program, + ) -> miden_protocol::vm::Program { + if advice_map.is_empty() { + program + } else { + program.with_advice_map(advice_map) + } + } + + /// Applies the advice map to a library if it's non-empty. + /// + /// This avoids cloning the MAST forest when there are no advice map entries. + fn apply_advice_map_to_library(advice_map: AdviceMap, library: Library) -> Library { + if advice_map.is_empty() { + library + } else { + library.with_advice_map(advice_map) + } + } + // COMPILATION // -------------------------------------------------------------------------------------------- @@ -246,7 +324,7 @@ impl CodeBuilder { component_path: impl AsRef, component_code: impl Parse, ) -> Result { - let CodeBuilder { assembler, source_manager } = self; + let CodeBuilder { assembler, source_manager, advice_map } = self; let mut parse_options = ParseOptions::for_library(); parse_options.path = Some(Path::new(component_path.as_ref()).into()); @@ -262,7 +340,9 @@ impl CodeBuilder { CodeBuilderError::build_error_with_report("failed to parse component code", err) })?; - Ok(AccountComponentCode::from(library)) + Ok(AccountComponentCode::from(Self::apply_advice_map_to_library( + advice_map, library, + ))) } /// Compiles the provided MASM code into a [`TransactionScript`]. @@ -279,12 +359,13 @@ impl CodeBuilder { self, tx_script: impl Parse, ) -> Result { - let assembler = self.assembler; + let CodeBuilder { assembler, advice_map, .. } = self; let program = assembler.assemble_program(tx_script).map_err(|err| { CodeBuilderError::build_error_with_report("failed to parse transaction script", err) })?; - Ok(TransactionScript::new(program)) + + Ok(TransactionScript::new(Self::apply_advice_map(advice_map, program))) } /// Compiles the provided MASM code into a [`NoteScript`]. @@ -297,13 +378,14 @@ impl CodeBuilder { /// # Errors /// Returns an error if: /// - The note script compiling fails - pub fn compile_note_script(self, program: impl Parse) -> Result { - let assembler = self.assembler; + pub fn compile_note_script(self, source: impl Parse) -> Result { + let CodeBuilder { assembler, advice_map, .. } = self; - let program = assembler.assemble_program(program).map_err(|err| { + let program = assembler.assemble_program(source).map_err(|err| { CodeBuilderError::build_error_with_report("failed to parse note script", err) })?; - Ok(NoteScript::new(program)) + + Ok(NoteScript::new(Self::apply_advice_map(advice_map, program))) } // ACCESSORS @@ -612,4 +694,82 @@ mod tests { Ok(()) } + + #[test] + fn test_code_builder_with_advice_map_entry() -> anyhow::Result<()> { + let key = Word::from([1u32, 2, 3, 4]); + let value = vec![Felt::new(42), Felt::new(43)]; + + let script = CodeBuilder::default() + .with_advice_map_entry(key, value.clone()) + .compile_tx_script("begin nop end") + .context("failed to compile tx script with advice map")?; + + let mast = script.mast(); + let stored_value = mast.advice_map().get(&key).expect("advice map entry should be present"); + assert_eq!(stored_value.as_ref(), value.as_slice()); + + Ok(()) + } + + #[test] + fn test_code_builder_extend_advice_map() -> anyhow::Result<()> { + let key1 = Word::from([1u32, 0, 0, 0]); + let key2 = Word::from([2u32, 0, 0, 0]); + + let mut advice_map = AdviceMap::default(); + advice_map.insert(key1, vec![Felt::new(1)]); + advice_map.insert(key2, vec![Felt::new(2)]); + + let script = CodeBuilder::default() + .with_extended_advice_map(advice_map) + .compile_tx_script("begin nop end") + .context("failed to compile tx script")?; + + let mast = script.mast(); + assert!(mast.advice_map().get(&key1).is_some(), "key1 should be present"); + assert!(mast.advice_map().get(&key2).is_some(), "key2 should be present"); + + Ok(()) + } + + #[test] + fn test_code_builder_advice_map_in_note_script() -> anyhow::Result<()> { + let key = Word::from([5u32, 6, 7, 8]); + let value = vec![Felt::new(100)]; + + let script = CodeBuilder::default() + .with_advice_map_entry(key, value.clone()) + .compile_note_script("begin nop end") + .context("failed to compile note script with advice map")?; + + let mast = script.mast(); + let stored_value = mast + .advice_map() + .get(&key) + .expect("advice map entry should be present in note script"); + assert_eq!(stored_value.as_ref(), value.as_slice()); + + Ok(()) + } + + #[test] + fn test_code_builder_advice_map_in_component_code() -> anyhow::Result<()> { + let key = Word::from([11u32, 22, 33, 44]); + let value = vec![Felt::new(500)]; + + let component_code = CodeBuilder::default() + .with_advice_map_entry(key, value.clone()) + .compile_component_code("test::component", "pub proc test nop end") + .context("failed to compile component code with advice map")?; + + let mast = component_code.mast_forest(); + let stored_value = mast + .advice_map() + .get(&key) + .expect("advice map entry should be present in component code"); + assert_eq!(stored_value.as_ref(), value.as_slice()); + + Ok(()) + } } diff --git a/crates/miden-standards/src/errors/standards.rs b/crates/miden-standards/src/errors/standards.rs index 007723901b..410f0358a5 100644 --- a/crates/miden-standards/src/errors/standards.rs +++ b/crates/miden-standards/src/errors/standards.rs @@ -9,6 +9,11 @@ use miden_protocol::errors::MasmError; // STANDARDS ERRORS // ================================================================================================ +/// Error Message: "expected attachment kind to be Word for network account target" +pub const ERR_ATTACHMENT_KIND_MISMATCH: MasmError = MasmError::from_static_str("expected attachment kind to be Word for network account target"); +/// Error Message: "expected network account target attachment scheme" +pub const ERR_ATTACHMENT_SCHEME_MISMATCH: MasmError = MasmError::from_static_str("expected network account target attachment scheme"); + /// Error Message: "burn requires exactly 1 note asset" pub const ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS: MasmError = MasmError::from_static_str("burn requires exactly 1 note asset"); @@ -18,8 +23,8 @@ pub const ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED: M /// Error Message: "number of approvers must be equal to or greater than threshold" pub const ERR_MALFORMED_MULTISIG_CONFIG: MasmError = MasmError::from_static_str("number of approvers must be equal to or greater than threshold"); -/// Error Message: "MINT script expects exactly 12 inputs for private or 16+ inputs for public output notes" -pub const ERR_MINT_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("MINT script expects exactly 12 inputs for private or 16+ inputs for public output notes"); +/// Error Message: "MINT script expects exactly 12 storage items for private or 16+ storage items for public output notes" +pub const ERR_MINT_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS: MasmError = MasmError::from_static_str("MINT script expects exactly 12 storage items for private or 16+ storage items for public output notes"); /// Error Message: "failed to reclaim P2IDE note because the reclaiming account is not the sender" pub const ERR_P2IDE_RECLAIM_ACCT_IS_NOT_SENDER: MasmError = MasmError::from_static_str("failed to reclaim P2IDE note because the reclaiming account is not the sender"); @@ -29,21 +34,21 @@ pub const ERR_P2IDE_RECLAIM_DISABLED: MasmError = MasmError::from_static_str("P2 pub const ERR_P2IDE_RECLAIM_HEIGHT_NOT_REACHED: MasmError = MasmError::from_static_str("failed to reclaim P2IDE note because the reclaim block height is not reached yet"); /// Error Message: "failed to consume P2IDE note because the note is still timelocked" pub const ERR_P2IDE_TIMELOCK_HEIGHT_NOT_REACHED: MasmError = MasmError::from_static_str("failed to consume P2IDE note because the note is still timelocked"); -/// Error Message: "P2IDE note expects exactly 4 note inputs" -pub const ERR_P2IDE_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("P2IDE note expects exactly 4 note inputs"); +/// Error Message: "P2IDE note expects exactly 4 note storage items" +pub const ERR_P2IDE_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS: MasmError = MasmError::from_static_str("P2IDE note expects exactly 4 note storage items"); /// Error Message: "P2ID's target account address and transaction address do not match" pub const ERR_P2ID_TARGET_ACCT_MISMATCH: MasmError = MasmError::from_static_str("P2ID's target account address and transaction address do not match"); -/// Error Message: "P2ID note expects exactly 2 note inputs" -pub const ERR_P2ID_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("P2ID note expects exactly 2 note inputs"); +/// Error Message: "P2ID note expects exactly 2 note storage items" +pub const ERR_P2ID_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS: MasmError = MasmError::from_static_str("P2ID note expects exactly 2 note storage items"); /// Error Message: "note sender is not the owner" pub const ERR_SENDER_NOT_OWNER: MasmError = MasmError::from_static_str("note sender is not the owner"); +/// Error Message: "SWAP script expects exactly 16 note storage items" +pub const ERR_SWAP_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS: MasmError = MasmError::from_static_str("SWAP script expects exactly 16 note storage items"); /// Error Message: "SWAP script requires exactly 1 note asset" pub const ERR_SWAP_WRONG_NUMBER_OF_ASSETS: MasmError = MasmError::from_static_str("SWAP script requires exactly 1 note asset"); -/// Error Message: "SWAP script expects exactly 16 note inputs" -pub const ERR_SWAP_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("SWAP script expects exactly 16 note inputs"); /// Error Message: "failed to approve multisig transaction as it was already executed" pub const ERR_TX_ALREADY_EXECUTED: MasmError = MasmError::from_static_str("failed to approve multisig transaction as it was already executed"); diff --git a/crates/miden-standards/src/note/mint_inputs.rs b/crates/miden-standards/src/note/mint_inputs.rs deleted file mode 100644 index 3fd62e3e67..0000000000 --- a/crates/miden-standards/src/note/mint_inputs.rs +++ /dev/null @@ -1,118 +0,0 @@ -use alloc::vec::Vec; - -use miden_protocol::errors::NoteError; -use miden_protocol::note::{NoteAttachment, NoteInputs, NoteRecipient}; -use miden_protocol::{Felt, MAX_INPUTS_PER_NOTE, Word}; - -/// Represents the different input formats for MINT notes. -/// - Private: Creates a private output note using a precomputed recipient digest (12 MINT note -/// inputs) -/// - Public: Creates a public output note by providing script root, serial number, and -/// variable-length inputs (16+ MINT note inputs: 16 fixed + variable number of output note -/// inputs) -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum MintNoteInputs { - Private { - recipient_digest: Word, - amount: Felt, - tag: Felt, - attachment: NoteAttachment, - }, - Public { - recipient: NoteRecipient, - amount: Felt, - tag: Felt, - attachment: NoteAttachment, - }, -} - -impl MintNoteInputs { - pub fn new_private(recipient_digest: Word, amount: Felt, tag: Felt) -> Self { - Self::Private { - recipient_digest, - amount, - tag, - attachment: NoteAttachment::default(), - } - } - - pub fn new_public( - recipient: NoteRecipient, - amount: Felt, - tag: Felt, - ) -> Result { - // Calculate total number of inputs that will be created: - // 16 fixed inputs (tag, amount, attachment_kind, attachment_scheme, ATTACHMENT, - // SCRIPT_ROOT, SERIAL_NUM) + variable recipient inputs length - const FIXED_PUBLIC_INPUTS: usize = 16; - let total_inputs = FIXED_PUBLIC_INPUTS + recipient.inputs().num_values() as usize; - - if total_inputs > MAX_INPUTS_PER_NOTE { - return Err(NoteError::TooManyInputs(total_inputs)); - } - - Ok(Self::Public { - recipient, - amount, - tag, - attachment: NoteAttachment::default(), - }) - } - - /// Overwrites the [`NoteAttachment`] of the note inputs. - pub fn with_attachment(self, attachment: NoteAttachment) -> Self { - match self { - MintNoteInputs::Private { - recipient_digest, - amount, - tag, - attachment: _, - } => MintNoteInputs::Private { - recipient_digest, - amount, - tag, - attachment, - }, - MintNoteInputs::Public { recipient, amount, tag, attachment: _ } => { - MintNoteInputs::Public { recipient, amount, tag, attachment } - }, - } - } -} - -impl From for NoteInputs { - fn from(mint_inputs: MintNoteInputs) -> Self { - match mint_inputs { - MintNoteInputs::Private { - recipient_digest, - amount, - tag, - attachment, - } => { - let attachment_scheme = Felt::from(attachment.attachment_scheme().as_u32()); - let attachment_kind = Felt::from(attachment.attachment_kind().as_u8()); - let attachment = attachment.content().to_word(); - - let mut input_values = Vec::with_capacity(12); - input_values.extend_from_slice(&[tag, amount, attachment_kind, attachment_scheme]); - input_values.extend_from_slice(attachment.as_elements()); - input_values.extend_from_slice(recipient_digest.as_elements()); - NoteInputs::new(input_values) - .expect("number of inputs should not exceed max inputs") - }, - MintNoteInputs::Public { recipient, amount, tag, attachment } => { - let attachment_scheme = Felt::from(attachment.attachment_scheme().as_u32()); - let attachment_kind = Felt::from(attachment.attachment_kind().as_u8()); - let attachment = attachment.content().to_word(); - - let mut input_values = vec![tag, amount, attachment_kind, attachment_scheme]; - input_values.extend_from_slice(attachment.as_elements()); - input_values.extend_from_slice(recipient.script().root().as_elements()); - input_values.extend_from_slice(recipient.serial_num().as_elements()); - input_values.extend_from_slice(recipient.inputs().values()); - NoteInputs::new(input_values) - .expect("number of inputs should not exceed max inputs") - }, - } - } -} diff --git a/crates/miden-standards/src/note/mint_storage.rs b/crates/miden-standards/src/note/mint_storage.rs new file mode 100644 index 0000000000..ce1c251fd8 --- /dev/null +++ b/crates/miden-standards/src/note/mint_storage.rs @@ -0,0 +1,124 @@ +use alloc::vec::Vec; + +use miden_protocol::errors::NoteError; +use miden_protocol::note::{NoteAttachment, NoteRecipient, NoteStorage}; +use miden_protocol::{Felt, MAX_NOTE_STORAGE_ITEMS, Word}; + +/// Represents the different storage formats for MINT notes. +/// - Private: Creates a private output note using a precomputed recipient digest (12 MINT note +/// storage items) +/// - Public: Creates a public output note by providing script root, serial number, and +/// variable-length storage (16+ MINT note storage items: 16 fixed + variable number of output +/// note storage items) +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MintNoteStorage { + Private { + recipient_digest: Word, + amount: Felt, + tag: Felt, + attachment: NoteAttachment, + }, + Public { + recipient: NoteRecipient, + amount: Felt, + tag: Felt, + attachment: NoteAttachment, + }, +} + +impl MintNoteStorage { + pub fn new_private(recipient_digest: Word, amount: Felt, tag: Felt) -> Self { + Self::Private { + recipient_digest, + amount, + tag, + attachment: NoteAttachment::default(), + } + } + + pub fn new_public( + recipient: NoteRecipient, + amount: Felt, + tag: Felt, + ) -> Result { + // Calculate total number of storage items that will be created: + // 16 fixed items (tag, amount, attachment_kind, attachment_scheme, ATTACHMENT, + // SCRIPT_ROOT, SERIAL_NUM) + variable recipient number of storage items + const FIXED_PUBLIC_STORAGE_ITEMS: usize = 16; + let total_storage_items = + FIXED_PUBLIC_STORAGE_ITEMS + recipient.storage().num_items() as usize; + + if total_storage_items > MAX_NOTE_STORAGE_ITEMS { + return Err(NoteError::TooManyStorageItems(total_storage_items)); + } + + Ok(Self::Public { + recipient, + amount, + tag, + attachment: NoteAttachment::default(), + }) + } + + /// Overwrites the [`NoteAttachment`] of the note storage. + pub fn with_attachment(self, attachment: NoteAttachment) -> Self { + match self { + MintNoteStorage::Private { + recipient_digest, + amount, + tag, + attachment: _, + } => MintNoteStorage::Private { + recipient_digest, + amount, + tag, + attachment, + }, + MintNoteStorage::Public { recipient, amount, tag, attachment: _ } => { + MintNoteStorage::Public { recipient, amount, tag, attachment } + }, + } + } +} + +impl From for NoteStorage { + fn from(mint_storage: MintNoteStorage) -> Self { + match mint_storage { + MintNoteStorage::Private { + recipient_digest, + amount, + tag, + attachment, + } => { + let attachment_scheme = Felt::from(attachment.attachment_scheme().as_u32()); + let attachment_kind = Felt::from(attachment.attachment_kind().as_u8()); + let attachment = attachment.content().to_word(); + + let mut storage_values = Vec::with_capacity(12); + storage_values.extend_from_slice(&[ + tag, + amount, + attachment_kind, + attachment_scheme, + ]); + storage_values.extend_from_slice(attachment.as_elements()); + storage_values.extend_from_slice(recipient_digest.as_elements()); + NoteStorage::new(storage_values) + .expect("number of storage items should not exceed max storage items") + }, + MintNoteStorage::Public { recipient, amount, tag, attachment } => { + let attachment_scheme = Felt::from(attachment.attachment_scheme().as_u32()); + let attachment_kind = Felt::from(attachment.attachment_kind().as_u8()); + let attachment = attachment.content().to_word(); + + let mut storage_values = vec![tag, amount, attachment_kind, attachment_scheme]; + storage_values.extend_from_slice(attachment.as_elements()); + storage_values.extend_from_slice(recipient.script().root().as_elements()); + storage_values.extend_from_slice(recipient.serial_num().as_elements()); + storage_values.extend_from_slice(recipient.storage().items()); + NoteStorage::new(storage_values) + .expect("number of storage items should not exceed max storage items") + }, + } + } +} diff --git a/crates/miden-standards/src/note/mod.rs b/crates/miden-standards/src/note/mod.rs index 30f0d8d172..bfce6f6088 100644 --- a/crates/miden-standards/src/note/mod.rs +++ b/crates/miden-standards/src/note/mod.rs @@ -10,27 +10,27 @@ use miden_protocol::note::{ NoteAssets, NoteAttachment, NoteDetails, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, }; use miden_protocol::{Felt, Word}; use utils::build_swap_tag; -pub mod mint_inputs; +pub mod mint_storage; pub mod utils; mod network_account_target; pub use network_account_target::{NetworkAccountTarget, NetworkAccountTargetError}; -mod well_known_note_attachment; -pub use well_known_note_attachment::WellKnownNoteAttachment; +mod standard_note_attachment; +pub use standard_note_attachment::StandardNoteAttachment; -mod well_known_note; -pub use mint_inputs::MintNoteInputs; -pub use well_known_note::{NoteConsumptionStatus, WellKnownNote}; +mod standard_note; +pub use mint_storage::MintNoteStorage; +pub use standard_note::{NoteConsumptionStatus, StandardNote}; // STANDARDIZED SCRIPTS // ================================================================================================ @@ -121,7 +121,7 @@ pub fn create_swap_note( return Err(NoteError::other("requested asset same as offered asset")); } - let note_script = WellKnownNote::SWAP.script(); + let note_script = StandardNote::SWAP.script(); let payback_serial_num = rng.draw_word(); let payback_recipient = utils::build_p2id_recipient(sender, payback_serial_num)?; @@ -143,7 +143,7 @@ pub fn create_swap_note( inputs.extend_from_slice(attachment.as_elements()); inputs.extend_from_slice(requested_asset_word.as_elements()); inputs.extend_from_slice(payback_recipient.digest().as_elements()); - let inputs = NoteInputs::new(inputs)?; + let inputs = NoteStorage::new(inputs)?; // build the tag for the SWAP use case let tag = build_swap_tag(swap_note_type, &offered_asset, &requested_asset); @@ -171,7 +171,7 @@ pub fn create_swap_note( /// checking if the note sender equals the faucet owner to authorize minting. /// /// MINT notes are always PUBLIC (for network execution). Output notes can be either PRIVATE -/// or PUBLIC depending on the MintNoteInputs variant used. +/// or PUBLIC depending on the MintNoteStorage variant used. /// /// The passed-in `rng` is used to generate a serial number for the note. The note's tag /// is automatically set to the faucet's account ID for proper routing. @@ -179,7 +179,7 @@ pub fn create_swap_note( /// # Parameters /// - `faucet_id`: The account ID of the network faucet that will mint the assets /// - `sender`: The account ID of the note creator (must be the faucet owner) -/// - `mint_inputs`: The input configuration specifying private or public output mode +/// - `mint_storage`: The storage configuration specifying private or public output mode /// - `attachment`: The [`NoteAttachment`] of the MINT note /// - `rng`: Random number generator for creating the serial number /// @@ -188,24 +188,24 @@ pub fn create_swap_note( pub fn create_mint_note( faucet_id: AccountId, sender: AccountId, - mint_inputs: MintNoteInputs, + mint_storage: MintNoteStorage, attachment: NoteAttachment, rng: &mut R, ) -> Result { - let note_script = WellKnownNote::MINT.script(); + let note_script = StandardNote::MINT.script(); let serial_num = rng.draw_word(); // MINT notes are always public for network execution let note_type = NoteType::Public; - // Convert MintNoteInputs to NoteInputs - let inputs = NoteInputs::from(mint_inputs); + // Convert MintNoteStorage to NoteStorage + let storage = NoteStorage::from(mint_storage); let tag = NoteTag::with_account_target(faucet_id); let metadata = NoteMetadata::new(sender, note_type, tag).with_attachment(attachment); let assets = NoteAssets::new(vec![])?; // MINT notes have no assets - let recipient = NoteRecipient::new(serial_num, note_script, inputs); + let recipient = NoteRecipient::new(serial_num, note_script, storage); Ok(Note::new(assets, metadata, recipient)) } @@ -238,13 +238,13 @@ pub fn create_burn_note( attachment: NoteAttachment, rng: &mut R, ) -> Result { - let note_script = WellKnownNote::BURN.script(); + let note_script = StandardNote::BURN.script(); let serial_num = rng.draw_word(); // BURN notes are always public let note_type = NoteType::Public; - let inputs = NoteInputs::new(vec![])?; + let inputs = NoteStorage::new(vec![])?; let tag = NoteTag::with_account_target(faucet_id); let metadata = NoteMetadata::new(sender, note_type, tag).with_attachment(attachment); diff --git a/crates/miden-standards/src/note/network_account_target.rs b/crates/miden-standards/src/note/network_account_target.rs index 2ea446bafb..5646eb5def 100644 --- a/crates/miden-standards/src/note/network_account_target.rs +++ b/crates/miden-standards/src/note/network_account_target.rs @@ -9,7 +9,7 @@ use miden_protocol::note::{ NoteExecutionHint, }; -use crate::note::WellKnownNoteAttachment; +use crate::note::StandardNoteAttachment; // NETWORK ACCOUNT TARGET // ================================================================================================ @@ -36,7 +36,7 @@ impl NetworkAccountTarget { /// The standardized scheme of [`NetworkAccountTarget`] attachments. pub const ATTACHMENT_SCHEME: NoteAttachmentScheme = - WellKnownNoteAttachment::NetworkAccountTarget.attachment_scheme(); + StandardNoteAttachment::NetworkAccountTarget.attachment_scheme(); // CONSTRUCTORS // -------------------------------------------------------------------------------------------- diff --git a/crates/miden-standards/src/note/well_known_note.rs b/crates/miden-standards/src/note/standard_note.rs similarity index 77% rename from crates/miden-standards/src/note/well_known_note.rs rename to crates/miden-standards/src/note/standard_note.rs index 98d44c59e2..4be9e37eb0 100644 --- a/crates/miden-standards/src/note/well_known_note.rs +++ b/crates/miden-standards/src/note/standard_note.rs @@ -14,7 +14,7 @@ use crate::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet}; use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt}; use crate::account::wallets::BasicWallet; -// WELL KNOWN NOTE SCRIPTS +// STANDARD NOTE SCRIPTS // ================================================================================================ // Initialize the P2ID note script only once @@ -102,11 +102,11 @@ fn burn_root() -> Word { BURN_SCRIPT.root() } -// WELL KNOWN NOTE +// STANDARD NOTE // ================================================================================================ -/// The enum holding the types of basic well-known notes provided by the `miden-lib`. -pub enum WellKnownNote { +/// The enum holding the types of standard notes provided by the `miden-lib`. +pub enum StandardNote { P2ID, P2IDE, SWAP, @@ -114,30 +114,30 @@ pub enum WellKnownNote { BURN, } -impl WellKnownNote { +impl StandardNote { // CONSTANTS // -------------------------------------------------------------------------------------------- - /// Expected number of inputs of the P2ID note. - const P2ID_NUM_INPUTS: usize = 2; + /// Expected number of storage items of the P2ID note. + const P2ID_NUM_STORAGE_ITEMS: usize = 2; - /// Expected number of inputs of the P2IDE note. - const P2IDE_NUM_INPUTS: usize = 4; + /// Expected number of storage items of the P2IDE note. + const P2IDE_NUM_STORAGE_ITEMS: usize = 4; - /// Expected number of inputs of the SWAP note. - const SWAP_NUM_INPUTS: usize = 16; + /// Expected number of storage items of the SWAP note. + const SWAP_NUM_STORAGE_ITEMS: usize = 16; - /// Expected number of inputs of the MINT note (private mode). - const MINT_NUM_INPUTS_PRIVATE: usize = 8; + /// Expected number of storage items of the MINT note (private mode). + const MINT_NUM_STORAGE_ITEMS_PRIVATE: usize = 8; - /// Expected number of inputs of the BURN note. - const BURN_NUM_INPUTS: usize = 0; + /// Expected number of storage items of the BURN note. + const BURN_NUM_STORAGE_ITEMS: usize = 0; // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - /// Returns a [WellKnownNote] instance based on the note script of the provided [Note]. Returns - /// `None` if the provided note is not a basic well-known note. + /// Returns a [StandardNote] instance based on the note script of the provided [Note]. Returns + /// `None` if the provided note is not a standard note. pub fn from_note(note: &Note) -> Option { let note_script_root = note.script().root(); @@ -163,18 +163,18 @@ impl WellKnownNote { // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns the expected inputs number of the active note. - pub fn num_expected_inputs(&self) -> usize { + /// Returns the expected number of storage items of the active note. + pub fn expected_num_storage_items(&self) -> usize { match self { - Self::P2ID => Self::P2ID_NUM_INPUTS, - Self::P2IDE => Self::P2IDE_NUM_INPUTS, - Self::SWAP => Self::SWAP_NUM_INPUTS, - Self::MINT => Self::MINT_NUM_INPUTS_PRIVATE, - Self::BURN => Self::BURN_NUM_INPUTS, + Self::P2ID => Self::P2ID_NUM_STORAGE_ITEMS, + Self::P2IDE => Self::P2IDE_NUM_STORAGE_ITEMS, + Self::SWAP => Self::SWAP_NUM_STORAGE_ITEMS, + Self::MINT => Self::MINT_NUM_STORAGE_ITEMS_PRIVATE, + Self::BURN => Self::BURN_NUM_STORAGE_ITEMS, } } - /// Returns the note script of the current [WellKnownNote] instance. + /// Returns the note script of the current [StandardNote] instance. pub fn script(&self) -> NoteScript { match self { Self::P2ID => p2id(), @@ -185,7 +185,7 @@ impl WellKnownNote { } } - /// Returns the script root of the current [WellKnownNote] instance. + /// Returns the script root of the current [StandardNote] instance. pub fn script_root(&self) -> Word { match self { Self::P2ID => p2id_root(), @@ -196,7 +196,7 @@ impl WellKnownNote { } } - /// Returns a boolean value indicating whether this [WellKnownNote] is compatible with the + /// Returns a boolean value indicating whether this [StandardNote] is compatible with the /// provided [AccountInterface]. pub fn is_compatible_with(&self, account_interface: &AccountInterface) -> bool { if account_interface.components().contains(&AccountComponentInterface::BasicWallet) { @@ -233,7 +233,7 @@ impl WellKnownNote { } } - /// Performs the inputs check of the provided well-known note against the target account and the + /// Performs the inputs check of the provided standard note against the target account and the /// block number. /// /// This function returns: @@ -261,11 +261,11 @@ impl WellKnownNote { /// /// It performs: /// - for `P2ID` note: - /// - check that note inputs have correct number of values. - /// - assertion that the account ID provided by the note inputs is equal to the target + /// - check that note storage has correct number of values. + /// - assertion that the account ID provided by the note storage is equal to the target /// account ID. /// - for `P2IDE` note: - /// - check that note inputs have correct number of values. + /// - check that note storage has correct number of values. /// - check that the target account is either the receiver account or the sender account. /// - check that depending on whether the target account is sender or receiver, it could be /// either consumed, or consumed after timelock height, or consumed after reclaim height. @@ -276,18 +276,18 @@ impl WellKnownNote { block_ref: BlockNumber, ) -> Result, StaticAnalysisError> { match self { - WellKnownNote::P2ID => { - let input_account_id = parse_p2id_inputs(note.inputs().values())?; + StandardNote::P2ID => { + let input_account_id = parse_p2id_storage(note.storage().items())?; if input_account_id == target_account_id { Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization)) } else { - Ok(Some(NoteConsumptionStatus::NeverConsumable("account ID provided to the P2ID note inputs doesn't match the target account ID".into()))) + Ok(Some(NoteConsumptionStatus::NeverConsumable("account ID provided to the P2ID note storage doesn't match the target account ID".into()))) } }, - WellKnownNote::P2IDE => { + StandardNote::P2IDE => { let (receiver_account_id, reclaim_height, timelock_height) = - parse_p2ide_inputs(note.inputs().values())?; + parse_p2ide_storage(note.storage().items())?; let current_block_height = block_ref.as_u32(); @@ -318,10 +318,10 @@ impl WellKnownNote { )))) } // if the target account is neither the sender nor the receiver (from the note's - // inputs), then this account cannot consume the note + // storage), then this account cannot consume the note } else { Ok(Some(NoteConsumptionStatus::NeverConsumable( - "target account of the transaction does not match neither the receiver account specified by the P2IDE inputs, nor the sender account".into() + "target account of the transaction does not match neither the receiver account specified by the P2IDE storage, nor the sender account".into() ))) } }, @@ -336,71 +336,75 @@ impl WellKnownNote { // HELPER FUNCTIONS // ================================================================================================ -/// Returns the receiver account ID parsed from the provided P2ID note inputs. +/// Returns the receiver account ID parsed from the provided P2ID note storage. /// /// # Errors /// /// Returns an error if: -/// - the length of the provided note inputs array is not equal to the expected inputs number of the -/// P2ID note. -/// - first two elements of the note inputs array does not form the valid account ID. -fn parse_p2id_inputs(note_inputs: &[Felt]) -> Result { - if note_inputs.len() != WellKnownNote::P2ID.num_expected_inputs() { +/// - the length of the provided note storage array is not equal to the expected number of storage +/// items of the P2ID note. +/// - first two elements of the note storage array does not form the valid account ID. +fn parse_p2id_storage(note_storage: &[Felt]) -> Result { + if note_storage.len() != StandardNote::P2ID.expected_num_storage_items() { return Err(StaticAnalysisError::new(format!( - "P2ID note should have {} inputs, but {} was provided", - WellKnownNote::P2ID.num_expected_inputs(), - note_inputs.len() + "P2ID note should have {} storage items, but {} was provided", + StandardNote::P2ID.expected_num_storage_items(), + note_storage.len() ))); } - try_read_account_id_from_inputs(note_inputs) + try_read_account_id_from_storage(note_storage) } /// Returns the receiver account ID, reclaim height and timelock height parsed from the provided -/// P2IDE note inputs. +/// P2IDE note storage. /// /// # Errors /// /// Returns an error if: -/// - the length of the provided note inputs array is not equal to the expected inputs number of the -/// P2IDE note. -/// - first two elements of the note inputs array does not form the valid account ID. -/// - third note inputs array element (reclaim height) is not a valid u32 value. -/// - fourth note inputs array element (timelock height) is not a valid u32 value. -fn parse_p2ide_inputs(note_inputs: &[Felt]) -> Result<(AccountId, u32, u32), StaticAnalysisError> { - if note_inputs.len() != WellKnownNote::P2IDE.num_expected_inputs() { +/// - the length of the provided note storage array is not equal to the expected number of storage +/// items of the P2IDE note. +/// - first two elements of the note storage array does not form the valid account ID. +/// - third note storage array element (reclaim height) is not a valid u32 value. +/// - fourth note storage array element (timelock height) is not a valid u32 value. +fn parse_p2ide_storage( + note_storage: &[Felt], +) -> Result<(AccountId, u32, u32), StaticAnalysisError> { + if note_storage.len() != StandardNote::P2IDE.expected_num_storage_items() { return Err(StaticAnalysisError::new(format!( - "P2IDE note should have {} inputs, but {} was provided", - WellKnownNote::P2IDE.num_expected_inputs(), - note_inputs.len() + "P2IDE note should have {} storage items, but {} was provided", + StandardNote::P2IDE.expected_num_storage_items(), + note_storage.len() ))); } - let receiver_account_id = try_read_account_id_from_inputs(note_inputs)?; + let receiver_account_id = try_read_account_id_from_storage(note_storage)?; - let reclaim_height = u32::try_from(note_inputs[2]) + let reclaim_height = u32::try_from(note_storage[2]) .map_err(|_err| StaticAnalysisError::new("reclaim block height should be a u32"))?; - let timelock_height = u32::try_from(note_inputs[3]) + let timelock_height = u32::try_from(note_storage[3]) .map_err(|_err| StaticAnalysisError::new("timelock block height should be a u32"))?; Ok((receiver_account_id, reclaim_height, timelock_height)) } -/// Reads the account ID from the first two note input values. +/// Reads the account ID from the first two note storage values. /// -/// Returns None if the note input values used to construct the account ID are invalid. -fn try_read_account_id_from_inputs(note_inputs: &[Felt]) -> Result { - if note_inputs.len() < 2 { +/// Returns None if the note storage values used to construct the account ID are invalid. +fn try_read_account_id_from_storage( + note_storage: &[Felt], +) -> Result { + if note_storage.len() < 2 { return Err(StaticAnalysisError::new(format!( - "P2ID and P2IDE notes should have at least 2 note inputs, but {} was provided", - note_inputs.len() + "P2ID and P2IDE notes should have at least 2 note storage items, but {} was provided", + note_storage.len() ))); } - AccountId::try_from([note_inputs[1], note_inputs[0]]).map_err(|source| { + AccountId::try_from([note_storage[1], note_storage[0]]).map_err(|source| { StaticAnalysisError::with_source( - "failed to create an account ID from the first two note inputs", + "failed to create an account ID from the first two note storage items", source, ) }) diff --git a/crates/miden-standards/src/note/well_known_note_attachment.rs b/crates/miden-standards/src/note/standard_note_attachment.rs similarity index 52% rename from crates/miden-standards/src/note/well_known_note_attachment.rs rename to crates/miden-standards/src/note/standard_note_attachment.rs index 90ca70a5b3..17ec1332df 100644 --- a/crates/miden-standards/src/note/well_known_note_attachment.rs +++ b/crates/miden-standards/src/note/standard_note_attachment.rs @@ -1,18 +1,18 @@ use miden_protocol::note::NoteAttachmentScheme; -/// The [`NoteAttachmentScheme`]s of well-known note attachmens. +/// The [`NoteAttachmentScheme`]s of standard note attachments. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] -pub enum WellKnownNoteAttachment { +pub enum StandardNoteAttachment { /// See [`NetworkAccountTarget`](crate::note::NetworkAccountTarget) for details. NetworkAccountTarget, } -impl WellKnownNoteAttachment { - /// Returns the [`NoteAttachmentScheme`] of the well-known attachment. +impl StandardNoteAttachment { + /// Returns the [`NoteAttachmentScheme`] of the standard attachment. pub const fn attachment_scheme(&self) -> NoteAttachmentScheme { match self { - WellKnownNoteAttachment::NetworkAccountTarget => NoteAttachmentScheme::new(1u32), + StandardNoteAttachment::NetworkAccountTarget => NoteAttachmentScheme::new(1u32), } } } diff --git a/crates/miden-standards/src/note/utils.rs b/crates/miden-standards/src/note/utils.rs index b111e68bce..5c591ad6eb 100644 --- a/crates/miden-standards/src/note/utils.rs +++ b/crates/miden-standards/src/note/utils.rs @@ -2,10 +2,10 @@ use miden_protocol::account::AccountId; use miden_protocol::asset::Asset; use miden_protocol::block::BlockNumber; use miden_protocol::errors::NoteError; -use miden_protocol::note::{NoteInputs, NoteRecipient, NoteTag, NoteType}; +use miden_protocol::note::{NoteRecipient, NoteStorage, NoteTag, NoteType}; use miden_protocol::{Felt, Word}; -use super::well_known_note::WellKnownNote; +use super::standard_note::StandardNote; /// Creates a [NoteRecipient] for the P2ID note. /// @@ -15,10 +15,10 @@ pub fn build_p2id_recipient( target: AccountId, serial_num: Word, ) -> Result { - let note_script = WellKnownNote::P2ID.script(); - let note_inputs = NoteInputs::new(vec![target.suffix(), target.prefix().as_felt()])?; + let note_script = StandardNote::P2ID.script(); + let note_storage = NoteStorage::new(vec![target.suffix(), target.prefix().as_felt()])?; - Ok(NoteRecipient::new(serial_num, note_script, note_inputs)) + Ok(NoteRecipient::new(serial_num, note_script, note_storage)) } /// Creates a [NoteRecipient] for the P2IDE note. @@ -31,19 +31,19 @@ pub fn build_p2ide_recipient( timelock_block_height: Option, serial_num: Word, ) -> Result { - let note_script = WellKnownNote::P2IDE.script(); + let note_script = StandardNote::P2IDE.script(); let reclaim_height_u32 = reclaim_block_height.map_or(0, |bn| bn.as_u32()); let timelock_height_u32 = timelock_block_height.map_or(0, |bn| bn.as_u32()); - let note_inputs = NoteInputs::new(vec![ + let note_storage = NoteStorage::new(vec![ target.suffix(), target.prefix().into(), Felt::new(reclaim_height_u32 as u64), Felt::new(timelock_height_u32 as u64), ])?; - Ok(NoteRecipient::new(serial_num, note_script, note_inputs)) + Ok(NoteRecipient::new(serial_num, note_script, note_storage)) } /// Returns a note tag for a swap note with the specified parameters. @@ -63,7 +63,7 @@ pub fn build_swap_tag( offered_asset: &Asset, requested_asset: &Asset, ) -> NoteTag { - let swap_root_bytes = WellKnownNote::SWAP.script().root().as_bytes(); + let swap_root_bytes = StandardNote::SWAP.script().root().as_bytes(); // Construct the swap use case ID from the 14 most significant bits of the script root. This // leaves the two most significant bits zero. let mut swap_use_case_id = (swap_root_bytes[0] as u16) << 6; @@ -147,13 +147,13 @@ mod tests { // Check the 8 bits of the first script root byte. assert_eq!( (actual_tag.as_u32() >> 22) as u8, - WellKnownNote::SWAP.script().root().as_bytes()[0], + StandardNote::SWAP.script().root().as_bytes()[0], "swap script root byte 0 should match" ); // Extract the 6 bits of the second script root byte and shift for comparison. assert_eq!( ((actual_tag.as_u32() & 0b00000000_00111111_00000000_00000000) >> 16) as u8, - WellKnownNote::SWAP.script().root().as_bytes()[1] >> 2, + StandardNote::SWAP.script().root().as_bytes()[1] >> 2, "swap script root byte 1 should match with the lower two bits set to zero" ); } diff --git a/crates/miden-standards/src/testing/note.rs b/crates/miden-standards/src/testing/note.rs index 4e4460f432..e859d13a98 100644 --- a/crates/miden-standards/src/testing/note.rs +++ b/crates/miden-standards/src/testing/note.rs @@ -11,9 +11,9 @@ use miden_protocol::note::{ Note, NoteAssets, NoteAttachment, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, }; @@ -29,7 +29,7 @@ use crate::code_builder::CodeBuilder; #[derive(Debug, Clone)] pub struct NoteBuilder { sender: AccountId, - inputs: Vec, + storage: Vec, assets: Vec, note_type: NoteType, serial_num: Word, @@ -51,7 +51,7 @@ impl NoteBuilder { Self { sender, - inputs: vec![], + storage: vec![], assets: vec![], note_type: NoteType::Public, serial_num, @@ -64,15 +64,15 @@ impl NoteBuilder { } } - /// Set the note's input to `inputs`. + /// Set the note's storage to `storage`. /// /// Note: This overwrite the inputs, the previous input values are discarded. - pub fn note_inputs( + pub fn note_storage( mut self, - inputs: impl IntoIterator, + storage: impl IntoIterator, ) -> Result { - let validate = NoteInputs::new(inputs.into_iter().collect())?; - self.inputs = validate.into(); + let validate = NoteStorage::new(storage.into_iter().collect())?; + self.storage = validate.into(); Ok(self) } @@ -151,8 +151,8 @@ impl NoteBuilder { let vault = NoteAssets::new(self.assets)?; let metadata = NoteMetadata::new(self.sender, self.note_type, self.tag) .with_attachment(self.attachment); - let inputs = NoteInputs::new(self.inputs)?; - let recipient = NoteRecipient::new(self.serial_num, note_script, inputs); + let storage = NoteStorage::new(self.storage)?; + let recipient = NoteRecipient::new(self.serial_num, note_script, storage); Ok(Note::new(vault, metadata, recipient)) } diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index af565ab386..23bea98751 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -34,6 +34,7 @@ anyhow = { workspace = true } itertools = { default-features = false, features = ["use_alloc"], version = "0.14" } rand = { features = ["os_rng", "small_rng"], workspace = true } rand_chacha = { workspace = true } +thiserror = { workspace = true } winterfell = { version = "0.13" } [dev-dependencies] diff --git a/crates/miden-testing/src/executor.rs b/crates/miden-testing/src/executor.rs index 02b79705ed..b5b9b1e085 100644 --- a/crates/miden-testing/src/executor.rs +++ b/crates/miden-testing/src/executor.rs @@ -88,12 +88,20 @@ impl CodeExecutor { #[cfg(test)] impl CodeExecutor { pub fn with_default_host() -> Self { + use miden_protocol::ProtocolLib; use miden_protocol::transaction::TransactionKernel; + use miden_standards::StandardsLib; let mut host = DefaultHost::default(); - let test_lib = TransactionKernel::library(); - host.load_library(test_lib.mast_forest()).unwrap(); + let standards_lib = StandardsLib::default(); + host.load_library(standards_lib.mast_forest()).unwrap(); + + let protocol_lib = ProtocolLib::default(); + host.load_library(protocol_lib.mast_forest()).unwrap(); + + let kernel_lib = TransactionKernel::library(); + host.load_library(kernel_lib.mast_forest()).unwrap(); CodeExecutor::new(host) } diff --git a/crates/miden-testing/src/kernel_tests/tx/mod.rs b/crates/miden-testing/src/kernel_tests/tx/mod.rs index c3a9f84f77..212ed67c8e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/mod.rs +++ b/crates/miden-testing/src/kernel_tests/tx/mod.rs @@ -19,6 +19,7 @@ mod test_account; mod test_account_delta; mod test_account_interface; mod test_active_note; +mod test_array; mod test_asset; mod test_asset_vault; mod test_auth; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index d0146be16b..d315b9c7c0 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -23,7 +23,8 @@ use miden_protocol::account::{ StorageSlotName, StorageSlotType, }; -use miden_protocol::assembly::diagnostics::{IntoDiagnostic, NamedSource, Report, WrapErr, miette}; +use miden_protocol::assembly::diagnostics::NamedSource; +use miden_protocol::assembly::diagnostics::reporting::PrintDiagnostic; use miden_protocol::assembly::{DefaultSourceManager, Library}; use miden_protocol::asset::{Asset, FungibleAsset}; use miden_protocol::errors::tx_kernel::{ @@ -73,7 +74,7 @@ use crate::{ // ================================================================================================ #[tokio::test] -pub async fn compute_commitment() -> miette::Result<()> { +pub async fn compute_commitment() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); // Precompute a commitment to a changed account so we can assert it during tx script execution. @@ -144,19 +145,13 @@ pub async fn compute_commitment() -> miette::Result<()> { ); let tx_context_builder = TransactionContextBuilder::new(account); - let tx_script = CodeBuilder::with_mock_libraries() - .compile_tx_script(tx_script) - .into_diagnostic()?; - let tx_context = tx_context_builder - .tx_script(tx_script) - .build() - .map_err(|err| miette::miette!("{err}"))?; + let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(tx_script)?; + let tx_context = tx_context_builder.tx_script(tx_script).build()?; tx_context .execute() .await - .into_diagnostic() - .wrap_err("failed to execute transaction")?; + .map_err(|err| anyhow::anyhow!("failed to execute transaction: {err}"))?; Ok(()) } @@ -165,7 +160,7 @@ pub async fn compute_commitment() -> miette::Result<()> { // ================================================================================================ #[tokio::test] -async fn test_account_type() -> miette::Result<()> { +async fn test_account_type() -> anyhow::Result<()> { let procedures = vec![ ("is_fungible_faucet", AccountType::FungibleFaucet), ("is_non_fungible_faucet", AccountType::NonFungibleFaucet), @@ -197,9 +192,7 @@ async fn test_account_type() -> miette::Result<()> { ); let exec_output = CodeExecutor::with_default_host() - .stack_inputs( - StackInputs::new(vec![account_id.prefix().as_felt()]).into_diagnostic()?, - ) + .stack_inputs(StackInputs::new(vec![account_id.prefix().as_felt()])?) .run(&code) .await?; @@ -225,7 +218,7 @@ async fn test_account_type() -> miette::Result<()> { } #[tokio::test] -async fn test_account_validate_id() -> miette::Result<()> { +async fn test_account_validate_id() -> anyhow::Result<()> { let test_cases = [ (ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, None), (ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, None), @@ -275,23 +268,27 @@ async fn test_account_validate_id() -> miette::Result<()> { match (result, expected_error) { (Ok(_), None) => (), (Ok(_), Some(err)) => { - miette::bail!("expected error {err} but validation was successful") + anyhow::bail!("expected error {err} but validation was successful") }, (Err(ExecutionError::FailedAssertion { err_code, err_msg, .. }), Some(err)) => { if err_code != err.code() { - miette::bail!( + anyhow::bail!( "actual error \"{}\" (code: {err_code}) did not match expected error {err}", err_msg.as_ref().map(AsRef::as_ref).unwrap_or("") ); } }, - // Construct Reports to get the diagnostics-based error messages. (Err(err), None) => { - return Err(Report::from(err) - .context("validation is supposed to succeed but error occurred")); + return Err(anyhow::anyhow!( + "validation is supposed to succeed but error occurred: {}", + PrintDiagnostic::new(&err) + )); }, (Err(err), Some(_)) => { - return Err(Report::from(err).context("unexpected different error than expected")); + return Err(anyhow::anyhow!( + "unexpected different error than expected: {}", + PrintDiagnostic::new(&err) + )); }, } } @@ -300,7 +297,7 @@ async fn test_account_validate_id() -> miette::Result<()> { } #[tokio::test] -async fn test_is_faucet_procedure() -> miette::Result<()> { +async fn test_is_faucet_procedure() -> anyhow::Result<()> { let test_cases = [ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, @@ -327,10 +324,9 @@ async fn test_is_faucet_procedure() -> miette::Result<()> { prefix = account_id.prefix().as_felt(), ); - let exec_output = CodeExecutor::with_default_host() - .run(&code) - .await - .wrap_err("failed to execute is_faucet procedure")?; + let exec_output = CodeExecutor::with_default_host().run(&code).await.map_err(|err| { + anyhow::anyhow!("failed to execute is_faucet procedure: {}", PrintDiagnostic::new(&err)) + })?; let is_faucet = account_id.is_faucet(); assert_eq!( @@ -348,7 +344,7 @@ async fn test_is_faucet_procedure() -> miette::Result<()> { // TODO: update this test once the ability to change the account code will be implemented #[tokio::test] -pub async fn test_compute_code_commitment() -> miette::Result<()> { +pub async fn test_compute_code_commitment() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); let account = tx_context.account(); @@ -377,7 +373,7 @@ pub async fn test_compute_code_commitment() -> miette::Result<()> { // ================================================================================================ #[tokio::test] -async fn test_get_item() -> miette::Result<()> { +async fn test_get_item() -> anyhow::Result<()> { for storage_item in [AccountStorage::mock_value_slot0(), AccountStorage::mock_value_slot1()] { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); @@ -412,7 +408,7 @@ async fn test_get_item() -> miette::Result<()> { } #[tokio::test] -async fn test_get_map_item() -> miette::Result<()> { +async fn test_get_map_item() -> anyhow::Result<()> { let slot = AccountStorage::mock_map_slot(); let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) @@ -459,7 +455,7 @@ async fn test_get_map_item() -> miette::Result<()> { } #[tokio::test] -async fn test_get_storage_slot_type() -> miette::Result<()> { +async fn test_get_storage_slot_type() -> anyhow::Result<()> { for slot_name in [ AccountStorage::mock_value_slot0().name(), AccountStorage::mock_value_slot1().name(), @@ -604,7 +600,7 @@ async fn test_account_set_item_fails_on_reserved_faucet_metadata_slot() -> anyho } #[tokio::test] -async fn test_is_slot_id_lt() -> miette::Result<()> { +async fn test_is_slot_id_lt() -> anyhow::Result<()> { // Note that the slot IDs derived from the names are essentially randomly sorted, so these cover // "less than" and "greater than" outcomes. let mut test_cases = (0..100) @@ -711,7 +707,7 @@ async fn test_set_item() -> anyhow::Result<()> { } #[tokio::test] -async fn test_set_map_item() -> miette::Result<()> { +async fn test_set_map_item() -> anyhow::Result<()> { let (new_key, new_value) = (Word::from([109, 110, 111, 112u32]), Word::from([9, 10, 11, 12u32])); @@ -1320,7 +1316,7 @@ async fn test_get_init_balance_subtraction() -> anyhow::Result<()> { // ================================================================================================ #[tokio::test] -async fn test_authenticate_and_track_procedure() -> miette::Result<()> { +async fn test_authenticate_and_track_procedure() -> anyhow::Result<()> { let mock_component = MockAccountComponent::with_empty_slots(); let account_code = AccountCode::from_components( @@ -1379,7 +1375,7 @@ async fn test_authenticate_and_track_procedure() -> miette::Result<()> { // ================================================================================================ #[tokio::test] -async fn test_was_procedure_called() -> miette::Result<()> { +async fn test_was_procedure_called() -> anyhow::Result<()> { // Create a standard account using the mock component let mock_component = MockAccountComponent::with_slots(AccountStorage::mock_storage_slots()); let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) @@ -1429,9 +1425,7 @@ async fn test_was_procedure_called() -> miette::Result<()> { ); // Compile the transaction script using the testing assembler with mock account - let tx_script = CodeBuilder::with_mock_libraries() - .compile_tx_script(tx_script_code) - .into_diagnostic()?; + let tx_script = CodeBuilder::with_mock_libraries().compile_tx_script(tx_script_code)?; // Create transaction context and execute let tx_context = TransactionContextBuilder::new(account).tx_script(tx_script).build().unwrap(); @@ -1439,8 +1433,7 @@ async fn test_was_procedure_called() -> miette::Result<()> { tx_context .execute() .await - .into_diagnostic() - .wrap_err("Failed to execute transaction")?; + .map_err(|err| anyhow::anyhow!("Failed to execute transaction: {err}"))?; Ok(()) } @@ -1451,7 +1444,7 @@ async fn test_was_procedure_called() -> miette::Result<()> { /// The call chain and dependency graph in this test is: /// `tx script -> account code -> external library` #[tokio::test] -async fn transaction_executor_account_code_using_custom_library() -> miette::Result<()> { +async fn transaction_executor_account_code_using_custom_library() -> anyhow::Result<()> { let external_library_code = format!( r#" use miden::protocol::native_account @@ -1476,15 +1469,20 @@ async fn transaction_executor_account_code_using_custom_library() -> miette::Res let external_library_source = NamedSource::new("external_library::external_module", external_library_code); - let external_library = - TransactionKernel::assembler().assemble_library([external_library_source])?; + let external_library = TransactionKernel::assembler() + .assemble_library([external_library_source]) + .map_err(|err| { + anyhow::anyhow!("failed to assemble library: {}", PrintDiagnostic::new(&err)) + })?; let mut assembler: miden_protocol::assembly::Assembler = CodeBuilder::with_mock_libraries_with_source_manager(Arc::new( DefaultSourceManager::default(), )) .into(); - assembler.link_static_library(&external_library)?; + assembler.link_static_library(&external_library).map_err(|err| { + anyhow::anyhow!("failed to link static library: {}", PrintDiagnostic::new(&err)) + })?; let account_component_source = NamedSource::new("account_component::account_module", ACCOUNT_COMPONENT_CODE); @@ -1499,29 +1497,25 @@ async fn transaction_executor_account_code_using_custom_library() -> miette::Res end"; let account_component = - AccountComponent::new(account_component_lib.clone(), AccountStorage::mock_storage_slots()) - .into_diagnostic()? + AccountComponent::new(account_component_lib.clone(), AccountStorage::mock_storage_slots())? .with_supports_all_types(); // Build an existing account with nonce 1. let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) .with_component(account_component) - .build_existing() - .into_diagnostic()?; + .build_existing()?; let tx_script = CodeBuilder::default() - .with_dynamically_linked_library(&account_component_lib) - .into_diagnostic()? - .compile_tx_script(tx_script_src) - .into_diagnostic()?; + .with_dynamically_linked_library(&account_component_lib)? + .compile_tx_script(tx_script_src)?; let tx_context = TransactionContextBuilder::new(native_account.clone()) .tx_script(tx_script) .build() .unwrap(); - let executed_tx = tx_context.execute().await.into_diagnostic()?; + let executed_tx = tx_context.execute().await?; // Account's initial nonce of 1 should have been incremented by 1. assert_eq!(executed_tx.account_delta().nonce_delta(), Felt::new(1)); @@ -1565,7 +1559,7 @@ async fn incrementing_nonce_twice_fails() -> anyhow::Result<()> { } #[tokio::test] -async fn test_has_procedure() -> miette::Result<()> { +async fn test_has_procedure() -> anyhow::Result<()> { // Create a standard account using the mock component let mock_component = MockAccountComponent::with_slots(AccountStorage::mock_storage_slots()); let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) @@ -1603,7 +1597,7 @@ async fn test_has_procedure() -> miette::Result<()> { // Compile the transaction script using the testing assembler with mock account let tx_script = CodeBuilder::with_mock_libraries() .compile_tx_script(tx_script_code) - .into_diagnostic()?; + .map_err(|err| anyhow::anyhow!("{err}"))?; // Create transaction context and execute let tx_context = TransactionContextBuilder::new(account).tx_script(tx_script).build().unwrap(); @@ -1611,8 +1605,7 @@ async fn test_has_procedure() -> miette::Result<()> { tx_context .execute() .await - .into_diagnostic() - .wrap_err("Failed to execute transaction")?; + .map_err(|err| anyhow::anyhow!("Failed to execute transaction: {err}"))?; Ok(()) } @@ -1621,7 +1614,7 @@ async fn test_has_procedure() -> miette::Result<()> { // ================================================================================================ #[tokio::test] -async fn test_get_initial_item() -> miette::Result<()> { +async fn test_get_initial_item() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); // Test that get_initial_item returns the initial value before any changes @@ -1671,7 +1664,7 @@ async fn test_get_initial_item() -> miette::Result<()> { } #[tokio::test] -async fn test_get_initial_map_item() -> miette::Result<()> { +async fn test_get_initial_map_item() -> anyhow::Result<()> { let map_slot = AccountStorage::mock_map_slot(); let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_auth_component(Auth::IncrNonce) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index dc4beb3519..ccd2cd8d2f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -10,9 +10,9 @@ use miden_protocol::crypto::rand::FeltRng; use miden_protocol::note::{ Note, NoteAssets, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, }; @@ -26,7 +26,7 @@ use miden_protocol::transaction::{InputNote, OutputNote, TransactionKernel}; use miden_protocol::{Felt, StarkField, Word}; use miden_standards::note::{ NoteConsumptionStatus, - WellKnownNote, + StandardNote, create_p2id_note, create_p2ide_note, }; @@ -47,7 +47,7 @@ use crate::utils::create_public_p2any_note; use crate::{Auth, MockChain, TransactionContextBuilder, TxContextInput}; #[tokio::test] -async fn check_note_consumability_well_known_notes_success() -> anyhow::Result<()> { +async fn check_note_consumability_standard_notes_success() -> anyhow::Result<()> { let p2id_note = create_p2id_note( ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(), ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE.try_into().unwrap(), @@ -470,16 +470,16 @@ async fn test_check_note_consumability_static_analysis_invalid_inputs() -> anyho // create notes for testing // -------------------------------------------------------------------------------------------- - let p2ide_wrong_inputs_number = create_p2ide_note_with_inputs([1, 2, 3], sender_account_id); + let p2ide_wrong_inputs_number = create_p2ide_note_with_storage([1, 2, 3], sender_account_id); - let p2ide_invalid_target_id = create_p2ide_note_with_inputs([1, 2, 3, 4], sender_account_id); + let p2ide_invalid_target_id = create_p2ide_note_with_storage([1, 2, 3, 4], sender_account_id); - let p2ide_wrong_target = create_p2ide_note_with_inputs( + let p2ide_wrong_target = create_p2ide_note_with_storage( [wrong_target_id.suffix().as_int(), wrong_target_id.prefix().as_u64(), 3, 4], sender_account_id, ); - let p2ide_invalid_reclaim = create_p2ide_note_with_inputs( + let p2ide_invalid_reclaim = create_p2ide_note_with_storage( [ target_account_id.suffix().as_int(), target_account_id.prefix().as_u64(), @@ -489,7 +489,7 @@ async fn test_check_note_consumability_static_analysis_invalid_inputs() -> anyho sender_account_id, ); - let p2ide_invalid_timelock = create_p2ide_note_with_inputs( + let p2ide_invalid_timelock = create_p2ide_note_with_storage( [ target_account_id.suffix().as_int(), target_account_id.prefix().as_u64(), @@ -535,9 +535,9 @@ async fn test_check_note_consumability_static_analysis_invalid_inputs() -> anyho .await?; assert_matches!(consumability_info, NoteConsumptionStatus::NeverConsumable(reason) => { assert_eq!(reason.to_string(), format!( - "P2IDE note should have {} inputs, but {} was provided", - WellKnownNote::P2IDE.num_expected_inputs(), - p2ide_wrong_inputs_number.recipient().inputs().num_values() + "P2IDE note should have {} storage items, but {} was provided", + StandardNote::P2IDE.expected_num_storage_items(), + p2ide_wrong_inputs_number.recipient().storage().num_items() )); }); @@ -552,7 +552,7 @@ async fn test_check_note_consumability_static_analysis_invalid_inputs() -> anyho ) .await?; assert_matches!(consumability_info, NoteConsumptionStatus::NeverConsumable(reason) => { - assert_eq!(reason.to_string(), "failed to create an account ID from the first two note inputs"); + assert_eq!(reason.to_string(), "failed to create an account ID from the first two note storage items"); }); // check the note with a wrong target account ID (target is neither the sender nor the receiver) @@ -566,7 +566,7 @@ async fn test_check_note_consumability_static_analysis_invalid_inputs() -> anyho ) .await?; assert_matches!(consumability_info, NoteConsumptionStatus::NeverConsumable(reason) => { - assert_eq!(reason.to_string(), "target account of the transaction does not match neither the receiver account specified by the P2IDE inputs, nor the sender account"); + assert_eq!(reason.to_string(), "target account of the transaction does not match neither the receiver account specified by the P2IDE storage, nor the sender account"); }); // check the note with an invalid reclaim height @@ -648,7 +648,7 @@ async fn test_check_note_consumability_static_analysis_receiver( let target_account_id = account.id(); let sender_account_id = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(); - let p2ide = create_p2ide_note_with_inputs( + let p2ide = create_p2ide_note_with_storage( [ target_account_id.suffix().as_int(), target_account_id.prefix().as_u64(), @@ -738,7 +738,7 @@ async fn test_check_note_consumability_static_analysis_sender( let target_account_id: AccountId = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(); - let p2ide = create_p2ide_note_with_inputs( + let p2ide = create_p2ide_note_with_storage( [ target_account_id.suffix().as_int(), target_account_id.prefix().as_u64(), @@ -782,14 +782,17 @@ async fn test_check_note_consumability_static_analysis_sender( // HELPER FUNCTIONS // ================================================================================================ -/// Creates a mock P2IDE note with the specified note inputs. -fn create_p2ide_note_with_inputs(inputs: impl IntoIterator, sender: AccountId) -> Note { +/// Creates a mock P2IDE note with the specified note storage. +fn create_p2ide_note_with_storage( + storage: impl IntoIterator, + sender: AccountId, +) -> Note { let serial_num = RpoRandomCoin::new(Default::default()).draw_word(); - let note_script = WellKnownNote::P2IDE.script(); + let note_script = StandardNote::P2IDE.script(); let recipient = NoteRecipient::new( serial_num, note_script, - NoteInputs::new(inputs.into_iter().map(Felt::new).collect()).unwrap(), + NoteStorage::new(storage.into_iter().map(Felt::new).collect()).unwrap(), ); let tag = NoteTag::with_account_target(sender); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 3b2301338f..1c5b8488d7 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -8,9 +8,9 @@ use miden_protocol::errors::tx_kernel::ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_ use miden_protocol::note::{ Note, NoteAssets, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, }; @@ -223,7 +223,7 @@ async fn test_active_note_get_assets() -> anyhow::Result<()> { use miden::protocol::active_note proc process_note_0 - # drop the note inputs + # drop the note storage dropw dropw dropw dropw # set the destination pointer for note 0 assets @@ -246,7 +246,7 @@ async fn test_active_note_get_assets() -> anyhow::Result<()> { end proc process_note_1 - # drop the note inputs + # drop the note storage dropw dropw dropw dropw # set the destination pointer for note 1 assets @@ -321,17 +321,17 @@ async fn test_active_note_get_inputs() -> anyhow::Result<()> { .build()? }; - fn construct_inputs_assertions(note: &Note) -> String { + fn construct_storage_assertions(note: &Note) -> String { let mut code = String::new(); - for inputs_chunk in note.inputs().values().chunks(WORD_SIZE) { - let mut inputs_word = EMPTY_WORD; - inputs_word.as_mut_slice()[..inputs_chunk.len()].copy_from_slice(inputs_chunk); + for storage_chunk in note.storage().items().chunks(WORD_SIZE) { + let mut storage_word = EMPTY_WORD; + storage_word.as_mut_slice()[..storage_chunk.len()].copy_from_slice(storage_chunk); code += &format!( r#" - # assert the inputs are correct + # assert the storage items are correct # => [dest_ptr] - dup padw movup.4 mem_loadw_be push.{inputs_word} assert_eqw.err="inputs are incorrect" + dup padw movup.4 mem_loadw_be push.{storage_word} assert_eqw.err="storage items are incorrect" # => [dest_ptr] push.4 add @@ -362,17 +362,17 @@ async fn test_active_note_get_inputs() -> anyhow::Result<()> { dropw dropw dropw dropw # => [] - push.{NOTE_0_PTR} exec.active_note::get_inputs - # => [num_inputs, dest_ptr] + push.{NOTE_0_PTR} exec.active_note::get_storage + # => [num_storage_items, dest_ptr] - eq.{num_inputs} assert.err="unexpected num inputs" + eq.{num_storage_items} assert.err="unexpected num_storage_items" # => [dest_ptr] dup eq.{NOTE_0_PTR} assert.err="unexpected dest ptr" # => [dest_ptr] - # apply note 1 inputs assertions - {inputs_assertions} + # apply note 1 storage assertions + {storage_assertions} # => [dest_ptr] # clear the stack @@ -380,8 +380,8 @@ async fn test_active_note_get_inputs() -> anyhow::Result<()> { # => [] end "#, - num_inputs = note0.inputs().num_values(), - inputs_assertions = construct_inputs_assertions(note0), + num_storage_items = note0.storage().num_items(), + storage_assertions = construct_storage_assertions(note0), NOTE_0_PTR = 100000000, ); @@ -389,12 +389,12 @@ async fn test_active_note_get_inputs() -> anyhow::Result<()> { Ok(()) } -/// This test checks the scenario when an input note has exactly 8 inputs, and the transaction -/// script attempts to load the inputs to memory using the +/// This test checks the scenario when an input note has exactly 8 storage items, and the +/// transaction script attempts to load the storage to memory using the /// `miden::protocol::active_note::get_inputs` procedure. /// -/// Previously this setup was leading to the incorrect number of note inputs computed during the -/// `get_inputs` procedure, see the [issue #1363](https://github.com/0xMiden/miden-base/issues/1363) +/// Previously this setup was leading to the incorrect number of note storage items computed during +/// the `get_inputs` procedure, see the [issue #1363](https://github.com/0xMiden/miden-base/issues/1363) /// for more details. #[tokio::test] async fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { @@ -414,12 +414,12 @@ async fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { .compile_note_script("begin nop end") .context("failed to parse note script")?; - // create a recipient with note inputs, which number divides by 8. For simplicity create 8 input - // values + // create a recipient with note storage, which number divides by 8. For simplicity create 8 + // storage values let recipient = NoteRecipient::new( serial_num, note_script, - NoteInputs::new(vec![ + NoteStorage::new(vec![ ONE, Felt::new(2), Felt::new(3), @@ -429,7 +429,7 @@ async fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { Felt::new(7), Felt::new(8), ]) - .context("failed to create note inputs")?, + .context("failed to create note storage")?, ); let input_note = Note::new(vault.clone(), metadata, recipient); @@ -445,12 +445,12 @@ async fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - # execute the `get_inputs` procedure to trigger note inputs length assertion - push.0 exec.active_note::get_inputs - # => [num_inputs, 0] + # execute the `get_storage` procedure to trigger note number of storage items assertion + push.0 exec.active_note::get_storage + # => [num_storage_items, 0] - # assert that the inputs length is 8 - push.8 assert_eq.err=\"number of inputs values should be equal to 8\" + # assert that the number of storage items is 8 + push.8 assert_eq.err=\"number of storage values should be equal to 8\" # clean the stack drop diff --git a/crates/miden-testing/src/kernel_tests/tx/test_array.rs b/crates/miden-testing/src/kernel_tests/tx/test_array.rs new file mode 100644 index 0000000000..57913bdb4b --- /dev/null +++ b/crates/miden-testing/src/kernel_tests/tx/test_array.rs @@ -0,0 +1,135 @@ +//! Tests for the Array utility `get` and `set` procedures. + +use miden_protocol::account::{ + AccountBuilder, + AccountComponent, + StorageMap, + StorageSlot, + StorageSlotName, +}; +use miden_protocol::{Felt, FieldElement, Word}; +use miden_standards::code_builder::CodeBuilder; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha20Rng; + +use crate::{Auth, TransactionContextBuilder}; + +/// The slot name used for testing the array component. +const TEST_ARRAY_SLOT: &str = "test::array::data"; + +/// Verify that, given an account component with a storage map to hold the array data, +/// we can use the array utility to: +/// 1. Retrieve the initial value via `get` +/// 2. Update the value via `set` +/// 3. Retrieve the updated value via `get` +#[tokio::test] +async fn test_array_get_and_set() -> anyhow::Result<()> { + let slot_name = StorageSlotName::new(TEST_ARRAY_SLOT).expect("slot name should be valid"); + + let wrapper_component_code = format!( + r#" + use miden::core::word + use miden::standards::data_structures::array + + const ARRAY_SLOT_NAME = word("{slot_name}") + + #! Wrapper for array::get that uses exec internally. + #! Inputs: [index, pad(15)] + #! Outputs: [VALUE, pad(12)] + pub proc test_get + push.ARRAY_SLOT_NAME[0..2] + exec.array::get + end + + #! Wrapper for array::set that uses exec internally. + #! Inputs: [index, VALUE, pad(11)] + #! Outputs: [OLD_VALUE, pad(12)] + pub proc test_set + push.ARRAY_SLOT_NAME[0..2] + exec.array::set + end + "#, + ); + + // Build the wrapper component by linking against the array library + let wrapper_library = CodeBuilder::default() + .compile_component_code("wrapper::component", wrapper_component_code)?; + + // Create the wrapper account component with a storage map to hold the array data + let initial_value = Word::from([42u32, 42, 42, 42]); + let wrapper_component = AccountComponent::new( + wrapper_library.clone(), + vec![StorageSlot::with_map( + slot_name.clone(), + StorageMap::with_entries([( + Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO]), + initial_value, + )])?, + )], + )? + .with_supports_all_types(); + + // Build an account with the wrapper component that uses the array utility + let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_auth_component(Auth::IncrNonce) + .with_component(wrapper_component) + .build_existing()?; + + // Verify the storage slot exists + assert!( + account.storage().get(&slot_name).is_some(), + "Array data slot should exist in account storage" + ); + + // Transaction script that: + // 1. Gets the initial value at index 0 (should be [42, 42, 42, 42]) + // 2. Sets index 0 to [43, 43, 43, 43] + // 3. Gets the updated value at index 0 (should be [43, 43, 43, 43]) + let tx_script_code = r#" + use wrapper::component->wrapper + + begin + # Step 1: Get value at index 0 (should return [42, 42, 42, 42]) + push.0 + # => [index, pad(16)] + call.wrapper::test_get + # => [VALUE, pad(13)] + + # Verify value is [42, 42, 42, 42] + push.42.42.42.42 + assert_eqw.err="get(0) should return [42, 42, 42, 42] initially" + # => [pad(16)] (auto-padding) + + # Step 2: Set value at index 0 to [43, 43, 43, 43] + push.43.43.43.43 + push.0 + # => [index, VALUE, pad(16)] + call.wrapper::test_set + # => [OLD_VALUE, pad(17)] + dropw + + # Step 3: Get value at index 0 (should return [43, 43, 43, 43]) + push.0 + # => [index, pad(17)] + call.wrapper::test_get + # => [VALUE, pad(14)] + + # Verify value is [43, 43, 43, 43] + push.43.43.43.43 + assert_eqw.err="get(0) should return [43, 43, 43, 43] after set" + # => [pad(16)] (auto-padding) + end + "#; + + // Compile the transaction script with the wrapper library linked + let tx_script = CodeBuilder::default() + .with_dynamically_linked_library(&wrapper_library)? + .compile_tx_script(tx_script_code)?; + + // Create transaction context and execute + let tx_context = TransactionContextBuilder::new(account).tx_script(tx_script).build()?; + + tx_context.execute().await?; + + Ok(()) +} diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index 84a9d9322a..aaf943ad8c 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -292,10 +292,10 @@ async fn test_get_assets() -> anyhow::Result<()> { Ok(()) } -/// Check that the number of the inputs and their commitment of a note with one asset -/// obtained from the `input_note::get_inputs_info` procedure is correct. +/// Check that the number of the storage items and their commitment of a note with one asset +/// obtained from the `input_note::get_storage_info` procedure is correct. #[tokio::test] -async fn test_get_inputs_info() -> anyhow::Result<()> { +async fn test_get_storage_info() -> anyhow::Result<()> { let TestSetup { mock_chain, account, @@ -309,25 +309,25 @@ async fn test_get_inputs_info() -> anyhow::Result<()> { use miden::protocol::input_note begin - # get the inputs commitment and length from the input note with index 0 (the only one + # get the storage commitment and length from the input note with index 0 (the only one # we have) push.0 - exec.input_note::get_inputs_info - # => [NOTE_INPUTS_COMMITMENT, inputs_num] + exec.input_note::get_storage_info + # => [NOTE_STORAGE_COMMITMENT, num_storage_items] - # assert the correctness of the inputs commitment - push.{INPUTS_COMMITMENT} - assert_eqw.err="note 0 has incorrect inputs commitment" - # => [inputs_num] + # assert the correctness of the storage commitment + push.{STORAGE_COMMITMENT} + assert_eqw.err="note 0 has incorrect storage commitment" + # => [num_storage_items] - # assert the inputs have correct length - push.{inputs_num} - assert_eq.err="note 0 has incorrect inputs length" + # assert the storage has correct length + push.{num_storage_items} + assert_eq.err="note 0 has incorrect number of storage items" # => [] end "#, - INPUTS_COMMITMENT = p2id_note_1_asset.inputs().commitment(), - inputs_num = p2id_note_1_asset.inputs().num_values(), + STORAGE_COMMITMENT = p2id_note_1_asset.storage().commitment(), + num_storage_items = p2id_note_1_asset.storage().num_items(), ); let tx_script = CodeBuilder::default().compile_tx_script(code)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 49a5be0c60..e147a3ccb0 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -6,7 +6,6 @@ use miden_processor::fast::ExecutionOutput; use miden_protocol::account::auth::PublicKeyCommitment; use miden_protocol::account::{AccountBuilder, AccountId}; use miden_protocol::assembly::DefaultSourceManager; -use miden_protocol::assembly::diagnostics::miette::{self, miette}; use miden_protocol::asset::FungibleAsset; use miden_protocol::crypto::dsa::falcon512_rpo::SecretKey; use miden_protocol::crypto::rand::{FeltRng, RpoRandomCoin}; @@ -14,9 +13,9 @@ use miden_protocol::errors::MasmError; use miden_protocol::note::{ Note, NoteAssets, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, }; @@ -87,27 +86,23 @@ async fn test_note_setup() -> anyhow::Result<()> { } #[tokio::test] -async fn test_note_script_and_note_args() -> miette::Result<()> { +async fn test_note_script_and_note_args() -> anyhow::Result<()> { let mut tx_context = { let mut builder = MockChain::builder(); - let account = builder.add_existing_wallet(Auth::BasicAuth).map_err(|err| miette!(err))?; - let p2id_note_1 = builder - .add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(150)], - NoteType::Public, - ) - .map_err(|err| miette!(err))?; - let p2id_note_2 = builder - .add_p2id_note( - ACCOUNT_ID_SENDER.try_into().unwrap(), - account.id(), - &[FungibleAsset::mock(300)], - NoteType::Public, - ) - .map_err(|err| miette!(err))?; - let mut mock_chain = builder.build().map_err(|err| miette!(err))?; + let account = builder.add_existing_wallet(Auth::BasicAuth)?; + let p2id_note_1 = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(150)], + NoteType::Public, + )?; + let p2id_note_2 = builder.add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[FungibleAsset::mock(300)], + NoteType::Public, + )?; + let mut mock_chain = builder.build()?; mock_chain.prove_next_block().unwrap(); mock_chain @@ -173,7 +168,7 @@ fn note_setup_stack_assertions(exec_output: &ExecutionOutput, inputs: &Transacti note_script_root.reverse(); expected_stack[..4].copy_from_slice(¬e_script_root); - // assert that the stack contains the note inputs at the end of execution + // assert that the stack contains the note storage at the end of execution assert_eq!(exec_output.stack.as_slice(), expected_stack.as_slice()) } @@ -211,21 +206,21 @@ async fn test_build_recipient() -> anyhow::Result<()> { # Test with 4 values (needs padding to 8) push.{script_root} # SCRIPT_ROOT push.{serial_num} # SERIAL_NUM - push.4.4000 # num_inputs, inputs_ptr + push.4.4000 # num_storage_items, storage_ptr exec.note::build_recipient # => [RECIPIENT_4] # Test with 5 values (needs padding to 8) push.{script_root} # SCRIPT_ROOT push.{serial_num} # SERIAL_NUM - push.5.4000 # num_inputs, inputs_ptr + push.5.4000 # num_storage_items, storage_ptr exec.note::build_recipient # => [RECIPIENT_5, RECIPIENT_4] # Test with 8 values (no padding needed - exactly one rate block) push.{script_root} # SCRIPT_ROOT push.{serial_num} # SERIAL_NUM - push.8.4000 # num_inputs, inputs_ptr + push.8.4000 # num_storage_items, storage_ptr exec.note::build_recipient # => [RECIPIENT_8, RECIPIENT_5, RECIPIENT_4] @@ -243,41 +238,41 @@ async fn test_build_recipient() -> anyhow::Result<()> { let exec_output = &tx_context.execute_code(&code).await?; - // Create expected NoteInputs for each test case + // Create expected NoteStorage for each test case let inputs_4 = word_1.to_vec(); - let note_inputs_4 = NoteInputs::new(inputs_4.clone())?; + let note_storage_4 = NoteStorage::new(inputs_4.clone())?; let mut inputs_5 = word_1.to_vec(); inputs_5.push(word_2[0]); - let note_inputs_5 = NoteInputs::new(inputs_5.clone())?; + let note_storage_5 = NoteStorage::new(inputs_5.clone())?; let mut inputs_8 = word_1.to_vec(); inputs_8.extend_from_slice(&word_2.to_vec()); - let note_inputs_8 = NoteInputs::new(inputs_8.clone())?; + let note_storage_8 = NoteStorage::new(inputs_8.clone())?; // Create expected recipients and get their digests - let recipient_4 = NoteRecipient::new(serial_num, note_script.clone(), note_inputs_4.clone()); - let recipient_5 = NoteRecipient::new(serial_num, note_script.clone(), note_inputs_5.clone()); - let recipient_8 = NoteRecipient::new(serial_num, note_script.clone(), note_inputs_8.clone()); - - for note_inputs in [ - (note_inputs_4, inputs_4.clone()), - (note_inputs_5, inputs_5.clone()), - (note_inputs_8, inputs_8.clone()), + let recipient_4 = NoteRecipient::new(serial_num, note_script.clone(), note_storage_4.clone()); + let recipient_5 = NoteRecipient::new(serial_num, note_script.clone(), note_storage_5.clone()); + let recipient_8 = NoteRecipient::new(serial_num, note_script.clone(), note_storage_8.clone()); + + for note_storage in [ + (note_storage_4, inputs_4.clone()), + (note_storage_5, inputs_5.clone()), + (note_storage_8, inputs_8.clone()), ] { - let inputs_advice_map_key = note_inputs.0.commitment(); + let inputs_advice_map_key = note_storage.0.commitment(); assert_eq!( exec_output.advice.get_mapped_values(&inputs_advice_map_key).unwrap(), - note_inputs.1, - "advice entry with note inputs should contain the unpadded values" + note_storage.1, + "advice entry with note storage should contain the unpadded values" ); - let num_inputs_advice_map_key = - Hasher::hash_elements(note_inputs.0.commitment().as_elements()); + let num_storage_items_advice_map_key = + Hasher::hash_elements(note_storage.0.commitment().as_elements()); assert_eq!( - exec_output.advice.get_mapped_values(&num_inputs_advice_map_key).unwrap(), - &[Felt::from(note_inputs.0.num_values())], - "advice entry with num note inputs should contain the original number of values" + exec_output.advice.get_mapped_values(&num_storage_items_advice_map_key).unwrap(), + &[Felt::from(note_storage.0.num_items())], + "advice entry with note number of storage items should contain the original number of values" ); } @@ -292,7 +287,7 @@ async fn test_build_recipient() -> anyhow::Result<()> { } #[tokio::test] -async fn test_compute_inputs_commitment() -> anyhow::Result<()> { +async fn test_compute_storage_commitment() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; // Define test values as Words @@ -315,26 +310,26 @@ async fn test_compute_inputs_commitment() -> anyhow::Result<()> { push.{word_3} push.{addr_2} mem_storew_be dropw push.{word_4} push.{addr_3} mem_storew_be dropw - # push the number of values and pointer to the inputs on the stack + # push the number of values and pointer to the storage on the stack push.5.4000 - # execute the `compute_inputs_commitment` procedure for 5 values - exec.note::compute_inputs_commitment + # execute the `compute_storage_commitment` procedure for 5 values + exec.note::compute_storage_commitment # => [HASH_5] push.8.4000 - # execute the `compute_inputs_commitment` procedure for 8 values - exec.note::compute_inputs_commitment + # execute the `compute_storage_commitment` procedure for 8 values + exec.note::compute_storage_commitment # => [HASH_8, HASH_5] push.15.4000 - # execute the `compute_inputs_commitment` procedure for 15 values - exec.note::compute_inputs_commitment + # execute the `compute_storage_commitment` procedure for 15 values + exec.note::compute_storage_commitment # => [HASH_15, HASH_8, HASH_5] push.0.4000 - # check that calling `compute_inputs_commitment` procedure with 0 elements will result in an + # check that calling `compute_storage_commitment` procedure with 0 elements will result in an # empty word - exec.note::compute_inputs_commitment + exec.note::compute_storage_commitment # => [0, 0, 0, 0, HASH_15, HASH_8, HASH_5] # truncate the stack @@ -355,23 +350,23 @@ async fn test_compute_inputs_commitment() -> anyhow::Result<()> { let mut inputs_5 = word_1.to_vec(); inputs_5.push(word_2[0]); - let note_inputs_5_hash = NoteInputs::new(inputs_5)?.commitment(); + let note_storage_5_hash = NoteStorage::new(inputs_5)?.commitment(); let mut inputs_8 = word_1.to_vec(); inputs_8.extend_from_slice(&word_2.to_vec()); - let note_inputs_8_hash = NoteInputs::new(inputs_8)?.commitment(); + let note_storage_8_hash = NoteStorage::new(inputs_8)?.commitment(); let mut inputs_15 = word_1.to_vec(); inputs_15.extend_from_slice(&word_2.to_vec()); inputs_15.extend_from_slice(&word_3.to_vec()); inputs_15.extend_from_slice(&word_4[0..3]); - let note_inputs_15_hash = NoteInputs::new(inputs_15)?.commitment(); + let note_storage_15_hash = NoteStorage::new(inputs_15)?.commitment(); let mut expected_stack = alloc::vec::Vec::new(); - expected_stack.extend_from_slice(note_inputs_5_hash.as_elements()); - expected_stack.extend_from_slice(note_inputs_8_hash.as_elements()); - expected_stack.extend_from_slice(note_inputs_15_hash.as_elements()); + expected_stack.extend_from_slice(note_storage_5_hash.as_elements()); + expected_stack.extend_from_slice(note_storage_8_hash.as_elements()); + expected_stack.extend_from_slice(note_storage_15_hash.as_elements()); expected_stack.extend_from_slice(Word::empty().as_elements()); expected_stack.reverse(); @@ -380,12 +375,12 @@ async fn test_compute_inputs_commitment() -> anyhow::Result<()> { } #[tokio::test] -async fn test_build_metadata_header() -> miette::Result<()> { +async fn test_build_metadata_header() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); let sender = tx_context.account().id(); let receiver = AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE) - .map_err(|e| miette::miette!("Failed to convert account ID: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to convert account ID: {}", e))?; let test_metadata1 = NoteMetadata::new(sender, NoteType::Private, NoteTag::with_account_target(receiver)); @@ -435,13 +430,13 @@ pub async fn test_timelock() -> anyhow::Result<()> { use miden::protocol::tx begin - # store the note inputs to memory starting at address 0 - push.0 exec.active_note::get_inputs - # => [num_inputs, inputs_ptr] + # store the note storage to memory starting at address 0 + push.0 exec.active_note::get_storage + # => [num_storage_items, storage_ptr] - # make sure the number of inputs is 1 - eq.1 assert.err="number of note inputs is not 1" - # => [inputs_ptr] + # make sure the number of storage items is 1 + eq.1 assert.err="note number of storage items is not 1" + # => [storage_ptr] # read the timestamp at which the note can be consumed mem_load @@ -463,7 +458,7 @@ pub async fn test_timelock() -> anyhow::Result<()> { let lock_timestamp = 2_000_000_000; let source_manager = Arc::new(DefaultSourceManager::default()); let timelock_note = NoteBuilder::new(account.id(), &mut ChaCha20Rng::from_os_rng()) - .note_inputs([Felt::from(lock_timestamp)])? + .note_storage([Felt::from(lock_timestamp)])? .source_manager(source_manager.clone()) .code(code.clone()) .dynamically_linked_libraries(CodeBuilder::mock_libraries()) @@ -534,7 +529,7 @@ async fn test_public_key_as_note_input() -> anyhow::Result<()> { let vault = NoteAssets::new(vec![])?; let note_script = CodeBuilder::default().compile_note_script("begin nop end")?; let recipient = - NoteRecipient::new(serial_num, note_script, NoteInputs::new(public_key_value.to_vec())?); + NoteRecipient::new(serial_num, note_script, NoteStorage::new(public_key_value.to_vec())?); let note_with_pub_key = Note::new(vault.clone(), metadata, recipient); let tx_context = TransactionContextBuilder::new(target_account) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 262301743c..f71843b221 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -15,9 +15,9 @@ use miden_protocol::note::{ NoteAttachment, NoteAttachmentScheme, NoteExecutionHint, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, }; @@ -252,7 +252,7 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { let assets = NoteAssets::new(vec![input_asset_1])?; let metadata = NoteMetadata::new(tx_context.tx_inputs().account().id(), NoteType::Public, output_tag_1); - let inputs = NoteInputs::new(vec![])?; + let inputs = NoteStorage::new(vec![])?; let recipient = NoteRecipient::new(output_serial_no_1, input_note_1.script().clone(), inputs); let output_note_1 = Note::new(assets, metadata, recipient); @@ -267,7 +267,7 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { let metadata = NoteMetadata::new(tx_context.tx_inputs().account().id(), NoteType::Public, output_tag_2) .with_attachment(attachment); - let inputs = NoteInputs::new(vec![])?; + let inputs = NoteStorage::new(vec![])?; let recipient = NoteRecipient::new(output_serial_no_2, input_note_2.script().clone(), inputs); let output_note_2 = Note::new(assets, metadata, recipient); @@ -616,10 +616,10 @@ async fn test_build_recipient_hash() -> anyhow::Result<()> { let output_serial_no = Word::from([0, 1, 2, 3u32]); let tag = NoteTag::new(42 << 16 | 42); let single_input = 2; - let inputs = NoteInputs::new(vec![Felt::new(single_input)]).unwrap(); - let input_commitment = inputs.commitment(); + let storage = NoteStorage::new(vec![Felt::new(single_input)]).unwrap(); + let storage_commitment = storage.commitment(); - let recipient = NoteRecipient::new(output_serial_no, input_note_1.script().clone(), inputs); + let recipient = NoteRecipient::new(output_serial_no, input_note_1.script().clone(), storage); let code = format!( " use $kernel::prologue @@ -630,13 +630,13 @@ async fn test_build_recipient_hash() -> anyhow::Result<()> { begin exec.prologue::prepare_transaction - # input - push.{input_commitment} + # storage + push.{storage_commitment} # SCRIPT_ROOT push.{script_root} # SERIAL_NUM push.{output_serial_no} - # => [SERIAL_NUM, SCRIPT_ROOT, INPUT_COMMITMENT] + # => [SERIAL_NUM, SCRIPT_ROOT, STORAGE_COMMITMENT] exec.note::build_recipient_hash # => [RECIPIENT, pad(12)] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index 07ad25bf3d..2da5030531 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -44,7 +44,6 @@ use miden_protocol::transaction::memory::{ INPUT_NOTE_ASSETS_OFFSET, INPUT_NOTE_ATTACHMENT_OFFSET, INPUT_NOTE_ID_OFFSET, - INPUT_NOTE_INPUTS_COMMITMENT_OFFSET, INPUT_NOTE_METADATA_HEADER_OFFSET, INPUT_NOTE_NULLIFIER_SECTION_PTR, INPUT_NOTE_NUM_ASSETS_OFFSET, @@ -52,6 +51,7 @@ use miden_protocol::transaction::memory::{ INPUT_NOTE_SCRIPT_ROOT_OFFSET, INPUT_NOTE_SECTION_PTR, INPUT_NOTE_SERIAL_NUM_OFFSET, + INPUT_NOTE_STORAGE_COMMITMENT_OFFSET, INPUT_NOTES_COMMITMENT_PTR, KERNEL_PROCEDURES_PTR, NATIVE_ACCT_CODE_COMMITMENT_PTR, @@ -484,9 +484,9 @@ fn input_notes_memory_assertions( ); assert_eq!( - exec_output.get_note_mem_word(note_idx, INPUT_NOTE_INPUTS_COMMITMENT_OFFSET), - note.inputs().commitment(), - "note input commitment should be stored at the correct offset" + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_STORAGE_COMMITMENT_OFFSET), + note.storage().commitment(), + "note storage commitment should be stored at the correct offset" ); assert_eq!( diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index 6ff0536b97..576323f5ce 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -26,9 +26,9 @@ use miden_protocol::note::{ NoteAttachmentScheme, NoteHeader, NoteId, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, }; @@ -222,7 +222,7 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { // Create the expected output note for Note 2 which is public let serial_num_2 = Word::from([1, 2, 3, 4u32]); let note_script_2 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_CODE)?; - let inputs_2 = NoteInputs::new(vec![ONE])?; + let inputs_2 = NoteStorage::new(vec![ONE])?; let metadata_2 = NoteMetadata::new(account_id, note_type2, tag2).with_attachment(attachment2.clone()); let vault_2 = NoteAssets::new(vec![removed_asset_3, removed_asset_4])?; @@ -232,7 +232,7 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { // Create the expected output note for Note 3 which is public let serial_num_3 = Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]); let note_script_3 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_CODE)?; - let inputs_3 = NoteInputs::new(vec![ONE, Felt::new(2)])?; + let inputs_3 = NoteStorage::new(vec![ONE, Felt::new(2)])?; let metadata_3 = NoteMetadata::new(account_id, note_type3, tag3).with_attachment(attachment3.clone()); let vault_3 = NoteAssets::new(vec![])?; @@ -387,19 +387,19 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { assert_eq!(expected_output_note_3.id(), resulting_output_note_3.id()); assert_eq!(expected_output_note_3.assets(), resulting_output_note_3.assets().unwrap()); - // make sure that the number of note inputs remains the same + // make sure that the number of note storage items remains the same let resulting_note_2_recipient = resulting_output_note_2.recipient().expect("output note 2 is not full"); assert_eq!( - resulting_note_2_recipient.inputs().num_values(), - expected_output_note_2.inputs().num_values() + resulting_note_2_recipient.storage().num_items(), + expected_output_note_2.storage().num_items() ); let resulting_note_3_recipient = resulting_output_note_3.recipient().expect("output note 3 is not full"); assert_eq!( - resulting_note_3_recipient.inputs().num_values(), - expected_output_note_3.inputs().num_values() + resulting_note_3_recipient.storage().num_items(), + expected_output_note_3.storage().num_items() ); Ok(()) diff --git a/crates/miden-testing/src/lib.rs b/crates/miden-testing/src/lib.rs index fbbd4402b8..6763012635 100644 --- a/crates/miden-testing/src/lib.rs +++ b/crates/miden-testing/src/lib.rs @@ -17,7 +17,7 @@ pub use mock_chain::{ }; mod tx_context; -pub use tx_context::{TransactionContext, TransactionContextBuilder}; +pub use tx_context::{ExecError, TransactionContext, TransactionContextBuilder}; pub mod executor; @@ -27,3 +27,6 @@ pub mod utils; #[cfg(test)] mod kernel_tests; + +#[cfg(test)] +mod standards; diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index fe75c97fc4..8068147464 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -1094,7 +1094,12 @@ impl Serializable for AccountAuthenticator { fn write_into(&self, target: &mut W) { self.authenticator .as_ref() - .map(|auth| auth.keys().values().collect::>()) + .map(|auth| { + auth.keys() + .values() + .map(|(secret_key, public_key)| (secret_key, public_key.as_ref().clone())) + .collect::>() + }) .write_into(target); } } diff --git a/crates/miden-testing/src/standards/mod.rs b/crates/miden-testing/src/standards/mod.rs new file mode 100644 index 0000000000..288c3ee84c --- /dev/null +++ b/crates/miden-testing/src/standards/mod.rs @@ -0,0 +1 @@ +mod network_account_target; diff --git a/crates/miden-testing/src/standards/network_account_target.rs b/crates/miden-testing/src/standards/network_account_target.rs new file mode 100644 index 0000000000..9908377592 --- /dev/null +++ b/crates/miden-testing/src/standards/network_account_target.rs @@ -0,0 +1,130 @@ +//! Tests for the `miden::standards::attachments::network_account_target` module. + +use miden_protocol::Felt; +use miden_protocol::account::AccountStorageMode; +use miden_protocol::note::{NoteAttachment, NoteExecutionHint, NoteMetadata, NoteTag, NoteType}; +use miden_protocol::testing::account_id::AccountIdBuilder; +use miden_standards::note::NetworkAccountTarget; + +use crate::executor::CodeExecutor; + +#[tokio::test] +async fn network_account_target_get_id() -> anyhow::Result<()> { + let target_id = AccountIdBuilder::new() + .storage_mode(AccountStorageMode::Network) + .build_with_rng(&mut rand::rng()); + let exec_hint = NoteExecutionHint::Always; + + let attachment = NoteAttachment::from(NetworkAccountTarget::new(target_id, exec_hint)?); + let metadata = + NoteMetadata::new(target_id, NoteType::Public, NoteTag::with_account_target(target_id)) + .with_attachment(attachment.clone()); + let metadata_header = metadata.to_header_word(); + + let source = format!( + r#" + use miden::standards::attachments::network_account_target + use miden::protocol::note + + begin + push.{attachment_word} + push.{metadata_header} + exec.note::extract_attachment_info_from_metadata + # => [attachment_kind, attachment_scheme, NOTE_ATTACHMENT] + exec.network_account_target::get_id + # cleanup stack + movup.2 drop movup.2 drop + end + "#, + metadata_header = metadata_header, + attachment_word = attachment.content().to_word(), + ); + + let exec_output = CodeExecutor::with_default_host().run(&source).await?; + + assert_eq!(exec_output.stack[0], target_id.prefix().as_felt()); + assert_eq!(exec_output.stack[1], target_id.suffix()); + + Ok(()) +} + +#[tokio::test] +async fn network_account_target_new_attachment() -> anyhow::Result<()> { + let target_id = AccountIdBuilder::new() + .storage_mode(AccountStorageMode::Network) + .build_with_rng(&mut rand::rng()); + let exec_hint = NoteExecutionHint::Always; + + let attachment = NoteAttachment::from(NetworkAccountTarget::new(target_id, exec_hint)?); + let attachment_word = attachment.content().to_word(); + let expected_attachment_kind = Felt::from(attachment.attachment_kind().as_u8()); + + let source = format!( + r#" + use miden::standards::attachments::network_account_target + + begin + push.{exec_hint} + push.{target_id_suffix} + push.{target_id_prefix} + # => [target_id_prefix, target_id_suffix, exec_hint] + exec.network_account_target::new + # => [attachment_scheme, attachment_kind, ATTACHMENT, pad(16)] + + # cleanup stack + swapdw dropw dropw + end + "#, + target_id_prefix = target_id.prefix().as_felt(), + target_id_suffix = target_id.suffix(), + exec_hint = Felt::from(exec_hint), + ); + + let exec_output = CodeExecutor::with_default_host().run(&source).await?; + + assert_eq!(exec_output.stack[0], expected_attachment_kind); + assert_eq!( + exec_output.stack[1], + Felt::from(NetworkAccountTarget::ATTACHMENT_SCHEME.as_u32()) + ); + + assert_eq!(exec_output.stack.get_stack_word_be(2).unwrap(), attachment_word); + + Ok(()) +} + +#[tokio::test] +async fn network_account_target_attachment_round_trip() -> anyhow::Result<()> { + let target_id = AccountIdBuilder::new() + .storage_mode(AccountStorageMode::Network) + .build_with_rng(&mut rand::rng()); + let exec_hint = NoteExecutionHint::Always; + + let source = format!( + r#" + use miden::standards::attachments::network_account_target + + begin + push.{exec_hint} + push.{target_id_suffix} + push.{target_id_prefix} + # => [target_id_prefix, target_id_suffix, exec_hint] + exec.network_account_target::new + # => [attachment_scheme, attachment_kind, ATTACHMENT] + exec.network_account_target::get_id + # => [target_id_prefix, target_id_suffix] + movup.2 drop movup.2 drop + end + "#, + target_id_prefix = target_id.prefix().as_felt(), + target_id_suffix = target_id.suffix(), + exec_hint = Felt::from(exec_hint), + ); + + let exec_output = CodeExecutor::with_default_host().run(&source).await?; + + assert_eq!(exec_output.stack[0], target_id.prefix().as_felt()); + assert_eq!(exec_output.stack[1], target_id.suffix()); + + Ok(()) +} diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index 45711460c5..bffab5f566 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -4,7 +4,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_processor::fast::ExecutionOutput; -use miden_processor::{ExecutionError, FutureMaybeSend, MastForest, MastForestStore, Word}; +use miden_processor::{FutureMaybeSend, MastForest, MastForestStore, Word}; use miden_protocol::account::{ Account, AccountId, @@ -43,6 +43,7 @@ use miden_tx::{ use crate::executor::CodeExecutor; use crate::mock_host::MockHost; +use crate::tx_context::ExecError; // TRANSACTION CONTEXT // ================================================================================================ @@ -73,10 +74,6 @@ impl TransactionContext { /// is run on a modified [`TransactionExecutorHost`] which is loaded with the procedures exposed /// by the transaction kernel, and also individual kernel functions (not normally exposed). /// - /// To improve the error message quality, convert the returned [`ExecutionError`] into a - /// [`Report`](miden_protocol::assembly::diagnostics::Report) or use `?` with - /// [`miden_protocol::assembly::diagnostics::Result`]. - /// /// # Errors /// /// Returns an error if the assembly or execution of the provided code fails. @@ -84,7 +81,7 @@ impl TransactionContext { /// # Panics /// /// - If the provided `code` is not a valid program. - pub async fn execute_code(&self, code: &str) -> Result { + pub async fn execute_code(&self, code: &str) -> Result { // Fetch all witnesses for note assets and the fee asset. let mut asset_vault_keys = self .tx_inputs @@ -178,6 +175,7 @@ impl TransactionContext { .extend_advice_inputs(advice_inputs) .execute_program(program) .await + .map_err(ExecError::new) } /// Executes the transaction through a [TransactionExecutor] diff --git a/crates/miden-testing/src/tx_context/errors.rs b/crates/miden-testing/src/tx_context/errors.rs new file mode 100644 index 0000000000..c3f1a653d0 --- /dev/null +++ b/crates/miden-testing/src/tx_context/errors.rs @@ -0,0 +1,31 @@ +use alloc::string::ToString; + +use miden_processor::ExecutionError; +use miden_protocol::assembly::diagnostics::reporting::PrintDiagnostic; +use thiserror::Error; + +// EXECUTION ERROR +// ================================================================================================ + +/// A newtype wrapper around [`ExecutionError`] that provides better error messages +/// by using [`PrintDiagnostic`] for display formatting. +#[derive(Debug, Error)] +#[error("{}", PrintDiagnostic::new(.0).to_string())] +pub struct ExecError(pub ExecutionError); + +impl ExecError { + /// Creates a new `ExecError` from an `ExecutionError`. + pub fn new(error: ExecutionError) -> Self { + Self(error) + } + + /// Returns a reference to the inner `ExecutionError`. + pub fn as_execution_error(&self) -> &ExecutionError { + &self.0 + } + + /// Consumes `ExecError` and returns the inner `ExecutionError`. + pub fn into_execution_error(self) -> ExecutionError { + self.0 + } +} diff --git a/crates/miden-testing/src/tx_context/mod.rs b/crates/miden-testing/src/tx_context/mod.rs index 787c13d36e..30cb008889 100644 --- a/crates/miden-testing/src/tx_context/mod.rs +++ b/crates/miden-testing/src/tx_context/mod.rs @@ -1,5 +1,7 @@ mod builder; mod context; +mod errors; pub use builder::TransactionContextBuilder; pub use context::TransactionContext; +pub use errors::ExecError; diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 30aeb8fe76..1732ff3580 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -19,7 +19,7 @@ use rand::rngs::SmallRng; macro_rules! assert_execution_error { ($execution_result:expr, $expected_err:expr) => { match $execution_result { - Err(miden_processor::ExecutionError::FailedAssertion { label: _, source_file: _, clk: _, err_code, err_msg, err: _ }) => { + Err($crate::ExecError(miden_processor::ExecutionError::FailedAssertion { label: _, source_file: _, clk: _, err_code, err_msg, err: _ })) => { if let Some(ref msg) = err_msg { assert_eq!(msg.as_ref(), $expected_err.message(), "error messages did not match"); } diff --git a/crates/miden-testing/tests/agglayer/bridge_in.rs b/crates/miden-testing/tests/agglayer/bridge_in.rs index 81392b584d..c87bd0d77c 100644 --- a/crates/miden-testing/tests/agglayer/bridge_in.rs +++ b/crates/miden-testing/tests/agglayer/bridge_in.rs @@ -16,15 +16,15 @@ use miden_protocol::crypto::rand::FeltRng; use miden_protocol::note::{ Note, NoteAssets, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, }; use miden_protocol::transaction::OutputNote; use miden_standards::account::wallets::BasicWallet; -use miden_standards::note::WellKnownNote; +use miden_standards::note::StandardNote; use miden_testing::{AccountState, Auth, MockChain}; use rand::Rng; @@ -110,9 +110,9 @@ async fn test_bridge_in_claim_to_p2id() -> anyhow::Result<()> { }; // Create P2ID note for the user account (similar to network faucet test) - let p2id_script = WellKnownNote::P2ID.script(); + let p2id_script = StandardNote::P2ID.script(); let p2id_inputs = vec![user_account.id().suffix(), user_account.id().prefix().as_felt()]; - let note_inputs = NoteInputs::new(p2id_inputs)?; + let note_inputs = NoteStorage::new(p2id_inputs)?; let p2id_recipient = NoteRecipient::new(serial_num, p2id_script.clone(), note_inputs); let claim_note = create_claim_note(claim_params)?; diff --git a/crates/miden-testing/tests/agglayer/bridge_out.rs b/crates/miden-testing/tests/agglayer/bridge_out.rs index ab832bb50a..ee22a0a502 100644 --- a/crates/miden-testing/tests/agglayer/bridge_out.rs +++ b/crates/miden-testing/tests/agglayer/bridge_out.rs @@ -14,17 +14,17 @@ use miden_protocol::asset::{Asset, FungibleAsset}; use miden_protocol::note::{ Note, NoteAssets, - NoteInputs, NoteMetadata, NoteRecipient, NoteScript, + NoteStorage, NoteTag, NoteType, }; use miden_protocol::transaction::OutputNote; use miden_protocol::{Felt, Word}; use miden_standards::account::faucets::FungibleFaucetExt; -use miden_standards::note::WellKnownNote; +use miden_standards::note::StandardNote; use miden_testing::{AccountState, Auth, MockChain}; use rand::Rng; @@ -74,7 +74,7 @@ async fn test_bridge_out_consumes_b2agg_note() -> anyhow::Result<()> { // Get the B2AGG note script let b2agg_script = b2agg_script(); - // Create note inputs with destination network and address + // Create note storage with destination network and address // destination_network: u32 (AggLayer-assigned network ID) // destination_address: 20 bytes (Ethereum address) split into 5 u32 values let destination_network = Felt::new(1); // Example network ID @@ -83,11 +83,11 @@ async fn test_bridge_out_consumes_b2agg_note() -> anyhow::Result<()> { EthAddressFormat::from_hex(destination_address).expect("Valid Ethereum address"); let address_felts = eth_address.to_elements().to_vec(); - // Combine network ID and address felts into note inputs (6 felts total) + // Combine network ID and address felts into note storage (6 felts total) let mut input_felts = vec![destination_network]; input_felts.extend(address_felts); - let inputs = NoteInputs::new(input_felts.clone())?; + let inputs = NoteStorage::new(input_felts.clone())?; // Create the B2AGG note with assets from the faucet let b2agg_note_metadata = NoteMetadata::new(faucet.id(), note_type, tag); @@ -102,7 +102,7 @@ async fn test_bridge_out_consumes_b2agg_note() -> anyhow::Result<()> { let mut mock_chain = builder.build()?; // Get BURN note script to add to the transaction context - let burn_note_script: NoteScript = WellKnownNote::BURN.script(); + let burn_note_script: NoteScript = StandardNote::BURN.script(); // EXECUTE B2AGG NOTE AGAINST BRIDGE ACCOUNT (NETWORK TRANSACTION) // -------------------------------------------------------------------------------------------- @@ -232,18 +232,18 @@ async fn test_b2agg_note_reclaim_scenario() -> anyhow::Result<()> { // Get the B2AGG note script let b2agg_script = b2agg_script(); - // Create note inputs with destination network and address + // Create note storage with destination network and address let destination_network = Felt::new(1); let destination_address = "0x1234567890abcdef1122334455667788990011aa"; let eth_address = EthAddressFormat::from_hex(destination_address).expect("Valid Ethereum address"); let address_felts = eth_address.to_elements().to_vec(); - // Combine network ID and address felts into note inputs (6 felts total) + // Combine network ID and address felts into note storage (6 felts total) let mut input_felts = vec![destination_network]; input_felts.extend(address_felts); - let inputs = NoteInputs::new(input_felts.clone())?; + let inputs = NoteStorage::new(input_felts.clone())?; // Create the B2AGG note with the USER ACCOUNT as the sender // This is the key difference - the note sender will be the same as the consuming account diff --git a/crates/miden-testing/tests/lib.rs b/crates/miden-testing/tests/lib.rs index 04df2369c4..ac31d839cd 100644 --- a/crates/miden-testing/tests/lib.rs +++ b/crates/miden-testing/tests/lib.rs @@ -10,7 +10,7 @@ use miden_protocol::Word; use miden_protocol::account::AccountId; use miden_protocol::asset::FungibleAsset; use miden_protocol::crypto::utils::Serializable; -use miden_protocol::note::{Note, NoteAssets, NoteInputs, NoteMetadata, NoteRecipient, NoteType}; +use miden_protocol::note::{Note, NoteAssets, NoteMetadata, NoteRecipient, NoteStorage, NoteType}; use miden_protocol::testing::account_id::ACCOUNT_ID_SENDER; use miden_protocol::transaction::{ExecutedTransaction, ProvenTransaction}; use miden_standards::code_builder::CodeBuilder; @@ -63,7 +63,7 @@ pub fn get_note_with_fungible_asset_and_script( let vault = NoteAssets::new(vec![fungible_asset.into()]).unwrap(); let metadata = NoteMetadata::new(sender_id, NoteType::Public, 1.into()); - let inputs = NoteInputs::new(vec![]).unwrap(); + let inputs = NoteStorage::new(vec![]).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, inputs); Note::new(vault, metadata, recipient) diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index aaca772c55..13ca5f4c40 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -18,9 +18,9 @@ use miden_protocol::note::{ NoteAssets, NoteAttachment, NoteId, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, }; @@ -37,7 +37,7 @@ use miden_standards::errors::standards::{ ERR_FUNGIBLE_ASSET_DISTRIBUTE_WOULD_CAUSE_MAX_SUPPLY_TO_BE_EXCEEDED, ERR_SENDER_NOT_OWNER, }; -use miden_standards::note::{MintNoteInputs, WellKnownNote, create_burn_note, create_mint_note}; +use miden_standards::note::{MintNoteStorage, StandardNote, create_burn_note, create_mint_note}; use miden_standards::testing::note::NoteBuilder; use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; @@ -311,9 +311,9 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul let target_account_suffix = recipient_account_id.suffix(); let target_account_prefix = recipient_account_id.prefix().as_felt(); - // Use a length that is not a multiple of 8 (double word size) to make sure note inputs padding + // Use a length that is not a multiple of 8 (double word size) to make sure note storage padding // is correctly handled - let note_inputs = NoteInputs::new(vec![ + let note_storage = NoteStorage::new(vec![ target_account_suffix, target_account_prefix, Felt::new(0), @@ -324,7 +324,7 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul ])?; let note_recipient = - NoteRecipient::new(serial_num, output_note_script.clone(), note_inputs.clone()); + NoteRecipient::new(serial_num, output_note_script.clone(), note_storage.clone()); let output_script_root = note_recipient.script().root(); @@ -337,14 +337,14 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul use miden::protocol::note begin - # Build recipient hash from SERIAL_NUM, SCRIPT_ROOT, and INPUTS_COMMITMENT + # Build recipient hash from SERIAL_NUM, SCRIPT_ROOT, and STORAGE_COMMITMENT push.{script_root} # => [SCRIPT_ROOT] push.{serial_num} # => [SERIAL_NUM, SCRIPT_ROOT] - # Store note inputs in memory + # Store note storage in memory push.{input0} mem_store.0 push.{input1} mem_store.1 push.{input2} mem_store.2 @@ -354,7 +354,7 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul push.{input6} mem_store.6 push.7 push.0 - # => [inputs_ptr, num_inputs = 7, SERIAL_NUM, SCRIPT_ROOT] + # => [storage_ptr, num_storage_items = 7, SERIAL_NUM, SCRIPT_ROOT] exec.note::build_recipient # => [RECIPIENT] @@ -373,13 +373,13 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul end ", note_type = note_type as u8, - input0 = note_inputs.values()[0], - input1 = note_inputs.values()[1], - input2 = note_inputs.values()[2], - input3 = note_inputs.values()[3], - input4 = note_inputs.values()[4], - input5 = note_inputs.values()[5], - input6 = note_inputs.values()[6], + input0 = note_storage.items()[0], + input1 = note_storage.items()[1], + input2 = note_storage.items()[2], + input3 = note_storage.items()[3], + input4 = note_storage.items()[4], + input5 = note_storage.items()[5], + input6 = note_storage.items()[6], script_root = output_script_root, serial_num = serial_num, tag = u32::from(tag), @@ -430,16 +430,16 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul // Verify the note was created by the faucet assert_eq!(full_note.metadata().sender(), faucet.id()); - // Verify the note inputs commitment matches the expected commitment + // Verify the note storage commitment matches the expected commitment assert_eq!( - full_note.recipient().inputs().commitment(), - note_inputs.commitment(), - "Output note inputs commitment should match expected inputs commitment" + full_note.recipient().storage().commitment(), + note_storage.commitment(), + "Output note storage commitment should match expected storage commitment" ); assert_eq!( - full_note.recipient().inputs().num_values(), - note_inputs.num_values(), - "Output note inputs length should match expected inputs length" + full_note.recipient().storage().num_items(), + note_storage.num_items(), + "Output note number of storage items should match expected number of storage items" ); // Verify the output note ID matches the expected note ID @@ -509,13 +509,13 @@ async fn network_faucet_mint() -> anyhow::Result<()> { let recipient = p2id_mint_output_note.recipient().digest(); // Create the MINT note using the helper function - let mint_inputs = MintNoteInputs::new_private(recipient, amount, output_note_tag.into()); + let mint_storage = MintNoteStorage::new_private(recipient, amount, output_note_tag.into()); let mut rng = RpoRandomCoin::new([Felt::from(42u32); 4].into()); let mint_note = create_mint_note( faucet.id(), faucet_owner_account_id, - mint_inputs, + mint_storage, NoteAttachment::default(), &mut rng, )?; @@ -595,7 +595,7 @@ async fn test_network_faucet_owner_can_mint() -> anyhow::Result<()> { )?; let recipient = p2id_note.recipient().digest(); - let mint_inputs = MintNoteInputs::new_private(recipient, amount, output_note_tag.into()); + let mint_inputs = MintNoteStorage::new_private(recipient, amount, output_note_tag.into()); let mut rng = RpoRandomCoin::new([Felt::from(42u32); 4].into()); let mint_note = create_mint_note( @@ -650,7 +650,7 @@ async fn test_network_faucet_non_owner_cannot_mint() -> anyhow::Result<()> { )?; let recipient = p2id_note.recipient().digest(); - let mint_inputs = MintNoteInputs::new_private(recipient, amount, output_note_tag.into()); + let mint_inputs = MintNoteStorage::new_private(recipient, amount, output_note_tag.into()); // Create mint note from NON-OWNER let mut rng = RpoRandomCoin::new([Felt::from(42u32); 4].into()); @@ -737,7 +737,7 @@ async fn test_network_faucet_transfer_ownership() -> anyhow::Result<()> { let recipient = p2id_note.recipient().digest(); // Sanity Check: Prove that the initial owner can mint assets - let mint_inputs = MintNoteInputs::new_private(recipient, amount, output_note_tag.into()); + let mint_inputs = MintNoteStorage::new_private(recipient, amount, output_note_tag.into()); let mut rng = RpoRandomCoin::new([Felt::from(42u32); 4].into()); let mint_note = create_mint_note( @@ -1155,29 +1155,28 @@ async fn test_mint_note_output_note_types(#[case] note_type: NoteType) -> anyhow .unwrap(); // Create MINT note based on note type - let mint_inputs = match note_type { + let mint_storage = match note_type { NoteType::Private => { let output_note_tag = NoteTag::with_account_target(target_account.id()); let recipient = p2id_mint_output_note.recipient().digest(); - MintNoteInputs::new_private(recipient, amount, output_note_tag.into()) + MintNoteStorage::new_private(recipient, amount, output_note_tag.into()) }, NoteType::Public => { let output_note_tag = NoteTag::with_account_target(target_account.id()); - let p2id_script = WellKnownNote::P2ID.script(); - let p2id_inputs = + let p2id_script = StandardNote::P2ID.script(); + let p2id_storage = vec![target_account.id().suffix(), target_account.id().prefix().as_felt()]; - let note_inputs = NoteInputs::new(p2id_inputs)?; - let recipient = NoteRecipient::new(serial_num, p2id_script, note_inputs); - MintNoteInputs::new_public(recipient, amount, output_note_tag.into())? + let note_storage = NoteStorage::new(p2id_storage)?; + let recipient = NoteRecipient::new(serial_num, p2id_script, note_storage); + MintNoteStorage::new_public(recipient, amount, output_note_tag.into())? }, - NoteType::Encrypted => unreachable!("Encrypted note type not used in this test"), }; let mut rng = RpoRandomCoin::new([Felt::from(42u32); 4].into()); let mint_note = create_mint_note( faucet.id(), faucet_owner_account_id, - mint_inputs.clone(), + mint_storage.clone(), NoteAttachment::default(), &mut rng, )?; @@ -1189,7 +1188,7 @@ async fn test_mint_note_output_note_types(#[case] note_type: NoteType) -> anyhow mock_chain.build_tx_context(faucet.id(), &[mint_note.id()], &[])?; if note_type == NoteType::Public { - let p2id_script = WellKnownNote::P2ID.script(); + let p2id_script = StandardNote::P2ID.script(); tx_context_builder = tx_context_builder.add_note_script(p2id_script); } @@ -1215,7 +1214,6 @@ async fn test_mint_note_output_note_types(#[case] note_type: NoteType) -> anyhow assert_eq!(created_note, &p2id_mint_output_note); }, - NoteType::Encrypted => unreachable!("Encrypted note type not used in this test"), } mock_chain.add_pending_executed_transaction(&executed_transaction)?; diff --git a/crates/miden-testing/tests/scripts/send_note.rs b/crates/miden-testing/tests/scripts/send_note.rs index f80a4c5a06..0655f4bdd5 100644 --- a/crates/miden-testing/tests/scripts/send_note.rs +++ b/crates/miden-testing/tests/scripts/send_note.rs @@ -8,9 +8,9 @@ use miden_protocol::note::{ NoteAssets, NoteAttachment, NoteAttachmentScheme, - NoteInputs, NoteMetadata, NoteRecipient, + NoteStorage, NoteTag, NoteType, PartialNote, @@ -44,7 +44,7 @@ async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { let assets = NoteAssets::new(vec![sent_asset]).unwrap(); let note_script = CodeBuilder::default().compile_note_script("begin nop end").unwrap(); let serial_num = RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); - let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); + let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let note = Note::new(assets.clone(), metadata, recipient); let partial_note: PartialNote = note.clone().into(); @@ -56,9 +56,6 @@ async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { let executed_transaction = mock_chain .build_tx_context(sender_basic_wallet_account.id(), &[], &[]) .expect("failed to build tx context") - // TODO: This shouldn't be necessary. The attachment should be included in the tx - // script's mast forest's advice map. - .extend_advice_map(vec![(attachment.content().to_word(), elements)]) .tx_script(send_note_transaction_script) .extend_expected_output_notes(vec![OutputNote::Full(note.clone())]) .build()? @@ -107,7 +104,7 @@ async fn test_send_note_script_basic_fungible_faucet() -> anyhow::Result<()> { )])?; let note_script = CodeBuilder::default().compile_note_script("begin nop end").unwrap(); let serial_num = RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); - let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); + let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let note = Note::new(assets.clone(), metadata, recipient); let partial_note: PartialNote = note.clone().into(); diff --git a/crates/miden-tx/README.md b/crates/miden-tx/README.md index 9808957a3d..52edc143ad 100644 --- a/crates/miden-tx/README.md +++ b/crates/miden-tx/README.md @@ -6,7 +6,7 @@ This crate contains tool for creating, executing, and proving Miden blockchain t This crate exposes a few components to compile, run, and prove transactions. -The first requirement is to have a `DataStore` implementation. `DataStore` objects are responsible to load the data needed by the transactions executor, especially the account's code, the reference block data, and the note's inputs. +The first requirement is to have a `DataStore` implementation. `DataStore` objects are responsible to load the data needed by the transactions executor, especially the account's code, the reference block data, and the note's storage. ```rust let store = DataStore:new(); diff --git a/crates/miden-tx/src/auth/tx_authenticator.rs b/crates/miden-tx/src/auth/tx_authenticator.rs index 4845421445..ccd5f73b30 100644 --- a/crates/miden-tx/src/auth/tx_authenticator.rs +++ b/crates/miden-tx/src/auth/tx_authenticator.rs @@ -1,6 +1,7 @@ use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::string::ToString; +use alloc::sync::Arc; use alloc::vec::Vec; use miden_processor::FutureMaybeSend; @@ -145,7 +146,7 @@ pub trait TransactionAuthenticator { fn get_public_key( &self, pub_key_commitment: PublicKeyCommitment, - ) -> impl FutureMaybeSend>; + ) -> impl FutureMaybeSend>>; } /// A placeholder type for the generic trait bound of `TransactionAuthenticator<'_,'_,_,T>` @@ -171,7 +172,7 @@ impl TransactionAuthenticator for UnreachableAuth { fn get_public_key( &self, _pub_key_commitment: PublicKeyCommitment, - ) -> impl FutureMaybeSend> { + ) -> impl FutureMaybeSend>> { async { unreachable!("Type `UnreachableAuth` must not be instantiated") } } } @@ -183,7 +184,7 @@ impl TransactionAuthenticator for UnreachableAuth { #[derive(Clone, Debug)] pub struct BasicAuthenticator { /// pub_key |-> (secret_key, public_key) mapping - keys: BTreeMap, + keys: BTreeMap)>, } impl BasicAuthenticator { @@ -191,7 +192,7 @@ impl BasicAuthenticator { let mut key_map = BTreeMap::new(); for secret_key in keys { let pub_key = secret_key.public_key(); - key_map.insert(pub_key.to_commitment(), (secret_key.clone(), pub_key)); + key_map.insert(pub_key.to_commitment(), (secret_key.clone(), pub_key.into())); } BasicAuthenticator { keys: key_map } @@ -200,7 +201,10 @@ impl BasicAuthenticator { pub fn from_key_pairs(keys: &[(AuthSecretKey, PublicKey)]) -> Self { let mut key_map = BTreeMap::new(); for (secret_key, public_key) in keys { - key_map.insert(public_key.to_commitment(), (secret_key.clone(), public_key.clone())); + key_map.insert( + public_key.to_commitment(), + (secret_key.clone(), public_key.clone().into()), + ); } BasicAuthenticator { keys: key_map } @@ -210,7 +214,7 @@ impl BasicAuthenticator { /// /// Map keys represent the public key commitments, and values represent the (secret_key, /// public_key) pair that the authenticator would use to sign messages. - pub fn keys(&self) -> &BTreeMap { + pub fn keys(&self) -> &BTreeMap)> { &self.keys } } @@ -244,12 +248,12 @@ impl TransactionAuthenticator for BasicAuthenticator { fn get_public_key( &self, pub_key_commitment: PublicKeyCommitment, - ) -> impl FutureMaybeSend> { - async move { self.keys.get(&pub_key_commitment).map(|(_, pub_key)| pub_key) } + ) -> impl FutureMaybeSend>> { + async move { self.keys.get(&pub_key_commitment).map(|(_, pub_key)| pub_key.clone()) } } } -// HELPER FUNCTIONS +// EMPTY AUTHENTICATOR // ================================================================================================ impl TransactionAuthenticator for () { @@ -269,11 +273,14 @@ impl TransactionAuthenticator for () { fn get_public_key( &self, _pub_key_commitment: PublicKeyCommitment, - ) -> impl FutureMaybeSend> { + ) -> impl FutureMaybeSend>> { async { None } } } +// TESTS +// ================================================================================================ + #[cfg(test)] mod test { use miden_protocol::account::auth::AuthSecretKey; diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index 0e0cc1dda2..4eae85f935 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -17,6 +17,7 @@ use miden_protocol::errors::{ NoteError, ProvenTransactionError, TransactionInputError, + TransactionInputsExtractionError, TransactionOutputError, }; use miden_protocol::note::{NoteId, NoteMetadata}; @@ -72,6 +73,8 @@ impl From for TransactionExecutorError { #[derive(Debug, Error)] pub enum TransactionExecutorError { + #[error("failed to read fee asset from transaction inputs")] + FeeAssetRetrievalFailed(#[source] TransactionInputsExtractionError), #[error("failed to fetch transaction inputs from the data store")] FetchTransactionInputsFailed(#[source] DataStoreError), #[error("failed to fetch asset witnesses from the data store")] @@ -204,8 +207,8 @@ pub enum TransactionKernelError { AccountDeltaRemoveAssetFailed(#[source] AccountDeltaError), #[error("failed to add asset to note")] FailedToAddAssetToNote(#[source] NoteError), - #[error("note input data has hash {actual} but expected hash {expected}")] - InvalidNoteInputs { expected: Word, actual: Word }, + #[error("note storage has commitment {actual} but expected commitment {expected}")] + InvalidNoteStorage { expected: Word, actual: Word }, #[error( "failed to respond to signature requested since no authenticator is assigned to the host" )] @@ -222,9 +225,9 @@ pub enum TransactionKernelError { source: AssetError, }, #[error( - "note inputs data extracted from the advice map by the event handler is not well formed" + "note storage data extracted from the advice map by the event handler is not well formed" )] - MalformedNoteInputs(#[source] NoteError), + MalformedNoteStorage(#[source] NoteError), #[error( "note script data `{data:?}` extracted from the advice map by the event handler is not well formed" )] @@ -247,9 +250,9 @@ pub enum TransactionKernelError { )] NoteAttachmentArrayMismatch { actual: Word, provided: Word }, #[error( - "note input data in advice provider contains fewer elements ({actual}) than specified ({specified}) by its inputs length" + "note storage in advice provider contains fewer items ({actual}) than specified ({specified}) by its number of storage items" )] - TooFewElementsForNoteInputs { specified: u64, actual: u64 }, + TooFewElementsForNoteStorage { specified: u64, actual: u64 }, #[error("account procedure with procedure root {0} is not in the account procedure index map")] UnknownAccountProcedure(Word), #[error("code commitment {0} is not in the account procedure index map")] diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 1679190c92..90905d37db 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -26,7 +26,7 @@ use miden_protocol::assembly::{SourceFile, SourceManagerSync, SourceSpan}; use miden_protocol::asset::{AssetVaultKey, AssetWitness, FungibleAsset}; use miden_protocol::block::BlockNumber; use miden_protocol::crypto::merkle::smt::SmtProof; -use miden_protocol::note::{NoteInputs, NoteMetadata, NoteRecipient}; +use miden_protocol::note::{NoteMetadata, NoteRecipient, NoteStorage}; use miden_protocol::transaction::{ InputNote, InputNotes, @@ -375,7 +375,7 @@ where recipient_digest: Word, script_root: Word, metadata: NoteMetadata, - note_inputs: NoteInputs, + note_storage: NoteStorage, serial_num: Word, ) -> Result, TransactionKernelError> { let note_script_result = self.base_host.store().get_note_script(script_root).await; @@ -383,7 +383,7 @@ where match note_script_result { Ok(Some(note_script)) => { let script_felts: Vec = (¬e_script).into(); - let recipient = NoteRecipient::new(serial_num, note_script, note_inputs); + let recipient = NoteRecipient::new(serial_num, note_script, note_storage); if recipient.digest() != recipient_digest { return Err(TransactionKernelError::other(format!( @@ -581,14 +581,14 @@ where recipient_digest, serial_num, script_root, - note_inputs, + note_storage, } => { self.on_note_script_requested( note_idx, recipient_digest, script_root, metadata, - note_inputs, + note_storage, serial_num, ) .await diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index eaa57746da..1dc2378404 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -272,24 +272,34 @@ where .await .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?; - // Add the vault key for the fee asset to the list of asset vault keys which will need to be - // accessed at the end of the transaction. + let native_account_vault_root = account.vault().root(); let fee_asset_vault_key = AssetVaultKey::from_account_id(block_header.fee_parameters().native_asset_id()) .expect("fee asset should be a fungible asset"); + + let mut tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes) + .map_err(TransactionExecutorError::InvalidTransactionInputs)? + .with_tx_args(tx_args); + + // Add the vault key for the fee asset to the list of asset vault keys which will need to be + // accessed at the end of the transaction. asset_vault_keys.insert(fee_asset_vault_key); - // Fetch the witnesses for all asset vault keys. - let asset_witnesses = self - .data_store - .get_vault_asset_witnesses(account_id, account.vault().root(), asset_vault_keys) - .await - .map_err(TransactionExecutorError::FetchAssetWitnessFailed)?; + // filter out any asset vault keys for which we already have witnesses in the advice inputs + asset_vault_keys.retain(|asset_key| { + !tx_inputs.has_vault_asset_witness(native_account_vault_root, asset_key) + }); - let tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes) - .map_err(TransactionExecutorError::InvalidTransactionInputs)? - .with_tx_args(tx_args) - .with_asset_witnesses(asset_witnesses); + // if any of the witnesses are missing, fetch them from the data store and add to tx_inputs + if !asset_vault_keys.is_empty() { + let asset_witnesses = self + .data_store + .get_vault_asset_witnesses(account_id, native_account_vault_root, asset_vault_keys) + .await + .map_err(TransactionExecutorError::FetchAssetWitnessFailed)?; + + tx_inputs = tx_inputs.with_asset_witnesses(asset_witnesses); + } Ok(tx_inputs) } @@ -327,25 +337,23 @@ where AccountProcedureIndexMap::new([tx_inputs.account().code()]); let initial_fee_asset_balance = { + let vault_root = tx_inputs.account().vault().root(); let native_asset_id = tx_inputs.block_header().fee_parameters().native_asset_id(); let fee_asset_vault_key = AssetVaultKey::from_account_id(native_asset_id) .expect("fee asset should be a fungible asset"); - let fee_asset_witness = tx_inputs - .asset_witnesses() - .iter() - .find_map(|witness| witness.find(fee_asset_vault_key)); - - match fee_asset_witness { + let fee_asset = tx_inputs + .read_vault_asset(vault_root, fee_asset_vault_key) + .map_err(TransactionExecutorError::FeeAssetRetrievalFailed)?; + match fee_asset { Some(Asset::Fungible(fee_asset)) => fee_asset.amount(), Some(Asset::NonFungible(_)) => { return Err(TransactionExecutorError::FeeAssetMustBeFungible); }, - // If the witness does not contain the asset, its balance is zero. + // If the asset was not found, its balance is zero. None => 0, } }; - let host = TransactionExecutorHost::new( tx_inputs.account(), input_notes.clone(), diff --git a/crates/miden-tx/src/executor/notes_checker.rs b/crates/miden-tx/src/executor/notes_checker.rs index 9741e79824..e1ce83ff7f 100644 --- a/crates/miden-tx/src/executor/notes_checker.rs +++ b/crates/miden-tx/src/executor/notes_checker.rs @@ -13,7 +13,7 @@ use miden_protocol::transaction::{ TransactionKernel, }; use miden_prover::AdviceInputs; -use miden_standards::note::{NoteConsumptionStatus, WellKnownNote}; +use miden_standards::note::{NoteConsumptionStatus, StandardNote}; use super::TransactionExecutor; use crate::auth::TransactionAuthenticator; @@ -120,8 +120,8 @@ where if num_notes == 0 || num_notes > MAX_NUM_CHECKER_NOTES { return Err(NoteCheckerError::InputNoteCountOutOfRange(num_notes)); } - // Ensure well-known notes are ordered first. - notes.sort_unstable_by_key(|note| WellKnownNote::from_note(note).is_none()); + // Ensure standard notes are ordered first. + notes.sort_unstable_by_key(|note| StandardNote::from_note(note).is_none()); let notes = InputNotes::from(notes); let tx_inputs = self @@ -152,10 +152,10 @@ where note: InputNote, tx_args: TransactionArgs, ) -> Result { - // return the consumption status if we manage to determine it from the well-known note - if let Some(well_known_note) = WellKnownNote::from_note(note.note()) + // Return the consumption status if we manage to determine it from the standard note + if let Some(standard_note) = StandardNote::from_note(note.note()) && let Some(consumption_status) = - well_known_note.is_consumable(note.note(), target_account_id, block_ref) + standard_note.is_consumable(note.note(), target_account_id, block_ref) { return Ok(consumption_status); } diff --git a/crates/miden-tx/src/host/kernel_process.rs b/crates/miden-tx/src/host/kernel_process.rs index 5d948de35a..a79c65e516 100644 --- a/crates/miden-tx/src/host/kernel_process.rs +++ b/crates/miden-tx/src/host/kernel_process.rs @@ -1,6 +1,6 @@ use miden_processor::{ExecutionError, Felt, ProcessState}; use miden_protocol::account::{AccountId, StorageSlotId, StorageSlotType}; -use miden_protocol::note::{NoteId, NoteInputs}; +use miden_protocol::note::{NoteId, NoteStorage}; use miden_protocol::transaction::memory::{ ACCOUNT_STACK_TOP_PTR, ACCT_CODE_COMMITMENT_OFFSET, @@ -47,12 +47,12 @@ pub(super) trait TransactionKernelProcess { fn read_note_recipient_info_from_adv_map( &self, recipient_digest: Word, - ) -> Result<(NoteInputs, Word, Word), TransactionKernelError>; + ) -> Result<(NoteStorage, Word, Word), TransactionKernelError>; - fn read_note_inputs_from_adv_map( + fn read_note_storage_from_adv_map( &self, - inputs_commitment: &Word, - ) -> Result; + storage_commitment: &Word, + ) -> Result; fn has_advice_map_entry(&self, key: Word) -> bool; @@ -254,52 +254,53 @@ impl<'a> TransactionKernelProcess for ProcessState<'a> { fn read_note_recipient_info_from_adv_map( &self, recipient_digest: Word, - ) -> Result<(NoteInputs, Word, Word), TransactionKernelError> { - let (sn_script_hash, inputs_commitment) = + ) -> Result<(NoteStorage, Word, Word), TransactionKernelError> { + let (sn_script_hash, storage_commitment) = read_double_word_from_adv_map(self, recipient_digest)?; let (sn_hash, script_root) = read_double_word_from_adv_map(self, sn_script_hash)?; let (serial_num, _) = read_double_word_from_adv_map(self, sn_hash)?; - let inputs = self.read_note_inputs_from_adv_map(&inputs_commitment)?; + let inputs = self.read_note_storage_from_adv_map(&storage_commitment)?; Ok((inputs, script_root, serial_num)) } - /// Extracts and validates note inputs from the advice provider. - fn read_note_inputs_from_adv_map( + /// Extracts and validates note storage from the advice provider. + fn read_note_storage_from_adv_map( &self, - inputs_commitment: &Word, - ) -> Result { - let inputs_data = self.advice_provider().get_mapped_values(inputs_commitment); + storage_commitment: &Word, + ) -> Result { + let inputs_data = self.advice_provider().get_mapped_values(storage_commitment); match inputs_data { - None => Ok(NoteInputs::default()), - Some(inputs) => { - let inputs_commitment_hash = Hasher::hash_elements(inputs_commitment.as_elements()); - let num_inputs = self + None => Ok(NoteStorage::default()), + Some(storage_items) => { + let storage_commitment_hash = + Hasher::hash_elements(storage_commitment.as_elements()); + let num_storage_items = self .advice_provider() - .get_mapped_values(&inputs_commitment_hash) + .get_mapped_values(&storage_commitment_hash) .ok_or_else(|| { TransactionKernelError::other( - "expected num_inputs to be present in advice provider", + "expected num_storage_items to be present in advice provider", ) })?; - if num_inputs.len() != 1 { + if num_storage_items.len() != 1 { return Err(TransactionKernelError::other( - "expected num_inputs advice entry to contain exactly one element", + "expected num_storage_items advice entry to contain exactly one element", )); } - let num_inputs = num_inputs[0].as_int() as usize; + let num_storage_items = num_storage_items[0].as_int() as usize; - let note_inputs = NoteInputs::new(inputs[0..num_inputs].to_vec()) - .map_err(TransactionKernelError::MalformedNoteInputs)?; + let note_storage = NoteStorage::new(storage_items[0..num_storage_items].to_vec()) + .map_err(TransactionKernelError::MalformedNoteStorage)?; - if ¬e_inputs.commitment() == inputs_commitment { - Ok(note_inputs) + if ¬e_storage.commitment() == storage_commitment { + Ok(note_storage) } else { - Err(TransactionKernelError::InvalidNoteInputs { - expected: *inputs_commitment, - actual: note_inputs.commitment(), + Err(TransactionKernelError::InvalidNoteStorage { + expected: *storage_commitment, + actual: note_storage.commitment(), }) } }, diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index 760a6aff4e..d6fe1b73d6 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -10,10 +10,10 @@ use miden_protocol::note::{ NoteAttachmentKind, NoteAttachmentScheme, NoteId, - NoteInputs, NoteMetadata, NoteRecipient, NoteScript, + NoteStorage, NoteTag, NoteType, }; @@ -359,7 +359,7 @@ impl TransactionEvent { // try to read the full recipient from the advice provider let recipient_data = if process.has_advice_map_entry(recipient_digest) { - let (note_inputs, script_root, serial_num) = + let (note_storage, script_root, serial_num) = process.read_note_recipient_info_from_adv_map(recipient_digest)?; let note_script = process @@ -378,7 +378,7 @@ impl TransactionEvent { match note_script { Some(note_script) => { let recipient = - NoteRecipient::new(serial_num, note_script, note_inputs); + NoteRecipient::new(serial_num, note_script, note_storage); if recipient.digest() != recipient_digest { return Err(TransactionKernelError::other(format!( @@ -393,7 +393,7 @@ impl TransactionEvent { recipient_digest, serial_num, script_root, - note_inputs, + note_storage, }, } } else { @@ -557,7 +557,7 @@ pub(crate) enum RecipientData { recipient_digest: Word, serial_num: Word, script_root: Word, - note_inputs: NoteInputs, + note_storage: NoteStorage, }, } diff --git a/docs/src/note.md b/docs/src/note.md index b9ee08d714..99973c9f93 100644 --- a/docs/src/note.md +++ b/docs/src/note.md @@ -22,7 +22,7 @@ These components are: 1. [Assets](#assets) 2. [Script](#script) -3. [Inputs](#inputs) +3. [Storage](#storage) 4. [Serial number](#serial-number) 5. [Metadata](#metadata) @@ -42,13 +42,13 @@ The code executed when the `Note` is consumed. Each `Note` has a script that defines the conditions under which it can be consumed. When accounts consume notes in transactions, `Note` scripts call the account’s interface functions. This enables all sorts of operations beyond simple asset transfers. The Miden VM’s Turing completeness allows for arbitrary logic, making `Note` scripts highly versatile. There is no limit to the amount of code a `Note` can hold. -### Inputs +### Storage :::note -Arguments passed to the `Note` script during execution. +The storage of the `Note` that it can access during execution. ::: -A `Note` can have up to 128 input values, which adds up to a maximum of 1 KB of data. The `Note` script can access these inputs. They can convey arbitrary parameters for `Note` consumption. +A `Note` can store up to 1024 items in its storage, which adds up to a maximum of 8 KB of data. The `Note` script can access storage during execution and it is used to parameterize a note's script. For instance, a P2ID note stores the ID of the target account that can consume the note. This makes the P2ID note script reusable by changing the target account ID. ### Serial number @@ -82,7 +82,7 @@ Example use cases for attachments are: - Communicate the note details of a private note in encrypted form. This means the encrypted note is attached publicly to the otherwise private note. - For [network transactions](./transaction.md#network-transaction), encode the ID of the network account that should consume the note. This is a standardized attachment scheme in miden-standards called `NetworkAccountTarget`. -- Communicate the details of a _private_ note to the receiver so they can derive the note. For example, the payback note of a partially fillable swap note can be private and the receiver already knows a few details: It is a P2ID note, the serial number is derived from the SWAP note's serial number and the note inputs are the account ID of the receiver. The receiver only needs to now the exact amount that was filled to derive the full note for consumption. This amount can be encoded in the public attachment of the payback note, which allows this use case to work with private notes and still not require a side-channel. +- Communicate the details of a _private_ note to the receiver so they can derive the note. For example, the payback note of a partially fillable swap note can be private and the receiver already knows a few details: It is a P2ID note, the serial number is derived from the SWAP note's serial number and the note storage is the account ID of the receiver. The receiver only needs to now the exact amount that was filled to derive the full note for consumption. This amount can be encoded in the public attachment of the payback note, which allows this use case to work with private notes and still not require a side-channel. ## Note Lifecycle @@ -139,7 +139,7 @@ Using `Note` tags strikes a balance between privacy and efficiency. Without tags ### Note consumption -To consume a `Note`, the consumer must know its data, including the inputs needed to compute the nullifier. Consumption occurs as part of a transaction. Upon successful consumption a nullifier is generated for the consumed notes. +To consume a `Note`, the consumer must know its data, including the note's storage which is needed to compute the nullifier. Consumption occurs as part of a transaction. Upon successful consumption a nullifier is generated for the consumed notes. Upon successful verification of the transaction: @@ -151,7 +151,7 @@ Upon successful verification of the transaction: Consumption of a `Note` can be restricted to certain accounts or entities. For instance, the P2ID and P2IDE `Note` scripts target a specific account ID. Alternatively, Miden defines a RECIPIENT (represented as 32 bytes) computed as: ```arduino -hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment) +hash(hash(hash(serial_num, [0; 4]), script_root), storage_commitment) ``` Only those who know the RECIPIENT’s pre-image can consume the `Note`. For private notes, this ensures an additional layer of control and privacy, as only parties with the correct data can claim the `Note`. @@ -165,14 +165,14 @@ For a practical example, refer to the [SWAP note script](https://github.com/0xMi The `Note` nullifier, computed as: ```arduino -hash(serial_num, script_root, input_commitment, vault_hash) +hash(serial_num, script_root, storage_commitment, vault_hash) ``` This achieves the following properties: - Every `Note` can be reduced to a single unique nullifier. - One cannot derive a note's hash from its nullifier. -- To compute the nullifier, one must know all components of the `Note`: serial_num, script_root, input_commitment, and vault_hash. +- To compute the nullifier, one must know all components of the `Note`: serial_num, script_root, storage_commitment, and vault_hash. That means if a `Note` is private and the operator stores only the note's hash, only those with the `Note` details know if this `Note` has been consumed already. Zcash first [introduced](https://zcash.github.io/orchard/design/nullifiers.html#nullifiers) this approach. @@ -191,7 +191,7 @@ The P2ID note script implements a simple pay-to-account-ID pattern. It adds all **Key characteristics:** - **Purpose:** Direct asset transfer to a specific account ID -- **Inputs:** Requires exactly 2 note inputs containing the target account ID +- **Storage:** Requires exactly 2 storage items containing the target account ID - **Validation:** Ensures the consuming account's ID matches the target account ID specified in the note - **Requirements:** Target account must expose the `miden::standards::wallets::basic::receive_asset` procedure @@ -204,7 +204,7 @@ The P2IDE note script extends P2ID with additional features including time-locki **Key characteristics:** - **Purpose:** Advanced asset transfer with time-lock and reclaim capabilities -- **Inputs:** Requires exactly 4 note inputs: +- **Storage:** Requires exactly 4 storage items: - Target account ID - Reclaim block height (when sender can reclaim) - Time-lock block height (when target can consume) @@ -226,7 +226,7 @@ The SWAP note script implements atomic asset swapping functionality. **Key characteristics:** - **Purpose:** Atomic asset exchange between two parties -- **Inputs:** Requires exactly 16 note inputs specifying: +- **Storage:** Requires exactly 16 storage items specifying: - Requested asset details - Payback note recipient information - Note creation parameters (type, tag, attachment) diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index d9cef7f02e..40f09ea641 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -76,7 +76,7 @@ Active note procedures can be used to fetch data from the note that is currently | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `get_assets` | Writes the [assets](note.md#assets) of the active note into memory starting at the specified address.

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

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

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

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

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

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

**Inputs:** `[]`
**Outputs:** `[SERIAL_NUMBER]` | Note | @@ -93,7 +93,7 @@ Input note procedures can be used to fetch data on input notes consumed by the t | `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the input note with the specified index.

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

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

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

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

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

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

**Inputs:** `[note_index]`
**Outputs:** `[SERIAL_NUMBER]` | Any | @@ -119,10 +119,10 @@ Note utility procedures can be used to compute the required utility data or writ | Procedure | Description | Context | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `compute_inputs_commitment` | Computes the commitment to the output note inputs starting at the specified memory address.

**Inputs:** `[inputs_ptr, num_inputs]`
**Outputs:** `[INPUTS_COMMITMENT]` | Any | +| `compute_storage_commitment` | Computes the commitment to the output note storage starting at the specified memory address.

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

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

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

**Inputs:** `[inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]`
**Outputs:** `[RECIPIENT]` | Any | +| `build_recipient_hash` | Returns the `RECIPIENT` for a specified `SERIAL_NUM`, `SCRIPT_ROOT`, and storage commitment.

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

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

**Inputs:** `[METADATA]`
**Outputs:** `[sender_id_prefix, sender_id_suffix]` | Any | ## Transaction Procedures (`miden::protocol::tx`) diff --git a/docs/src/transaction.md b/docs/src/transaction.md index c4c9477cf5..22c85d0d6a 100644 --- a/docs/src/transaction.md +++ b/docs/src/transaction.md @@ -34,7 +34,7 @@ Every `Transaction` describes the process of an account changing its state. This A `Transaction` requires several inputs: - **Account**: A `Transaction` is always executed against a single account. The executor must have complete knowledge of the account's state. -- **Notes**: A `Transaction` can consume and output up to `1024` notes. The executor must have complete knowledge of the note data, including note inputs, before consumption. For private notes, the data cannot be fetched from the blockchain and must be received through an off-chain channel. +- **Notes**: A `Transaction` can consume and output up to `1024` notes. The executor must have complete knowledge of the note data, including note storage, before consumption. For private notes, the data cannot be fetched from the blockchain and must be received through an off-chain channel. - **Blockchain state**: The current reference block and information about the notes database used to authenticate notes to be consumed must be retrieved from the Miden operator before execution. Usually, notes to be consumed in a `Transaction` must have been created before the reference block. - **Transaction script (optional)**: The `Transaction` script is code defined by the executor. And like note scripts, they can invoke account methods, e.g., sign a transaction. There is no limit to the amount of code a `Transaction` script can hold. - **Transaction arguments (optional)**: For every note, the executor can inject transaction arguments that are present at runtime. If the note script — and therefore the note creator — allows, the note script can read those arguments to allow dynamic execution. See below for an example. @@ -64,7 +64,7 @@ To illustrate the `Transaction` protocol, we provide two examples for a basic `T ### Creating a P2ID note -Let's assume account A wants to create a P2ID note. P2ID notes are pay-to-ID notes that can only be consumed by a specified target account ID. Note creators can provide the target account ID using the [note inputs](note#inputs). +Let's assume account A wants to create a P2ID note. P2ID notes are pay-to-ID notes that can only be consumed by a specified target account ID. Note creators can provide the target account ID using the [note storage](note#inputs). In this example, account A uses the basic wallet and the authentication component provided by `miden-standards`. The basic wallet component defines the methods `wallets::basic::create_note` and `wallets::basic::move_asset_to_note` to create notes with assets, and `wallets::basic::receive_asset` to receive assets. The authentication component exposes `auth::basic::auth_tx_falcon512_rpo` which allows for signing a transaction. Some account methods like `active_account::get_id` are always exposed. @@ -80,7 +80,7 @@ To start the transaction process, the executor fetches and prepares all the inpu In the transaction's prologue the data is being authenticated by re-hashing the provided values and comparing them to the blockchain's data (this is how private data can be used and verified during the execution of transaction without actually revealing it to the network). -Then the P2ID note script is being executed. The script starts by reading the note inputs `active_note::get_inputs` — in our case the account ID of the intended target account. It checks if the provided target account ID equals the account ID of the executing account. This is the first time the note invokes a method exposed by the `Transaction` kernel, `active_account::get_id`. +Then the P2ID note script is being executed. The script starts by reading the note storage `active_note::get_storage` — in our case the account ID of the intended target account. It checks if the provided target account ID equals the account ID of the executing account. This is the first time the note invokes a method exposed by the `Transaction` kernel, `active_account::get_id`. If the check passes, the note script pushes the assets it holds into the account's vault. For every asset the note contains, the script calls the `wallets::basic::receive_asset` method exposed by the account's wallet component. The `wallets::basic::receive_asset` procedure calls `native_account::add_asset`, which cannot be called from the note itself. This allows accounts to control what functionality to expose, e.g. whether the account supports receiving assets or not, and the note cannot bypass that.