Polygon Miden

A rollup for high-throughput, private applications.

Using Polygon Miden, builders can create novel, high-throughput, private applications for payments, DeFi, digital assets and gaming. Applications and users are secured by Ethereum and AggLayer.

If you want to join the technical discussion, please check out the following:

!!! info - These docs are still work-in-progress. - Some topics have been discussed in greater depth, while others require additional clarification.

Status and features

Polygon Miden is currently on release v0.6. This is an early version of the protocol and its components.

!!! important We expect breaking changes on all components.

At the time of writing, Polygon Miden doesn't offer all the features you may expect from a zkRollup. During 2024, we expect to gradually implement more features.

Feature highlights

Private accounts

The Miden operator only tracks a commitment to account data in the public database. Users can only execute smart contracts when they know the interface.

Private notes

Like private accounts, the Miden operator only tracks a commitment to notes in the public database. Users need to communicate note details to each other off-chain (via a side channel) in order to consume private notes in transactions.

Public accounts

Polygon Miden supports public smart contracts like Ethereum. The code and state of those accounts is visible to the network and anyone can execute transactions against them.

Public notes

As with public accounts, public notes are also supported. That means, the Miden operator publicly stores note data. Note consumption is not private.

Local transaction execution

The Miden client allows for local transaction execution and proving. The Miden operator verifies the proof and, if valid, updates the state DBs with the new data.

Simple smart contracts

Currently, there are three different smart contracts available. A basic wallet smart contract that sends and receives assets, and fungible and non-fungible faucets to mint and burn assets.

All accounts are written in MASM.

P2ID, P2IDR, and SWAP note scripts

Currently, there are three different note scripts available. Two different versions of pay-to-id scripts of which P2IDR is reclaimable, and a swap script that allows for simple token swaps.

Simple block building

The Miden operator running the Miden node builds the blocks containing transactions.

Maintaining state

The Miden node stores all necessary information in its state DBs and provides this information via its RPC endpoints.

Planned features

!!! warning The following features are at a planning stage only.

Customized smart contracts

Accounts can expose any interface in the future. This is the Miden version of a smart contract. Account code can be arbitrarily complex due to the underlying Turing-complete Miden VM.

Customized note scripts

Users will be able to write their own note scripts using the Miden client. Note scripts are executed during note consumption and they can be arbitrarily complex due to the underlying Turing-complete Miden VM.

Network transactions

Transaction execution and proving can be outsourced to the network and to the Miden operator. Those transactions will be necessary when it comes to public shared state, and they can be useful if the user's device is not powerful enough to prove transactions efficiently.

Rust compiler

In order to write account code, note or transaction scripts, in Rust, there will be a Rust -> Miden Assembly compiler.

Block and epoch proofs

The Miden node will recursively verify transactions and in doing so build batches of transactions, blocks, and epochs.

Benefits of Polygon Miden

  • Ethereum security.
  • Developers can build applications that are infeasible on other systems. For example:
    • on-chain order book exchange due to parallel transaction execution and updatable transactions.
    • complex, incomplete information games due to client-side proving and cheap complex computations.
    • safe wallets due to hidden account state.
  • Better privacy properties than on Ethereum - first web2 privacy, later even stronger self-sovereignty.
  • Transactions can be recalled and updated.
  • Lower fees due to client-side proving.
  • dApps on Miden are safe to use due to account abstraction and compile-time safe Rust smart contracts.

License

Licensed under the MIT license.

Prerequisites

Create an account

P2P private transfer

P2P public transfer

Welcome to MkDocs

For full documentation visit mkdocs.org.

Commands

  • mkdocs new [dir-name] - Create a new project.
  • mkdocs serve - Start the live-reloading docs server.
  • mkdocs build - Build the documentation site.
  • mkdocs -h - Print help message and exit.

Project layout

mkdocs.yml    # The configuration file.
docs/
    index.md  # The documentation homepage.
    ...       # Other markdown pages, images and other files.

comments: true

Overview

Components

The Miden client currently has two main components:

  1. Miden client library.
  2. Miden client CLI.

Miden client library

The Miden client library is a Rust library that can be integrated into projects, allowing developers to interact with the Miden rollup.

The library provides a set of APIs and functions for executing transactions, generating proofs, and managing activity on the Miden network.

Miden client CLI

The Miden client also includes a command-line interface (CLI) that serves as a wrapper around the library, exposing its basic functionality in a user-friendly manner.

The CLI provides commands for interacting with the Miden rollup, such as submitting transactions, syncing with the network, and managing account data.


comments: true

Software prerequisites

Install the client

We currently recommend installing and running the client with the testing and concurrent features.

Run the following command to install the miden-client:

cargo install miden-cli --features concurrent,testing

This installs the miden binary (at ~/.cargo/bin/miden) with the testing and concurrent features.

Testing feature

The testing feature speeds up account creation.

!!! warning "Install the testing feature on node and client" - When using the client CLI alongside a locally-running node, make sure to install/execute the node with the testing feature. - Some validations can fail if the flag does not match on both the client and the node.

Concurrent feature

The concurrent flag enables optimizations that result in faster transaction execution and proving times.

Run the client

  1. Make sure you have already installed the client. If you don't have a miden-client.toml file in your directory, create one or run miden init to initialize one at the current working directory. You can do so without any arguments to use its defaults or define either the RPC config or the store config via --rpc and --store-path

  2. Run the client CLI using:

    miden
    

comments: true

The Miden client offers a range of functionality for interacting with the Miden rollup.

Transaction execution

The Miden client facilitates the execution of transactions on the Miden rollup; allowing users to transfer assets, mint new tokens, and perform various other operations.

Proof generation

The Miden rollup supports user-generated proofs which are key to ensuring the validity of transactions on the Miden rollup.

To enable such proofs, the client contains the functionality for executing, proving, and submitting transactions.

Miden network interactivity

The Miden client enables users to interact with the Miden network. This includes syncing with the latest blockchain data and managing account information.

Account generation and tracking

The Miden client provides features for generating and tracking accounts within the Miden rollup ecosystem. Users can create accounts and track their transaction status.


comments: true

The Miden client has the following architectural components:

!!! important "Customizable" - The RPC client and the store are Rust traits. - This allow developers and users to easily customize their implementations.

Store

The store is central to the client's design.

It manages the persistence of the following entities:

  • Accounts; including their state history and related information such as vault assets and account code.
  • Transactions and their scripts.
  • Notes.
  • Note tags.
  • Block headers and chain information that the client needs to execute transactions and consume notes.

Because Miden allows off-chain executing and proving, the client needs to know about the state of the blockchain at the moment of execution. To avoid state bloat, however, the client does not need to see the whole blockchain history, just the chain history intervals that are relevant to the user.

The store can track any number of accounts, and any number of notes that those accounts might have created or may want to consume.

RPC client

The RPC client communicates with the node through a defined set of gRPC methods.

Currently, these include:

  • GetBlockHeaderByNumber: Returns the block header information given a specific block number.
  • SyncState: Asks the node for information relevant to the client. For example, specific account changes, whether relevant notes have been created or consumed, etc.
  • SubmitProvenTransaction: Sends a locally-proved transaction to the node for inclusion in the blockchain.

Transaction executor

The transaction executor executes transactions using the Miden VM.

When executing, the executor needs access to relevant blockchain history. The executor uses a DataStore interface for accessing this data. This means that there may be some coupling between the executor and the store.


comments: true

To use the Miden client library in a Rust project, include it as a dependency.

In your project's Cargo.toml, add:

miden-client = { version = "0.6" }

Features

The Miden client library supports the testing and concurrent features which are both recommended for developing applications with the client. To use them, add the following to your project's Cargo.toml:

miden-client = { version = "0.6", features = ["testing", "concurrent"] }

Client instantiation

Spin up a client using the following Rust code and supplying a store and RPC endpoint.

The current supported store is the SqliteDataStore, which is a SQLite implementation of the Store trait.

#![allow(unused)]
fn main() {
let client: Client<TonicRpcClient, SqliteDataStore> = {
    
    let store = SqliteStore::new((&client_config).into()).await.map_err(ClientError::StoreError)?;

    let mut rng = rand::thread_rng();
    let coin_seed: [u64; 4] = rng.gen();

    let rng = RpoRandomCoin::new(coin_seed.map(Felt::new));
    let authenticator = StoreAuthenticator::new_with_rng(store.clone(), rng);
    let tx_prover = LocalTransactionProver::new(ProvingOptions::default());

    let client = Client::new(
        Box::new(TonicRpcClient::new(&client_config.rpc)),
        rng,
        Arc::new(store),
        Arc::new(authenticator),
        Arc::new(tx_prover),
        false, // set to true if you want a client with debug mode
    )
};
}

Create local account

With the Miden client, you can create and track any number of public and local accounts. For local accounts, the state is tracked locally, and the rollup only keeps commitments to the data, which in turn guarantees privacy.

The AccountTemplate enum defines the type of account. The following code creates a new local account:

#![allow(unused)]
fn main() {
let account_template = AccountTemplate::BasicWallet {
    mutable_code: false,
    storage_mode: AccountStorageMode::Private,
};
    
let (new_account, account_seed) = client.new_account(account_template).await?;
}

Once an account is created, it is kept locally and its state is automatically tracked by the client.

To create an public account, you can specify AccountStorageMode::Public like so:

let account_template = AccountTemplate::BasicWallet {
    mutable_code: false,
    storage_mode: AccountStorageMode::Public,
};

let (new_account, account_seed) = client.new_account(client_template).await?;

The account's state is also tracked locally, but during sync the client updates the account state by querying the node for the most recent account data.

Execute transaction

In order to execute a transaction, you first need to define which type of transaction is to be executed. This may be done with the TransactionRequest which represents a general definition of a transaction. Some standardized constructors are available for common transaction types.

Here is an example for a pay-to-id transaction type:

#![allow(unused)]
fn main() {
// Define asset
let faucet_id = AccountId::from_hex(faucet_id)?;
let fungible_asset = FungibleAsset::new(faucet_id, *amount)?.into();

let sender_account_id = AccountId::from_hex(bob_account_id)?;
let target_account_id = AccountId::from_hex(alice_account_id)?;
let payment_transaction = PaymentTransactionData::new(
    vec![fungible_asset.into()],
    sender_account_id,
    target_account_id,
);

let transaction_request = TransactionRequest::pay_to_id(
    payment_transaction,
    None,
    NoteType::Private,
    client.rng(),
)?;

// Execute transaction. No information is tracked after this.
let transaction_execution_result = client.new_transaction(sender_account_id, transaction_request.clone()).await?;

// Prove and submit the transaction, which is stored alongside created notes (if any)
client.submit_transaction(transaction_execution_result).await?
}

You can decide whether you want the note details to be public or private through the note_type parameter. You may also execute a transaction by manually defining a TransactionRequest instance. This allows you to run custom code, with custom note arguments as well.


comments: true

The following document lists the commands that the CLI currently supports.

!!! note Use --help as a flag on any command for more information.

Usage

Call a command on the miden-client like this:

miden <command> <flags> <arguments>

Optionally, you can include the --debug flag to run the command with debug mode, which enables debug output logs from scripts that were compiled in this mode:

miden --debug <flags> <arguments>

Note that the debug flag overrides the MIDEN_DEBUG environment variable.

Commands

init

Creates a configuration file for the client in the current directory.

# This will create a config file named `miden-client.toml` using default values
# This file contains information useful for the CLI like the RPC provider and database path
miden init

# You can use the --rpc flag to override the default rpc config
miden init --rpc 18.203.155.106
# You can specify the port
miden init --rpc 18.203.155.106:8080
# You can also specify the protocol (http/https)
miden init --rpc https://18.203.155.106
# You can specify both
miden init --rpc https://18.203.155.106:1234

# You can use the --store_path flag to override the default store config
miden init --store_path db/store.sqlite3

# You can provide both flags
miden init --rpc 18.203.155.106 --store_path db/store.sqlite3

account

Inspect account details.

Action Flags

FlagsDescriptionShort Flag
--listList all accounts monitored by this client-l
--show <ID>Show details of the account for the specified ID-s
--default <ID>Manage the setting for the default account-d

The --show flag also accepts a partial ID instead of the full ID. For example, instead of:

miden account --show 0x8fd4b86a6387f8d8

You can call:

miden account --show 0x8fd4b86

For the --default flag, if <ID> is "none" then the previous default account is cleared. If no <ID> is specified then the default account is shown.

new-wallet

Creates a new wallet account.

This command has two optional flags:

  • --storage-type <TYPE>: Used to select the storage mode of the account (private if not specified). It may receive "private" or "public".
  • --mutable: Makes the account code mutable (it's immutable by default).

After creating an account with the new-wallet command, it is automatically stored and tracked by the client. This means the client can execute transactions that modify the state of accounts and track related changes by synchronizing with the Miden node.

new-faucet

Creates a new faucet account.

This command has two optional flags:

  • --storage-type <type>: Used to select the storage mode of the account (private if not specified). It may receive "private" or "public".
  • --non-fungible: Makes the faucet asset non-fungible (it's fungible by default).

After creating an account with the new-faucet command, it is automatically stored and tracked by the client. This means the client can execute transactions that modify the state of accounts and track related changes by synchronizing with the Miden node.

info

View a summary of the current client state.

notes

View and manage notes.

Action Flags

FlagsDescriptionShort Flag
--list [<filter>]List input notes-l
--show <ID>Show details of the input note for the specified note ID-s

The --list flag receives an optional filter: - expected: Only lists expected notes. - committed: Only lists committed notes. - consumed: Only lists consumed notes. - processing: Only lists processing notes. - consumable: Only lists consumable notes. An additional --account-id <ID> flag may be added to only show notes consumable by the specified account. If no filter is specified then all notes are listed.

The --show flag also accepts a partial ID instead of the full ID. For example, instead of:

miden notes --show 0x70b7ecba1db44c3aa75e87a3394de95463cc094d7794b706e02a9228342faeb0

You can call:

miden notes --show 0x70b7ec

sync

Sync the client with the latest state of the Miden network. Shows a brief summary at the end.

tags

View and add tags.

Action Flags

FlagDescriptionAliases
--listList all tags monitored by this client-l
--add <tag>Add a new tag to the list of tags monitored by this client-a
--remove <tag>Remove a tag from the list of tags monitored by this client-r

tx

View transactions.

Action Flags

CommandDescriptionAliases
--listList tracked transactions-l

After a transaction gets executed, two entities start being tracked:

  • The transaction itself: It follows a lifecycle from Pending (initial state) and Committed (after the node receives it). It may also be Discarded if the transaction was not included in a block.
  • Output notes that might have been created as part of the transaction (for example, when executing a pay-to-id transaction).

Transaction creation commands

mint

Creates a note that contains a specific amount tokens minted by a faucet, that the target Account ID can consume.

Usage: miden mint --target <TARGET ACCOUNT ID> --asset <AMOUNT>::<FAUCET ID> --note-type <NOTE_TYPE>

consume-notes

Account ID consumes a list of notes, specified by their Note ID.

Usage: miden consume-notes --account <ACCOUNT ID> [NOTES]

For this command, you can also provide a partial ID instead of the full ID for each note. So instead of

miden consume-notes --account <some-account-id> 0x70b7ecba1db44c3aa75e87a3394de95463cc094d7794b706e02a9228342faeb0 0x80b7ecba1db44c3aa75e87a3394de95463cc094d7794b706e02a9228342faeb0

You can do:

miden consume-notes --account <some-account-id> 0x70b7ecb 0x80b7ecb

Additionally, you can optionally not specify note IDs, in which case any note that is known to be consumable by the executor account ID will be consumed.

Either Expected or Committed notes may be consumed by this command, changing their state to Processing. It's state will be updated to Consumed after the next sync.

send

Sends assets to another account. Sender Account creates a note that a target Account ID can consume. The asset is identified by the tuple (FAUCET ID, AMOUNT). The note can be configured to be recallable making the sender able to consume it after a height is reached.

Usage: miden send --sender <SENDER ACCOUNT ID> --target <TARGET ACCOUNT ID> --asset <AMOUNT>::<FAUCET ID> --note-type <NOTE_TYPE> <RECALL_HEIGHT>

swap

The source account creates a Swap note that offers some asset in exchange for some other asset. When another account consumes that note, it'll receive the offered amount and it'll have the requested amount removed from its assets (and put into a new note which the first account can then consume). Consuming the note will fail if the account doesn't have enough of the requested asset.

Usage: miden swap --source <SOURCE ACCOUNT ID> --offered-asset <OFFERED AMOUNT>::<OFFERED FAUCET ID> --requested-asset <REQUESTED AMOUNT>::<REQUESTED FAUCET ID> --note-type <NOTE_TYPE>

Tips

For send and consume-notes, you can omit the --sender and --account flags to use the default account defined in the config. If you omit the flag but have no default account defined in the config, you'll get an error instead.

For every command which needs an account ID (either wallet or faucet), you can also provide a partial ID instead of the full ID for each account. So instead of

miden send --sender 0x80519a1c5e3680fc --target 0x8fd4b86a6387f8d8 --asset 100::0xa99c5c8764d4e011

You can do:

miden send --sender 0x80519 --target 0x8fd4b --asset 100::0xa99c5c8764d4e011

!!! note The only exception is for using IDs as part of the asset, those should have the full faucet's account ID.

Transaction confirmation

When creating a new transaction, a summary of the transaction updates will be shown and confirmation for those updates will be prompted:

miden <tx command> ...

TX Summary:

...

Continue with proving and submission? Changes will be irreversible once the proof is finalized on the rollup (Y/N)

This confirmation can be skipped in non-interactive environments by providing the --force flag (miden send --force ...):

Importing and exporting

export

Export input note data to a binary file .

FlagDescriptionAliases
--filename <FILENAME>Desired filename for the binary file.-f
--export-type <EXPORT_TYPE>Exported note type.-e
Export type

The user needs to specify how the note should be exported via the --export-type flag. The following options are available:

  • id: Only the note ID is exported. When importing, if the note ID is already tracked by the client, the note will be updated with missing information fetched from the node. This works for both public and private notes. If the note isn't tracked and the note is public, the whole note is fetched from the node and is stored for later use.
  • full: The note is exported with all of its information (metadata and inclusion proof). When importing, the note is considered unverified. The note may not be consumed directly after importing as its block header will not be stored in the client. The block header will be fetched and be used to verify the note during the next sync. At this point the note will be committed and may be consumed.
  • partial: The note is exported with minimal information and may be imported even if the note is not yet committed on chain. At the moment of importing the note, the client will check the state of the note by doing a note sync, using the note's tag. Depending on the response, the note will be either stored as "Expected" or "Committed".

import

Import entities managed by the client, such as accounts and notes. The type of entities is inferred.


comments: true

After installation, use the client by running the following and adding the relevant commands:

miden

!!! info "Help" Run miden --help for information on miden commands.

Client Configuration

We configure the client using a TOML file (miden-client.toml).

[rpc]
endpoint = { protocol = "http", host = "localhost", port = 57291 }
timeout_ms = 10000

[store]
database_filepath = "store.sqlite3"

[cli]
default_account_id = "0x012345678"

The TOML file should reside in same the directory from which you run the CLI.

In the configuration file, you will find a section for defining the node's rpc endpoint and timeout and the store's filename database_filepath.

By default, the node is set up to run on localhost:57291.

!!! note - Running the node locally for development is encouraged. - However, the endpoint can point to any remote node.

There's an additional optional section used for CLI configuration. It currently contains the default account ID, which is used to execute transactions against it when the account flag is not provided.

By default none is set, but you can set and unset it with:

miden account --default <ACCOUNT_ID> #Sets default account
miden account --default none #Unsets default account

!!! note - The account must be tracked by the client in order to be set as the default account.

You can also see the current default account ID with:

miden account --default

Environment variables

  • MIDEN_DEBUG: When set to true, enables debug mode on the transaction executor and the script compiler. For any script that has been compiled and executed in this mode, debug logs will be output in order to facilitate MASM debugging (these instructions can be used to do so). This variable can be overridden by the --debug CLI flag.

!!! example "Executing, proving, and submitting transactions to the Miden node" For a complete example on how to run the client and submit transactions to the Miden node, refer to the Getting started documentation.

!!! example "Miden client API docs" The latest and complete reference for the Miden client API can be found at Miden client docs.rs.


comments: true

Miden architecture overview

Polygon Miden’s architecture departs considerably from typical blockchain designs to support privacy and parallel transaction execution.

In traditional blockchains, state and transactions must be transparent to be verifiable. This is necessary for block production and execution.

However, user generated zero-knowledge proofs allow state transitions, e.g. transactions, to be verifiable without being transparent.

Miden design goals

  • High throughput: The ability to process a high number of transactions (state changes) over a given time interval.
  • Privacy: The ability to keep data known to one’s self and anonymous while processing and/or storing it.
  • Asset safety: Maintaining a low risk of mistakes or malicious behavior leading to asset loss.

Actor model

The actor model inspires Polygon Miden’s execution model. This is a well-known computational design paradigm in concurrent systems. In the actor model, actors are state machines responsible for maintaining their own state. In the context of Polygon Miden, each account is an actor. Actors communicate with each other by exchanging messages asynchronously. One actor can send a message to another, but it is up to the recipient to apply the requested change to their state.

Polygon Miden’s architecture takes the actor model further and combines it with zero-knowledge proofs. Now, actors not only maintain and update their own state, but they can also prove the validity of their own state transitions to the rest of the network. This ability to independently prove state transitions enables local smart contract execution, private smart contracts, and much more. And it is quite unique in the rollup space. Normally only centralized entities - sequencer or prover - create zero-knowledge proofs, not the users.

Core concepts

Miden uses accounts and notes, both of which hold assets. Accounts consume and produce notes during transactions. Transactions describe the account state changes of single accounts.

Accounts

Accounts can hold assets and define rules how assets can be transferred. Accounts can represent users or autonomous smart contracts. The accounts chapter describes the design of an account, its storage types, and creating an account.

Notes

Notes are messages that accounts send to each other. A note stores assets and a script that defines how the note can be consumed. The note chapter describes the design, the storage types, and the creation of a note.

Assets

Assets can be fungible and non-fungible. They are stored in the owner’s account itself or in a note. The assets chapter describes asset issuance, customization, and storage.

Transactions

Transactions describe the production and consumption of notes by a single account.

Executing a transaction always results in a STARK proof.

The transaction chapter describes the transaction design and implementation, including an in-depth discussion of how transaction execution happens in the transaction kernel program.

Limits

Limits topic describes limits currently enforced in miden-base and miden-node.

Accounts produce and consume notes to communicate

Architecture core concepts

State and execution

The actor-based execution model requires a radically different approach to recording the system's state. Actors and the messages they exchange must be treated as first-class citizens. Polygon Miden addresses this by combining the state models of account-based systems like Ethereum and UTXO-based systems like Bitcoin and Zcash.

Miden's state model captures the individual states of all accounts and notes, and the execution model describes state progress in a sequence of blocks.

State model

State describes everything that is the case at a certain point in time. Individual states of accounts or notes can be stored on-chain and off-chain. This chapter describes the three different state databases in Miden.

Execution model

Execution defines how state progresses as aggregated-state-updates in batches, blocks, and epochs. The execution chapter describes the execution model and how blocks are built.

Operators capture and progress state

Architecture state process


comments: true

Accounts are basic building blocks representing a user or an autonomous smart contract.

For smart contracts the go-to solution is account-based state. Miden supports expressive smart contracts via a Turing-complete language and the use of accounts.

In Miden, an account is an entity which holds assets and defines rules about how to transfer these assets.

Account design

In Miden every account is a smart contract. The diagram below illustrates the basic components of an account.

![Architecture core concepts](../img/architecture/account/account-definition.png){ width="25%" }

!!! tip "Key to diagram" * Account ID: A unique identifier for an account. This does not change throughout its lifetime. * Storage: User-defined data which can be stored in an account. * Nonce: A counter which increments whenever the account state changes. * Vault: A collection of assets stored in an account. * Code: A collection of functions which define the external interface for an account.

Account ID

A ~63 bits long identifier for the account ID (one field element felt).

The four most significant bits specify the account type - regular or faucet - and the account-storage-modes - public or private.

Account storage

The storage of an account is composed of a variable number of index-addressable storage slots, up to 255 slots in total.

Each slot has a type which defines its size and structure. Currently, the following types are supported:

  • StorageSlot::Value: contains a single Word of data (i.e., 32 bytes).
  • StorageSlot::Map: contains a StorageMap which is a key-value map where both keys and values are Words. The value of a storage slot containing a map is the commitment to the underlying map.

As described below, accounts can be stored off-chain (private) and on-chain (public). Accounts that store huge amounts of data, as it is possible using storage maps, are better designed as off-chain accounts.

Nonce

A counter which increments whenever the account state changes.

Nonce values must be strictly monotonically increasing and increment by any value smaller than 2^32 for every account update.

Vault

An asset container for an account.

An account vault can contain an unlimited number of assets. The assets are stored in a sparse Merkle tree as follows:

  • For fungible assets, the index of a node is defined by the issuing faucet ID, and the value of the node is the asset itself. Thus, for any fungible asset there will be only one node in the tree.
  • For non-fungible assets, the index is defined by the asset itself, and the asset is also the value of the node.

An account vault can be reduced to a single hash which is the root of the sparse Merkle tree.

Code

The interface for accounts. In Miden every account is a smart contract. It has an interface that exposes functions that can be called by note scripts and transaction scripts. Users cannot call those functions directly.

Functions exposed by the account have the following properties:

  • Functions are actually roots of Miden program MASTs (i.e., a 32-byte hash). Thus, the function identifier is a commitment to the code which is executed when a function is invoked.
  • Only account functions have mutable access to an account's storage and vault. Therefore, the only way to modify an account's internal state is through one of the account's functions.
  • Account functions can take parameters and can create new notes.

!!! note Since code in Miden is expressed as MAST, every function is a commitment to the underlying code. The code cannot change unnoticed to the user because its hash would change. Behind any MAST root there can only be 256 functions.

Example account code

Currently, Miden provides two standard implementations for account code.

Basic user account

There is a standard for a basic user account. It exposes three functions via its interface.

Basic user account code
  use.miden::contracts::wallets::basic->basic_wallet
  use.miden::contracts::auth::basic

  export.basic_wallet::receive_asset
  export.basic_wallet::create_note
  export.basic_wallet::move_asset_to_note
  export.basic::auth_tx_rpo_falcon512

Note scripts or transaction scripts can call receive_asset, create_note and move_asset_to_note procedures.

Transaction scripts can also call auth_tx_rpo_falcon512 and authenticate the transaction.

!!! warning Without correct authentication, i.e. knowing the correct private key, a note cannot successfully invoke receive_asset, create_note or move_asset_to_note.

Basic fungible faucet (faucet for fungible assets)

There is also a standard for a basic fungible faucet.

Fungible faucet code
#! Distributes freshly minted fungible assets to the provided recipient.
#!
#! ...
export.distribute
    # get max supply of this faucet. We assume it is stored at pos 3 of slot 1
    push.METADATA_SLOT exec.account::get_item drop drop drop
    # => [max_supply, amount, tag, note_type, RECIPIENT, ...]

    # get total issuance of this faucet so far and add amount to be minted
    exec.faucet::get_total_issuance
    # => [total_issuance, max_supply, amount, tag, note_type RECIPIENT, ...]

    # compute maximum amount that can be minted, max_mint_amount = max_supply - total_issuance
    sub
    # => [max_supply - total_issuance, amount, tag, note_type, RECIPIENT, ...]

    # check that amount =< max_supply - total_issuance, fails if otherwise
    dup.1 gte assert.err=ERR_BASIC_FUNGIBLE_MAX_SUPPLY_OVERFLOW
    # => [asset, tag, note_type, RECIPIENT, ...]

    # creating the asset
    exec.asset::create_fungible_asset
    # => [ASSET, tag, note_type, RECIPIENT, ...]

    # mint the asset; this is needed to satisfy asset preservation logic.
    exec.faucet::mint
    # => [ASSET, tag, note_type, RECIPIENT, ...]

    # store and drop the ASSET
    mem_storew.3 dropw
    # => [tag, note_type, RECIPIENT, ...]

    # create a note containing the asset
    exec.tx::create_note
    # => [note_ptr, ZERO, ZERO, ...]

    # store and drop the ASSET
    padw mem_loadw.3 movup.4 exec.tx::add_asset_to_note
    # => [note_ptr, ASSET, ZERO, ...]
end

#! Burns fungible assets.
#!
#! ...
export.burn
    # burning the asset
    exec.faucet::burn
    # => [ASSET]

    # increments the nonce (anyone should be able to call that function)
    push.1 exec.account::incr_nonce

    # clear the stack
    padw swapw dropw
    # => [...]
end

The contract exposes two functions distribute and burn.

The first function distribute can only be called by the faucet owner, otherwise it fails. As inputs, the function expects everything that is needed to create a note containing the freshly minted asset, i.e., amount, metadata, and recipient.

The second function burn burns the tokens that are contained in a note and can be called by anyone.

!!! info "Difference between burn and distribute" The burn procedure exposes exec.account::incr_nonce, so by calling burn the nonce of the executing account gets increased by 1 and the transaction will pass the epilogue check. The distribute procedure does not expose that. That means the executing user needs to call basic::auth_tx_rpo_falcon512 which requires the private key.*

Account creation

For an account to exist it must be present in the account database kept on the Miden node(s).

However, new accounts can be created locally by users using the Miden client. The process is as follows:

  • Alice creates a new account ID (according to the account types) using the Miden client.
  • Alice's Miden client asks the Miden node to check if the new ID already exists.
  • Alice shares the ID with Bob (eg. when Alice wants to receive funds).
  • Bob executes a transaction and creates a note that contains an asset for Alice.
  • Alice consumes Bob's note to receive the asset in a transaction.
  • Depending on the account storage mode (private vs. public) and transaction type (local vs. network) the operator eventually receives the new account ID and - if the transaction is correct - adds the ID to the account database.

A user can create an account in one of the following manners:

  1. Use the Miden client as a wallet.
  2. Use the Miden base builtin functions for wallet creation: basic wallet, fungible faucet

Account types

There are two basic account types in Miden: Regular accounts and faucets. Only faucets can mint new assets. Regular accounts can be mutable or immutable, which simply means that it is possible to change the account code after creation.

Type and mutability is encoded in the most significant bits of the account's ID.

Basic mutableBasic immutableFungible faucetNon-fungible faucet
DescriptionFor most users, e.g. a wallet. Code changes allowed, including public API.For most smart contracts. Once deployed code is immutable.Users can issue fungible assets and customize them.Users can issue non-fungible assets and customize them.
Code updatabilityyesnonono
Most significant bits00011011

Public and private accounts

Users can decide whether to keep their accounts private or public at account creation. The account ID encodes this preference on the third and fourth most significant bit.

  • Accounts with public state: The actual state is stored on-chain. This is similar to how accounts work in public blockchains, like Ethereum. Smart contracts that depend on public shared state should be stored public on Miden, e.g., DEX contract.
  • Accounts with private state: Only the hash of the account is stored on-chain. Users who want to stay private, and manage their own data, should choose this option. Users who want to interact with private accounts need to know the account's interface.


comments: true

Two of Miden's key goals are parallel transaction execution and privacy.

Polygon Miden implements a hybrid UTXO and account-based state model which enforces these goals with notes. Notes interact with, and transfer assets between, accounts. They can be consumed and produced asynchronously and privately.

The concept of notes is a key divergence from Ethereum’s account-based model.

Note design

![Architecture core concepts](../img/architecture/note/note.png){ width="45%" }

!!! tip "Key to diagram" * Assets: An asset container for a note. It can contain up to 256 assets stored in an array which can be reduced to a single hash. * Script: To be executed in the transaction in which the note is consumed. The script defines the conditions for the consumption. If the script fails, the note cannot be consumed. * Inputs: Used to execute the note script. They can be accessed by the note script via transaction kernel procedures. A note can be associated with up to 128 input values. Each value is represented by a single field element. Thus, note input values can contain up to ~1 KB of data. * Serial number: A note's unique identifier to break link-ability between note hash and nullifier. Should be a random word chosen by the user - if revealed, the nullifier might be computed easily. * In addition, a note has metadata including the sender and the note tag. Those values are always public regardless of the note storage mode.

Note lifecycle

New notes are created by executing transactions.

After verifying the transaction proof the operator adds either only the note hash (private notes) or the full note data (public notes) to the note database.

Notes can be produced and consumed locally by users in local transactions or by the operator in a network transaction.

Note consumption requires the transacting party to know the note data to compute the nullifier. After successful verification, the operator sets the corresponding entry in the nullifier database to "consumed".

![Architecture core concepts](../img/architecture/note/note-life-cycle.png)

Note creation

Notes are created as the outputs (OutputNotes) of Miden transactions. Operators record the notes to the note database. After successful verification of the underlying transactions, those notes can be consumed.

The note script

Every note has a script which gets executed at note consumption. It is always executed in the context of a single account, and thus, may invoke zero or more of the account's functions. The script allows for more than just asset transfers; actions which could be of arbitrary complexity thanks to the Turing completeness of the Miden VM.

By design, every note script can be defined as a unique hash or the root of a Miden program MAST. That also means every function is a commitment to the underlying code. That code cannot change unnoticed to the user because its hash changes. That way it is easy to recognize standardized notes and those which deviate.

Note scripts are created together with their inputs, i.e., the creator of the note defines which inputs are used at note execution by the executor. However, the executor or prover can pass optional note args. Note args are data put onto the stack right before a note script is executed. These are different from note inputs, as the executing account can specify arbitrary note args.

There are standard note scripts (P2ID, P2IDR, SWAP) that users can create and add to their notes using the Miden client or by calling internal Rust code.

  • P2ID and P2IDR scripts are used to send assets to a specific account ID. The scripts check at note consumption if the executing account ID equals the account ID that was set by the note creator as note inputs. The P2IDR script is reclaimable and thus after a certain block height can also be consumed by the sender itself.
  • SWAP script is a simple way to swap assets. It adds an asset from the note into the consumer's vault and creates a new note consumable by the first note's issuer containing the requested asset.

??? note "Example note script pay to ID (P2ID)"

#### Goal of the P2ID script

The P2ID script defines a specific target account ID as the only account that can consume the note. Such notes ensure a targeted asset transfer.

#### Imports and context

The P2ID script uses procedures from the account, note and wallet API.

```arduino
use.miden::account
use.miden::note
use.miden::contracts::wallets::basic->wallet
```

As discussed in detail in [transaction kernel procedures](transactions/procedures.md) certain procedures can only be invoked in certain contexts. The note script is being executed in the note context of the [transaction kernel](transactions/kernel.md).

#### Main script

The main part of the P2ID script checks if the executing account is the same as the account defined in the `NoteInputs`. The creator of the note defines the note script and the note inputs separately to ensure usage of the same standardized P2ID script regardless of the target account ID. That way, it is enough to check the script root (see above).

```arduino
# 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.
#
# Requires that the account exposes: miden::contracts::wallets::basic::receive_asset procedure.
#
# Inputs: [SCRIPT_ROOT]
# Outputs: []
#
# Note inputs are assumed to be as follows:
# - target_account_id is the ID of the account for which the note is intended.
#
# FAILS if:
# - Account does not expose miden::contracts::wallets::basic::receive_asset procedure.
# - Account ID of executing account is not equal to the Account ID specified via note inputs.
# - 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.
begin
    # drop the transaction script root
    dropw
    # => []

    # load the note inputs to memory starting at address 0
    push.0 exec.note::get_inputs
        # => [inputs_ptr]

    # read the target account id from the note inputs
    mem_load
    # => [target_account_id]

    exec.account::get_id
    # => [account_id, target_account_id, ...]

    # ensure account_id = target_account_id, fails otherwise
    assert_eq
    # => [...]

    exec.add_note_assets_to_account
    # => [...]
end
```

1. Every note script starts with the note script root on top of the stack. 
2. After the `dropw`, the stack is cleared. 
3. Next, the script stored the note inputs at pos 0 in the [relative note context memory](https://0xpolygonmiden.github.io/miden-base/transactions/transaction-procedures.html#transaction-contexts) by `push.0 exec.note::get_inputs`. 
4. Then, `mem_load` loads a `Felt` from the specified memory address and puts it on top of the stack, in that cases the `target_account_id` defined by the creator of the note.
5. Now, the note invokes `get_id` from the account API using `exec.account::get_id` - which is possible even in the note context. 

Because, there are two account IDs on top of the stack now, `assert_eq` fails if the two account IDs (target_account_id and executing_account_id) are not the same. That means, the script cannot be successfully executed if executed by any other account than the account specified by the note creator using the note inputs.

If execution hasn't failed, the script invokes a helper procedure `exec.add_note_assets_to_account` to add the note's assets into the executing account's vault.

#### Add assets

This procedure adds the assets held by the note into the account's vault.

```arduino
#! Helper procedure to add all assets of a note to an account.
#!
#! Inputs: []
#! Outputs: []
#!
proc.add_note_assets_to_account
    push.0 exec.note::get_assets
    # => [num_of_assets, 0 = ptr, ...]

    # compute the pointer at which we should stop iterating
    dup.1 add
    # => [end_ptr, ptr, ...]

    # pad the stack and move the pointer to the top
    padw movup.5
    # => [ptr, 0, 0, 0, 0, end_ptr, ...]

    # compute the loop latch
    dup dup.6 neq
    # => [latch, ptr, 0, 0, 0, 0, end_ptr, ...]

    while.true
        # => [ptr, 0, 0, 0, 0, end_ptr, ...]

        # save the pointer so that we can use it later
        dup movdn.5
        # => [ptr, 0, 0, 0, 0, ptr, end_ptr, ...]

        # load the asset and add it to the account
        mem_loadw call.wallet::receive_asset
        # => [ASSET, ptr, end_ptr, ...]

        # increment the pointer and compare it to the end_ptr
        movup.4 add.1 dup dup.6 neq
        # => [latch, ptr+1, ASSET, end_ptr, ...]
    end

    # clear the stack
    drop dropw drop
end
```

The procedure starts by calling `exec.note::get_assets`. As with the note's inputs before, this writes the assets of the note into memory starting at the specified address. Assets are stored in consecutive memory slots, so `dup.1 add` provides the last memory slot.

In Miden, [assets](assets.md) are represented by `Words`, so we need to pad the stack with four `0`s to make room for an asset. Now, if there is at least one asset (checked by `dup dup.6 neq`), the loop starts. It first saves the pointer for later use (`dup movdn.5`), then loads the first asset `mem_loadw` on top of the stack.

Now, the procedure calls the a function of the account interface `call.wallet::receive_asset` to put the asset into the account's vault. Due to different [contexts](https://0xpolygonmiden.github.io/miden-base/transactions/transaction-procedures.html#transaction-contexts), a note script cannot directly call an account function to add the asset. The account must expose this function in its [interface](https://0xpolygonmiden.github.io/miden-base/architecture/accounts.html#example-account-code).

Lastly, the pointer gets incremented, and if there is a second asset, the loop continues (`movup.4 add.1 dup dup.6 neq`). Finally, when all assets were put into the account's vault, the stack is cleared (`drop dropw drop`).

Note storage mode

Similar to accounts, there are two storage modes for notes in Miden - private and public. Notes can be stored publicly in the note database with all data publicly visible for everyone. Alternatively, notes can be stored privately by committing only the note hash to the note database.

Every note has a unique note hash. It is defined as follows:

hash(hash(hash(hash(serial_num, [0; 4]), script_hash), input_hash), vault_hash)

!!! info To compute a note's hash, we do not need to know the note's serial_num. Knowing the hash of the serial_num (as well as script_hash, input_hash and note_vault) is also sufficient. We compute the hash of serial_num as hash(serial_num, [0; 4]) to simplify processing within the VM._

Note discovery (note tags)

Note discovery describes the process by which Miden clients find notes they want to consume. Miden clients can query the Miden node for notes carrying a certain note tag in their metadata. Note tags are best-effort filters for notes registered on the network. They are lightweight values (32-bit) used to speed up queries. Clients can follow tags for specific use cases, such as swap scripts, or user-created custom tags. Tags are also used by the operator to identify notes intended for network execution and include the corresponding information on how to execute them.

The two most signification bits of the note tag have the following interpretation:

PrefixExecution hintTargetAllowed note type
0b00NetworkSpecificNoteType::Public
0b01NetworkUse caseNoteType::Public
0b10LocalAnyNoteType::Public
0b11LocalAnyAny
  • Execution hint: Set to Network for network transactions. These notes are validated and, if possible, consumed in a network transaction.
  • Target: Describes how to interpret the bits in the note tag. For tags with a specific target, the rest of the tag is interpreted as an account_id. For use case values, the meaning of the rest of the tag is not specified by the protocol and can be used by applications built on top of the rollup.
  • Allowed note type: Describes the note's storage mode, either public or private.

The following 30 bits can represent anything. In the above example note tag, it represents an account Id of a public account. As designed the first bit of a public account is always 0 which overlaps with the second most significant bit of the note tag.

0b00000100_11111010_01010110_11100010

This example note tag indicates that the network operator (Miden node) executes the note against a specific account - 0x09f4adc47857e2f6. Only the 30 most significant bits of the account id are represented in the note tag, since account Ids are 64-bit values but note tags only have 32-bits. Knowing a 30-bit prefix already narrows the set of potential target accounts down enough.

Using note tags is a compromise between privacy and latency. If a user queries the operator using the note ID, the operator learns which note a specific user is interested in. Alternatively, if a user always downloads all registered notes and filters locally, it is quite inefficient. By using tags, users can customize privacy parameters by narrowing or broadening their note tag schemes.

??? note "Example note tag for P2ID" P2ID scripts can only be consumed by the specified account ID (target ID). In the standard schema, the target ID is encoded into the note tag.

For network execution of a P2ID note, the note tag is encoded as follows: 0b00000100_11111010_01010110_11100010. This encoding allows the Miden operator to quickly identify the account against which the transaction must be executed.

For local execution of a P2ID note, the recipient needs to be able to discover the note. The recipient can query the Miden node for a specific tag to see if there are new P2ID notes to be consumed. In this case, the two most significant bits are set to 0b11, allowing any note type (private or public) to be used. The next 14 bits represent the 14 most significant bits of the account ID, and the remaining 16 bits are set to 0.

Example for local execution:
```
0b11000100_11111010_00000000_00000000
```
This "fuzzy matching" approach balances privacy and efficiency. A note with this tag could be intended for any account sharing the same 16-bit prefix.

Note consumption

As with creation, notes can only be consumed in Miden transactions. If a valid transaction consuming an InputNote gets verified by the Miden node, the note's unique nullifier gets added to the nullifier database and is therefore consumed.

Notes can only be consumed if the note data is known to the consumer. The note data must be provided as input to the transaction kernel. That means, for privately stored notes, there must be some off-chain communication to transmit the note's data from the sender to the target.

Note recipient to restrict note consumption

There are several ways to restrict the set of accounts that can consume a specific note. One way is to specifically define the target account ID as done in the P2ID and P2IDR note scripts. Another way is by using the concept of a RECIPIENT. Miden defines a RECIPIENT (represented as Word) as:

hash(hash(hash(serial_num, [0; 4]), script_hash), input_hash)

This concept restricts note consumption to those users who know the pre-image data of RECIPIENT - which might be a bigger set than a single account.

During the transaction prologue the users needs to provide all the data to compute the note hash. That means, one can create notes that can only be consumed if the serial_num and other data is known. This information can be passed off-chain from the sender to the consumer. This is only useful with private notes. For public notes, all note data is known, and anyone can compute the RECIPIENT.

You can see in the standard SWAP note script how RECIPIENT is used. Here, using a single hash, is sufficient to ensure that the swapped asset and its note can only be consumed by the defined target.

Note nullifier to ensure private consumption

The note's nullifier is computed as:

hash(serial_num, script_hash, input_hash, 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_hash, input_hash, 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 this approach.

![Architecture core concepts](../img/architecture/note/nullifier.png)


comments: true

In Miden, users can create and trade arbitrary fungible and non-fungible assets.

We differentiate between native and non-native assets in Miden. Native assets follow the Miden asset model. Non-native assets are all other data structures of value that can be exchanged.

Native assets in Polygon Miden have four goals:

  • Asset exchange should be parallelizable.
  • Asset ownership should be self-sovereign.
  • Asset usage should be censorship resistant.
  • Fees can be paid using any asset.

All native assets in Miden are stored directly in accounts, like Ether in Ethereum. Miden does not track asset ownership using global hashmaps, e.g., ERC20 contracts. Local asset storage in accounts provides privacy and the ability for client-side proofs. That is because ownership changes are reflected only on an account and not in an ERC20 account (global hashmap). Thus, these changes can happen in parallel. Additionally, asset exchange is censorship resistant at this level because there is no global contract the transfer must pass through. Finally, users can pay fees in any asset.

Native assets

Native assets are data structures that follow the Miden asset model (encoding, issuance, storing). All native assets are encoded using a single word (4 field elements). The asset encodes both the ID of the issuing account and the asset details.

Having the issuer's ID encoded in the asset makes determining the type of an asset, inside and outside Miden VM, cost-efficient. And, representing the asset in a word means the representation is a commitment to the asset data itself. That is particularly interesting for non-fungible assets.

Issuance

Only specialized accounts called faucets can issue assets. As with regular accounts, anyone can create a faucet account. Faucets can issue either fungible or non-fungible assets - but not both.

The faucet_id identifies the faucet and starts with a different sequence depending on the asset type, see the account id discussion. The faucet's code defines rules for how assets can be minted, who can mint them etc. Conceptually, faucet accounts on Miden are similar to ERC20 contracts on Ethereum. However, there is no ownership tracking in Miden faucets.

Faucets can create assets and immediately distribute them by producing notes. However, assets can also stay in the faucet after creation to be sent later, e.g., in a bundle. That way, one can mint a million NFTs locally in a single transaction and then send them out as needed in separate transactions in the future.

![Architecture core concepts](../img/architecture/asset/asset-issuance.png)

Fungible assets

A fungible asset is encoded using the amount and the faucet_id of the faucet which issued the asset. The amount is guaranteed to be $2^{63} - 1$ or smaller, the maximum supply for any fungible asset. Examples of fungible assets are ETH and stablecoins, e.g., DAI, USDT, and USDC.

If the faucet_id of MATIC is 2, 100 MATIC are encoded as [100, 0, 0, 2]; the 0s in the middle distinguish between fungible and non-fungible assets.

Non-fungible assets

A non-fungible asset is encoded by hashing the asset data into a word and then replacing the second element with the faucet_id of the issuing account: For example [e0, faucet_id, e2, e3]. Note that the second element is guaranteed to be non-zero. Together with the fungible asset encoding, this makes it easy to differentiate between both asset types by inspecting the second element.

Examples of non-fungible assets are all NFTs, e.g., a DevCon ticket. The ticket's data might be represented in a JSON string representing which DevCon, the date, the initial price, etc. Now, users can create a faucet for non-fungible DevCon tickets. This DevCon faucet would hash the JSON string into a word to transform the ticket into an asset.

Storage

Accounts and notes contain asset vaults that are used to store assets. Accounts can keep unlimited assets in a sparse Merkle tree called account vault. Notes can store up to 255 distinct assets.

![Architecture core concepts](../img/architecture/asset/asset-storage.png)

The information on which and how many assets are owned can be private depending on the account's or note's storage mode. This is true for any native asset in Miden.

Non-native assets

Miden is flexible enough to create other types of assets as well.

For example, developers can replicate the Ethereum ERC20 model, where ownership of fungible assets is recorded in a single account. To transact, users must send a note to that account to change the global hashmap.

Furthermore, a complete account can be treated as a programmable asset because ownership of accounts is transferrable. An account could be a "crypto kitty" with specific attributes and rules, and people can trade these "crypto kitties" by transferring accounts between each other.

We can also think of an account representing a car. The owner of the car can change so the car account - granting access to the physical car - can be treated as an asset. In this car account, there could be rules defining who is allowed to drive the car and when.


comments: true

Polygon Miden is an Ethereum Rollup. It batches transactions - or more precisely, proofs - that occur in the same time period into a block.

The Miden execution model describes how state progresses on an individual level via transactions and at the global level expressed as aggregated state updates in blocks.

![Architecture core concepts](../img/architecture/execution/execution.png)

Transaction execution

Every transaction results in a ZK proof that attests to its correctness.

There are two types of transactions: local and network. For every transaction there is a proof which is either created by the user in the Miden client or by the operator using the Miden node.

Transaction batching

To reduce the required space on the Ethereum blockchain, transaction proofs are aggregated into batches. This can happen in parallel on different machines that need to verify several proofs using the Miden VM and thus creating a proof.

Verifying a STARK proof within the VM is relatively efficient but it is still costly; we aim for 216 cycles.

Block production

Several batch proofs are aggregated into one block. This cannot happen in parallel and must be done by the Miden operator running the Miden node. The idea is the same, using recursive verification.

State progress

Miden has a centralized operator running a Miden node. Eventually, this will be a decentralized function.

Users send either transaction proofs (using local execution) or transaction data (for network execution) to the Miden node. Then, the Miden node uses recursive verification to aggregate transaction proofs into batches.

Batch proofs are aggregated into blocks by the Miden node. The blocks are then sent to Ethereum, and once a block is added to the L1 chain, the rollup chain is believed to have progressed to the next state.

A block produced by the Miden node looks something like this:

![Architecture core concepts](../img/architecture/execution/block.png){ width="80%" }

!!! tip "Block contents" * state updates only contain the hashes of changes. For example, for each updated account, we record a tuple ([account id], [new account hash]). * ZK Proof attests that, given a state commitment from the previous block, there was a sequence of valid transactions executed that resulted in the new state commitment, and the output also included state updates. * The block also contains full account and note data for public accounts and notes. For example, if account 123 is an updated public account which, in the state updates section we'd see a records for it as (123, 0x456..). The full new state of this account (which should hash to 0x456..) would be included in a separate section.

Verifying valid block state

To verify that a block describes a valid state transition, we do the following:

  1. Compute hashes of public account and note states.
  2. Make sure these hashes match records in the state updates section.
  3. Verify the included ZKP against the following public inputs:
    • State commitment from the previous block.
    • State commitment from the current block.
    • State updates from the current block.

The above can be performed by a verifier contract on Ethereum L1.

Syncing to current state from genesis

The block structure has another nice property. It is very easy for a new node to sync up to the current state from genesis.

The new node would need to do the following:

  1. Download only the first part of the blocks (i.e., without full account/note states) starting at the genesis up until the latest block.
  2. Verify all ZK proofs in the downloaded blocks. This is super quick (exponentially faster than re-executing original transactions) and can also be done in parallel.
  3. Download the current states of account, note, and nullifier databases.
  4. Verify that the downloaded current state matches the state commitment in the latest block.

Overall, state sync is dominated by the time needed to download the data.

Limits

The following are the current limits enforced in the miden-base and miden-node:

Accounts

  • Max assets per account: no limit.
  • Max top-level storage slots per account: 255. Each storage slot can contain an unlimited amount of data (e.g., if the storage slot contains an array or a map).
  • Max code size per account: no limit (but we plan to enforce code size limit in the future, at least for public accounts).

Notes

  • Min assets per note: 0.
  • Max assets per note: 255.
  • Max inputs per note: 128. The value can be represented using as a single byte while being evenly divisible by 8.
  • Max code size per note: no limit (but we plan to enforce code size limit in the future, at least for public notes).

Transactions

  • Max input notes per transaction: 1024.
  • Max output notes per transaction: 1024.
  • Max code size of tx script: no limit (but we plan to enforce code size limit in the future).
  • Max number of VM cycles: $2^{30}$.

Batches

  • Max number of input notes: 1024.
  • Max number of output notes: 1024.
  • Max number of accounts: 1024.
  • Max number of VM cycles: $2^{30}$.

Blocks

  • Max batches per block: 64.
  • Max number of accounts: 65536 (max accounts per batch × max number of batches).
  • Max number of input notes: 65536 (max notes per batch × max number of batches).
  • Max number of output notes: 65536 (max notes per batch × max number of batches).
  • Max public data size (for both notes and accounts): no limit.

comments: true

Miden rollup state describes the current condition of all accounts and notes; i.e. what is currently the case.

As the state model uses concurrent off-chain state, Polygon Miden aims for private, and parallel transaction execution and state bloat minimization.

Miden's goals include:

  • Notes and nullifiers to ensure privacy of note consumption.
  • Flexible data storage for users who can store their data off-chain or with the network.
  • Parallel transactions executed concurrently by distinct actors.
  • Concurrent state model that allows block production without knowing the full state.

Privacy is enforced by a UTXO-like state model consisting of notes and nullifiers combined with off-chain execution using zero-knowledge proofs.

State bloat describes the ever growing state stored in blockchain nodes. Polygon Miden addresses this challenge via its state model that enables concurrent off-chain execution and off-chain storage. Simply put, users can store their own data locally which reduces the burden on the network while integrity is ensured using zero-knowledge.

State components

Miden nodes maintain three databases to describe state:

  1. A database of accounts.
  2. A database of notes.
  3. A database of nullifiers for already consumed notes.
![Architecture core concepts](../img/architecture/state/state.png){ width="80%" }

These databases are represented by authenticated data structures that enable easy proof of items added to or removed from a database. This ensures that the commitment to the database remains very small.

Polygon Miden has two databases to capture the note states. The note database is append-only and stores all notes permanently. The nullifier database stores nullifiers that indicate that a note has been previously consumed. Separating note storage into these two databases gives Polygon Miden client-side proving and advanced privacy.

Account database

The latest account states - and data for on-chain accounts - are recorded in a sparse Merkle tree which maps account IDs to account hashes, and account data if needed.

![Architecture core concepts](../img/architecture/state/account-db.png){ width="80%" }

As described in the accounts section, there are two types of accounts:

  • Public accounts where all account data is stored on-chain.
  • Private accounts where only the hashes of accounts are stored on-chain.

Private accounts significantly reduce the storage overhead for nodes. A private account contributes only $40$ bytes to the global state ($8$ bytes account ID + $32$ bytes account hash). Or, said another way, 1 billion private accounts takes up only $40$ GB of state.

!!! warning Losing the state of a private account means loss of funds in a similar manner as a loss of a private key - as the user won't be able to execute transactions. This problem can be mitigated by storing encrypted account state in the cloud or backing it up somewhere else. Unlike storing private keys in the cloud, this does not compromise privacy or account security.

 In the future we hope to enable encrypted accounts where the account data is stored on-chain but in an encrypted format. This is especially interesting for shared accounts like advanced multi-sig wallets.

Note database

Notes are recorded in an append-only accumulator, a Merkle Mountain Range. Each leaf is a block header which contains the commitment to all notes created in that block. The commitment is a Sparse Merkle Tree of all the notes in a block. The size of the Merkle Mountain Range grows logarithmically with the number of items in it.

![Architecture core concepts](../img/architecture/state/note-db.png){ width="80%" }

As described in the notes section, there are two types of notes:

  • Public notes where the entire note content is recorded in the state.
  • Private notes where only a note's hash is recorded in the state.

As with accounts, there is a strong incentive to use private notes as they result in lower fees. This is also beneficial to the network as a private note adds only $64$ bytes to the state ($32$ bytes when it is produced, and $32$ bytes when it is consumed).

Using a Merkle Mountain Range (append-only accumulator) is important for two reasons:

  1. Membership witnesses (that a note exists in the database) against such an accumulator needs to be updated very infrequently.
  2. Old membership witnesses can be extended to a new accumulator value, but this extension does not need to be done by the original witness holder.

Both of these properties are needed for supporting local transactions using client-side proofs and privacy. In an append-only data structure, witness data does not become stale when the data structure is updated. That means users can generate valid proofs even if they don’t have the latest state of this database; so there is no need to query the operator on a constantly changing state.

However, the size of the note database does not grow indefinitely. Theoretically, at high tps, it grows very quickly: at $1$K TPS there are about $1$TB/year added to the database. But, only the unconsumed public notes, and enough info to construct membership proofs against them, need to be stored explicitly. Private notes, as well as public notes which have already been consumed, can be safely discarded. Such notes would still remain in the accumulator, but there is no need to store them explicitly as the append-only accumulator can be updated without knowing about all the items stored in it. This reduces actual storage requirements to a fraction of the database's nominal size.

Nullifier database

Nullifiers are stored in a sparse Merkle tree, which maps note nullifiers to block numbers at which the nullifiers are inserted into the chain (or to 0 for nullifiers which haven't been recorded yet). Nullifiers provide information on whether a specific note has been consumed. The database allows proving that a given nullifier is not in the database.

![Architecture core concepts](../img/architecture/state/nullifier-db.png){ width="80%" }

To prove that a note has not been consumed previously, the operator needs to provide a Merkle path to its node, and then show that the value in that node is 0. In our case nullifiers are $32$ bytes each, and thus, the height of the Sparse Merkle Tree needs to be $256$.

To add new nullifiers to the database, operators need to maintain the entire nullifier set. Otherwise, they would not be able to compute the new root of the tree.

!!! note Nullifiers as constructed in Miden break linkability of privately stored notes and the information about the note's consumption. To know the note's nullifier one must know the note's data.

In the future, when the network experiences a large number of transactions per second (TPS), there will be one tree per epoch (~3 months), and Miden nodes always store trees for at least two epochs. However, the roots of the old trees are still stored. If a user wants to consume a note that is more than $6$ months old, there must be a merkle path provided to the Miden Node for verification.

State bloat minimization

Operators don’t need to know the entire state to verify or produce a new block. No operator is required to store the entire state.

At its core, the idea is simple: Instead of storing the full state data with the operators, the users store their data, and the rollup only keeps track of commitments to the data. At least for private accounts, some smart contracts need to be publicly visible. This minimizes state bloat—as the operator doesn’t need to store an ever-growing database, and provides privacy because all other users and the operator only see a hash of other users’ data.

That way the account and note databases remain manageable, even at high usage for extended periods of time.


comments: true

!!! tip "Recap" Polygon Miden network architecture contains a bi-directional token bridge and state machine.

Miden nodes act as operators that maintain state and compress state transitions recursively into STARK-proofs. The token bridge on Ethereum verifies these proofs. 

Users can run Miden clients to send RPC requests to the Miden nodes to update the state.

The major components of the Polygon Miden network are:

- Miden clients which represent Miden users.
- Miden nodes which manage the Miden rollup and compress proofs.
- A verifier contract which maintains and verifies state on Ethereum.
- A bridge contract as an entry and exit point for users.

Overview of the Miden network

Miden architecture overview

Miden clients

Users run Miden clients and they provide an interface for wallets representing accounts on Miden.

Miden clients can execute and prove transactions with the tx prover. They can handle arbitrary signature schemes. The default is Falcon. There is a wallet user interface, a database that stores account data locally, and the required smart contract code that represents the account on Miden.

Miden nodes

Operators run Miden nodes.

Operators ensure integrity of account, note, and nullifier states - all of which represent the state of Polygon Miden. Operators can execute and prove transactions against single accounts and they can also verify proofs of locally executed transactions.

Furthermore, the operator compresses the proofs in several steps up to a single proof that gets published and verified on the verifier contract. Operators also watch events emitted by the bridge contract to detect deposits and withdrawals.

Node modules

To manage all of this, Miden nodes have separate modules.

  • Tx prover: Executes and proves transactions, like the Miden client.
  • Tx aggregator: Batches multiple proofs together to reduce the final state proof size using recursive proving.
  • Block producer: exposes the RPC interface to the user and collects transactions in the tx pool and stores the state of Polygon Miden in its three databases (accounts, notes, and nullifiers).

Verifier contract

This contract on Ethereum verifies proofs sent by the operator running a Miden Node. The proof is verified against the current state root. If accepted the state root changes.

!!! note - Polygon Miden will integrate into the AggLayer. - The specific design is not yet finalized.

Bridge contract

This contract serves as a bridge for Miden users on Ethereum. Users can deposit their tokens and get an equivalent amount minted and sent to the specified address on Polygon Miden.

!!! note - Polygon Miden will integrate into the AggLayer and the Unified Bridge. - The specific design is not yet finalized.


comments: true

Context overview

Miden assembly program execution, the code the transaction kernel runs, spans multiple isolated contexts. An execution context defines its own memory space which is inaccessible from other execution contexts. Note scripts cannot directly write to account data which should only be possible if the account exposes relevant functions.

Specific contexts

The kernel program always starts executing from a root context. Thus, the prologue sets the memory for the root context. To move execution into a different context, the kernel invokes a procedure using the call or dyncall instruction. In fact, any time the kernel invokes a procedure using the call instruction, it executes in a new context.

While executing in a note, account, or tx script context, the kernel executes some procedures in the kernel context, which is where all necessary information was stored during the prologue. The kernel switches context via the syscall instruction. The set of procedures invoked via the syscall instruction is limited by the transaction kernel API. When the procedure called via syscall returns, execution moves back to the note, account, or tx script where it was invoked.

Context switches

![Transaction contexts](../../img/architecture/transaction/transaction-contexts.png)

The above diagram shows different context switches in a simple transaction. In this example, an account consumes a P2ID note and receives the asset into its vault. As with any MASM program, the transaction kernel program starts in the root context. It executes the prologue and stores all necessary information into the root memory.

The next step, note processing, starts with a dyncall which invokes the note script. This command moves execution into a different context (1). In this new context, the note has no access to the kernel memory. After a successful ID check, which changes back to the kernel context twice to get the note inputs and the account id, the script executes the add_note_assets_to_account procedure.

# 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.
# ...
begin

    ... <check correct ID>

    exec.add_note_assets_to_account
    # => [...]
end

The procedure cannot simply add assets to the account, because it is executed in a note context. Therefore, it needs to call the account interface. This moves execution into a second context - account context - isolated from the note context (2).

#! Helper procedure to add all assets of a note to an account.
#! ...
proc.add_note_assets_to_account
    ...

    while.true
        ...

        # load the asset and add it to the account
        mem_loadw call.wallet::receive_asset
        # => [ASSET, ptr, end_ptr, ...]
        ...
    end
    ...
end

The wallet smart contract provides an interface that accounts use to receive and send assets. In this new context, the wallet calls the add_asset procedure of the account API.

export.receive_asset
    exec.account::add_asset
    ...
end

The account API exposes procedures to manage accounts. This particular procedure that was called by the wallet invokes a syscall to return back to the root context (3), where the account vault is stored in memory (see prologue). Procedures defined in the Kernel API should be invoked with syscall using the corresponding procedure offset and the exec_kernel_proc kernel procedure.

#! Add the specified asset to the vault.
#! ...
export.add_asset
    exec.kernel_proc_offsets::account_vault_add_asset_offset
    syscall.exec_kernel_proc
end

Now, the asset can be safely added to the vault within the kernel context, and the note can be successfully processed.



comments: true

Transactions overview

Architecture overview

The Miden transaction architecture comprises a set of components that interact with each other. This section of the documentation discusses each component.

The diagram shows the components responsible for Miden transactions and how they fit together.

![Transactions architecture overview](../../img/architecture/transaction/tx-overview.png)

!!! tip "Key to diagram" - The transaction executor prepares, executes, and proves transactions. - The executor compiles the transaction kernel plus user-defined notes and transaction scripts into a single executable program for the Miden VM. - Users write scripts using kernel procedures and contexts.

Miden transactions

Transactions in Miden facilitate single account state changes. Miden requires two transactions to transfer assets between accounts.

A transaction takes a single account and some notes as input, and outputs the same account with a new state, together with some other notes.

Miden aims for the following:

  • Parallel transaction execution: Because a transaction is always performed against a single account, Miden obtains asynchronicity.
  • Private transaction execution: Because every transaction emits a state-change with a STARK proof, there is privacy when the transaction executes locally.

There are two types of transactions in Miden: local transactions and network transactions.

Transaction design

Transactions describe the state-transition of a single account that takes chain data and 0 to 1024 notes as input and produces a TransactionWitness and 0 to 1024 notes as output.

![Transaction diagram](../../img/architecture/transaction/transaction-diagram.png){ width="75%" }

At its core, a transaction is an executable program - the transaction kernel program - that processes the provided inputs and creates the requested outputs. Because the program is executed by the Miden VM, a STARK-proof is generated for every transaction.

Asset transfer using two transactions

Transferring assets between accounts requires two transactions as shown in the diagram below.

![Transaction flow](../../img/architecture/transaction/transaction-flow.png)

The first transaction invokes some functions on account_a (e.g. create_note and move_asset_to_note functions) which creates a new note and also updates the internal state of account_a. The second transaction consumes the note which invokes a function on account_b (e.g. a receive_asset function) which updates the internal state of account_b.

Asynchronous execution

Both transactions can be executed asynchronously: first transaction1 is executed, and then, some time later, transaction2 is executed.

This opens up a few interesting possibilities:

  • Owner of account_b may wait until they receive many notes and process them all in a single transaction.
  • A note script may include a clause which allows the source account to consume the note after some time. Thus, if account_b does not consume the note after the specified time, the funds can be returned. This mechanism can be used to make sure funds sent to non-existent accounts are not lost (see the P2IDR note script).
  • Neither sender nor the recipient need to know who the other side is. From the sender's perspective they just need to create note1 (and for this they need to know the assets to be transferred and the root of the note's script). They don't need any information on who will eventually consume the note. From the recipient's perspective, they just need to consume note1. They don't need to know who created it.
  • Both transactions can be executed "locally". For example, we could generate a zk-proof that transaction1 was executed and submit it to the network. The network can verify the proof without the need for executing the transaction itself. The same can be done for transaction2. Moreover, we can mix and match. For example, transaction1 can be executed locally, but transaction2 can be executed on the network, or vice-versa.

Local and network transactions

![Local vs network transactions](../../img/architecture/transaction/local-vs-network-transaction.png)

Local transactions

This is where clients executing the transactions also generate the proofs of their correct execution. So, no additional work needs to be performed by the network.

Local transactions are useful for several reasons:

  1. They are cheaper (i.e. lower fees) as zk-proofs are already generated by the clients.
  2. They allow fairly complex computations because the proof size doesn't grow linearly with the complexity of the computation.
  3. They enable privacy as neither the account state nor account code are needed to verify the zk-proof.

Network transactions

This is where the operator executes the transaction and generates the proofs.

Network transactions are useful for two reasons:

  1. Clients may not have sufficient resources to generate zk-proofs.
  2. Executing many transactions against the same public account by different clients is challenging, as the account state changes after every transaction. Due to this, the Miden node/operator acts as a "synchronizer" to execute transactions sequentially by feeding the output of the previous transaction into the input of the next.


comments: true

The transaction kernel program, written in MASM, is responsible for executing a Miden rollup transaction within the Miden VM. It is defined as MASM kernel.

The kernel provides context-sensitive security which prevents unwanted read and write access. It defines a set of procedures which can be invoked from other contexts; e.g. notes executed in the root context.

In general, the kernel's procedures must reflect everything users might want to do while executing transactions; from transferring assets to complex smart contract interactions with custom code.

!!! info - Learn more about Miden transaction procedures and contexts.

The kernel has a well-defined structure which does the following:

  1. The prologue prepares the transaction for processing by parsing the transaction data and setting up the root context.
  2. Note processing executes the note processing loop which consumes each InputNote and invokes the note script of each note.
  3. Transaction script processing executes the optional transaction script.
  4. The epilogue finalizes the transaction by computing the output notes commitment, the final account hash, asserting asset invariant conditions, and asserting the nonce rules are upheld.
![Transaction program](../../img/architecture/transaction/transaction-program.png)

Input

The transaction kernel program receives two types of inputs, public inputs via the operand_stack and private inputs via the advice_provider.

The operand stack holds the global inputs which serve as a commitment to the data being provided via the advice provider.

The advice provider holds data of the last known block, account and input note data. The details are laid out in the next paragraph.

Prologue

The transaction prologue executes at the beginning of a transaction.

It performs the following tasks:

  1. Unhashes the inputs and lays them out in the root context memory.
  2. Builds a single vault (tx vault) containing assets of all inputs (input notes and initial account state).
  3. Verifies that all input notes are present in the note DB.

In other words, the prologue stores all provided information from the inputs and the advice provider into the appropriate memory slots. It then reads the data for the account and notes from the advice provider, writes it to memory, hashes it, and verifies that the resulting hash matches the commitments provided via the stack. Finally, it creates a single vault for the assets that are involved.

The diagram below shows the memory layout. The kernel context has access to all memory slots.

![Memory layout kernel](../../img/architecture/transaction/memory-layout-kernel.png)

Bookkeeping section

The bookkeeping section keeps track of variables which are used internally by the transaction kernel.

Global inputs

These are stored in the pre-defined memory slots.

Global inputs come from the operand_stack and go to the VM at transaction execution. They include the block hash, the account ID, the initial account hash, and the nullifier commitment. This is a sequential hash of all (nullifier, ZERO) pairs for the notes consumed in the transaction.

Block data

The block data processing involves reading the block data from the advice provider and storing it at the appropriate memory addresses. Block data comes from the latest known block and consists of note, state and tx hash, the block's previous hash and proof hash, as well as the block number. As the data is read from the advice provider, the block hash is computed. It is asserted that the computed block hash matches the block hash stored in the global inputs.

Chain data

Chain data is processed in a similar way to block data. In this case the chain root is recomputed and compared against the chain root stored in the block data section.

Account data

Account data processing involves reading the data from the advice provider and storing it at the appropriate memory addresses. The account data consists of account vault roots, storage, and code.

As the account data is read from the advice provider, the account hash is computed. If the account is new then the global initial account hash is updated and the new account is validated. If the account already exists then the computed account hash is asserted against the account hash provided via global inputs. It is also asserted that the account id matches the account id provided via the global inputs (operand_stack).

Input note data

Input note processing involves the kernel reading the data from each note and storing it at the appropriate memory addresses. All the data (note, account, and blockchain data) comes from the advice provider and global inputs.

Next to the total number of input notes, input note data consists of a serial number, the roots of the script, the inputs and asset vault, its metadata, and all its assets.

As each note is consumed, its hash and nullifier are computed.

The transaction nullifier commitment is computed via a sequential hash of (nullifier, ZERO) pairs for all input notes. This step involves authentication such that the input note data provided via the advice provider is consistent with the chain history.

!!! info - Note data is required for computing the nullifier, e.g. the note script and the serial number. - The system needs to know the note data to execute the prologue of a transaction. This is how the note recipient defines the set of users who can consume a specific note. - The executing account provides the pre-image data to the recipient at the time of execution.

If a transaction script is provided, its root is stored at a pre-defined memory address.

Note processing

Input notes are consumed in a loop.

For every note, the MAST root of the note script is loaded onto the stack. Then, by calling a dyncall the note script is executed in a new context which prevents unwanted memory access.

    # loop while we have notes to consume
    while.true
        # execute the note setup script
        exec.note::prepare_note
        # => [note_script_root_ptr, NOTE_ARGS]

        # invoke the note script using the dyncall instruction
        dyncall
        # => [OUTPUT_3, OUTPUT_2, OUTPUT_1, OUTPUT_0]

        # clean up note script outputs
        dropw dropw dropw dropw
        # => []

        # check if we have more notes to consume and should loop again
        exec.note::increment_current_input_note_ptr
        loc_load.0
        neq
        # => [should_loop]
    end

When processing a note, new note creation might be triggered. If so, all necessary information about the new note is stored in the output note data in memory.

!!! info - The Miden transaction kernel program prevents notes from having direct access to account storage. - Notes can only call the account interface to trigger write operations in the account.

Transaction script processing

If a transaction script is provided with the transaction, it is processed after all notes are consumed. By loading the transaction script root onto the stack, the kernel can invoke a dyncall and in doing so execute the script. The transaction script is then again executed in its own context.

The transaction script can be used to authenticate the transaction by increasing the account's nonce and signing the transaction, as in the following example:

    use.miden::contracts::auth::basic->auth_tx

    begin
        call.auth_tx::auth_tx_rpo_falcon512
    end

!!! note - The executing account must expose the auth_tx_rpo_falcon512 function in order for the transaction script to call it.

Epilogue

The epilogue finalizes the transaction. It does the following:

  1. Computes the final account hash.
  2. If the account has changed, it asserts that the final account nonce is greater than the initial account nonce.
  3. Computes the output notes commitment.
  4. Asserts that the input and output vault roots are equal.

There is an exception for special accounts, called faucets, which can mint or burn assets. In these cases, input and output vault roots are not equal.

Outputs

The transaction kernel program outputs the transaction script root, a commitment of all newly created outputs notes, and the account hash in its new state.



comments: true

There are user-facing procedures and kernel procedures. Users don't directly invoke kernel procedures, but instead they invoke them indirectly via account code, note, or transaction scripts. In these cases, kernel procedures are invoked by a syscall instruction which always executes in the kernel context.

User-facing procedures (APIs)

These procedures can be used to create smart contract/account code, note scripts, or account scripts. They basically serve as an API for the underlying kernel procedures. If a procedure can be called in the current context, an exec is sufficient. Otherwise the context procedures must be invoked by call. Users never need to invoke syscall procedures themselves.

!!! tip If capitalized, a variable representing a word, e.g., ACCT_HASH consists of four felts. If lowercase, the variable is represented by a single felt.

Account

To import the account procedures, set use.miden::account at the beginning of the file.

Any procedure that changes the account state must be invoked in the account context and not by note or transaction scripts. All procedures invoke syscall to the kernel API and some are restricted by the kernel procedure exec.authenticate_account_origin, which fails if the parent context is not the executing account.

Procedure nameStackOutputContextDescription
get_id[][acct_id]account, note
  • Returns the account id.
get_nonce[][nonce]account, note
  • Returns the account nonce.
get_initial_hash[][H]account, note
  • Returns the initial account hash.
get_current_hash[][ACCT_HASH]account, note
  • Computes and returns the account hash from account data stored in memory.
incr_nonce[value][]account
  • Increments the account nonce by the provided value which can be at most 2^32 - 1 otherwise the procedure panics.
get_item[index][VALUE]account, note
  • Gets an item VALUE by index from the account storage.
  • Panics if the index is out of bounds.
set_item[index, V'][R', V]account
  • Sets an index/value pair in the account storage.
  • Panics if the index is out of bounds. R is the new storage commitment.
set_code[CODE_COMMITMENT][]account
  • Sets the code (CODE_COMMITMENT) of the account the transaction is being executed against.
  • This procedure can only be executed on regular accounts with updatable code. Otherwise, the procedure fails.
get_balance[faucet_id][balance]account, note
  • Returns the balance of a fungible asset associated with a faucet_id.
  • Panics if the asset is not a fungible asset.
has_non_fungible_asset[ASSET][has_asset]account, note
  • Returns a boolean has_asset indicating whether the non-fungible asset is present in the vault.
  • Panics if the ASSET is a fungible asset.
add_asset[ASSET][ASSET']account
  • Adds the specified asset ASSET to the vault. Panics under various conditions.
  • If ASSET is a non-fungible asset, then ASSET' is the same as ASSET.
  • If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault after ASSET was added to it.
remove_asset[ASSET][ASSET]account
  • Removes the specified ASSET from the vault.
  • Panics under various conditions.
get_vault_commitment[][COM]account, note
  • Returns a commitment COM to the account vault.

Note

To import the note procedures, set use.miden::note at the beginning of the file. All procedures are restricted to the note context.

Procedure nameInputsOutputsContextDescription
get_assets[dest_ptr][num_assets, dest_ptr]note
  • Writes the assets of the currently executing note into memory starting at the specified address dest_ptr .
  • num_assets is the number of assets in the currently executing note.
get_inputs[dest_ptr][dest_ptr]note
  • Writes the inputs of the currently executed note into memory starting at the specified address, dest_ptr.
get_sender[][sender]note
  • Returns the sender of the note currently being processed. Panics if a note is not being processed.
compute_inputs_hash[inputs_ptr, num_inputs][HASH]note
  • Computes hash of note inputs starting at the specified memory address.

Tx

To import the transaction procedures set use.miden::tx at the beginning of the file. Only the create_note procedure is restricted to the account context.

Procedure nameInputsOutputsContextDescription
get_block_number[][num]account, note
  • Returns the block number num of the last known block at the time of transaction execution.
get_block_hash[][H]account, note
  • Returns the block hash H of the last known block at the time of transaction execution.
get_input_notes_hash[][COM]account, note
  • Returns the input notes hash COM.
  • This is computed as a sequential hash of (nullifier, empty_word_or_note_hash) tuples over all input notes. The empty_word_or_notes_hash functions as a flag, if the value is set to zero, then the notes are authenticated by the transaction kernel. If the value is non-zero, then note authentication will be delayed to the batch/block kernel. The delayed authentication allows a transaction to consume a public note that is not yet included to a block.
get_output_notes_hash[0, 0, 0, 0][COM]account, note
  • Returns the output notes hash COM.
  • This is computed as a sequential hash of (note_id, note_metadata) tuples over all output notes.
create_note[ASSET, tag, RECIPIENT][ptr]account
  • Creates a new note and returns a pointer to the memory address at which the note is stored.
  • ASSET is the asset to be included in the note.
  • tag is the tag to be included in the note. RECIPIENT is the recipient of the note.
  • ptr is the pointer to the memory address at which the note is stored.

Asset

To import the asset procedures set use.miden::asset at the beginning of the file. These procedures can only be called by faucet accounts.

Procedure nameStackOutputContextDescription
build_fungible_asset[faucet_id, amount][ASSET]faucet
  • Builds a fungible asset ASSET for the specified fungible faucet faucet_id, and amount of asset to create.
create_fungible_asset[amount][ASSET]faucet
  • Creates a fungible asset ASSET for the faucet the transaction is being executed against and amount of the asset to create.
build_non_fungible_asset[faucet_id, DATA_HASH][ASSET]faucet
  • Builds a non-fungible asset ASSET for the specified non-fungible faucet.
  • faucet_id is the faucet to create the asset for.
  • DATA_HASH is the data hash of the non-fungible asset to build.
create_non_fungible_asset[DATA_HASH][ASSET]faucet
  • Creates a non-fungible asset ASSET for the faucet the transaction is being executed against.
  • DATA_HASH is the data hash of the non-fungible asset to create.

Faucet

To import the faucet procedures, set use.miden::faucet at the beginning of the file.

Procedure nameStackOutputsContextDescription
mint[ASSET][ASSET]faucet
  • Mint an asset ASSET from the faucet the transaction is being executed against.
  • Panics under various conditions.
burn[ASSET][ASSET]faucet
  • Burn an asset ASSET from the faucet the transaction is being executed against.
  • Panics under various conditions.
get_total_issuance[][total_issuance]faucet
  • Returns the total_issuance of the fungible faucet the transaction is being executed against.
  • Panics if the transaction is not being executed against a fungible faucet.

comments: true

The Miden transaction executor is the component that executes transactions.

Transaction execution consists of the following steps and results in an ExecutedTransaction object:

  1. Fetch the data required to execute a transaction from the data store.
  2. Compile the transaction into an executable MASM program using the transaction compiler.
  3. Execute the transaction program and create an ExecutedTransaction object.
  4. Prove the ExecutedTransaction using the transaction prover.
![Transaction execution process](../../img/architecture/transaction/transaction-execution-process.png)

One of the main reasons for separating out the execution and proving steps is to allow stateless provers; i.e. the executed transaction has all the data it needs to re-execute and prove a transaction without database access. This supports easier proof-generation distribution.

Data store and transaction inputs

The data store defines the interface that transaction objects use to fetch the data for transaction executions. Specifically, it provides the following inputs to the transaction:

  • Account data which includes the AccountID and the AccountCode that is executed during the transaction.
  • A BlockHeader which contains metadata about the block, commitments to the current state of the chain, and the hash of the proof that attests to the integrity of the chain.
  • A ChainMmr which authenticates input notes during transaction execution. Authentication is achieved by providing an inclusion proof for the transaction's input notes against the ChainMmr-root associated with the latest block known at the time of transaction execution.
  • InputNotes consumed by the transaction that include the corresponding note data, e.g. the note script and serial number.

!!! note - InputNotes must be already recorded on-chain in order for the transaction to succeed. - There is no nullifier-check during a transaction. Nullifiers are checked by the Miden operator during transaction verification. So at the transaction level, there is "double spending".

Transaction compiler

Every transaction is executed within the Miden VM to generate a transaction proof. In Miden, there is a proof for every transaction.

The transaction compiler is responsible for building executable programs. The generated MASM programs can then be executed by the Miden VM which generates a zk-proof. In addition to transaction compilation, the transaction compiler provides methods for compiling Miden account code, note scripts, and transaction scripts.

Compilation results in an executable MASM program. The program includes the provided account interface and notes, an optional transaction script, and the transaction kernel program. The transaction kernel program defines procedures and the memory layout for all parts of the transaction.

After compilation, assuming correctly-populated inputs, including the advice provider, the transaction can be executed.

Executed transactions and the transaction outputs

The ExecutedTransaction object represents the result of a transaction not its proof. From this object, the account and storage delta can be extracted. Furthermore, the ExecutedTransaction is an input to the transaction prover.

A successfully executed transaction results in a new account state which is a vector of all created notes (OutputNotes) and a vector of all the consumed notes (InputNotes) together with their nullifiers.

Transaction prover

The transaction prover proves the inputted ExecutedTransaction and returns a ProvenTransaction object. The Miden node verifies the ProvenTransaction object using the transaction verifier and, if valid, updates the state databases.