Polygon Miden

Miden is a zero-knowledge rollup for high-throughput and private applications. Miden allows users to prove state changes of local data where the network only tracks a commitment of it. This leads to privacy and high-throughput. We think, Privacy Scales Better. Users can also let the Operator prove public state changes as in other known rollups.

Polygon Miden is a modular execution layer that extends Ethereum's capabilities using powerful features such as parallel transaction execution and client-side proving. With Miden, developers can create novel, high-throughput, privacy preserving dApps for DeFi, RWA and Autonomous Worlds using their favorite languages such as Rust and TypeScript.

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

This documentation is still Work In Progress. Some topics have been discussed in greater depth, while others require additional clarification. Sections of this documentation might later be reorganized in order to achieve a better flow.

Status and features

Polygon Miden is currently on release v0.1. This is an early version of the protocol and its components. We expect to keep making even breaking changes to all components.

At this point, adventurous Pioneers can execute first transactions and send assets to each other. Polygon Miden doesn't offer all the features one would expect from a zkRollup, yet. During 2024, we expect to offer gradually more features. Eventually, developers should be able to code any application they want on Polygon Miden.

Feature highlights

  • Private accounts. The Miden Operator only tracks a commitment to any account data in the public database. Users can only execute smart contracts of which they know the interface.
  • Private notes. Like private accounts, the Miden Operator only tracks a commitment to any notes in the public database. Users need to communicate note details to each other off-chain (via any side channel) in order to consume private notes in transactions.
  • Local transaction execution. The Miden Client allows for local transaction execution and proving. The Miden Operator verifies the proof and if valid, the state DBs are updated with the new data.
  • Simple smart contracts. Currently, there are three different smart contracts available. A basic wallet smart contracts to send and receive 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 is able to build blocks containing transactions. There is no recursive verification of transactions enabled yet.
  • Maintaining state. The Miden Node stores all necessary information already in its State DBs and provides this infos via its RPC endpoint.

Planned features

  • Public accounts. Polygon Miden will support public smart contracts as know on Ethereum. Code and state of those accounts will be visible to the network and anyone can execute transactions against them.
  • Public notes. As with public accounts, also public notes will be supported. That means, note data will be publicly stored by the Miden Operator. Note consumption will not be private.
  • Customized smart contracts. Accounts can expose any interface in the future. This is the Miden version of a smart contract. Account code can be arbitrary 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 arbitrary 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 infeasible on other systems, e.g.
    • onchain order book exchange due to parallel tx execution and updatable transactions
    • complex, incomplete information games due to client-side proving and cheap complex computations
    • safe wallets due to assets being stored in the accounts and account state can be hidden
  • Better privacy properties than on Ethereum - first web2 privacy, later even stronger privacy guarantees
  • 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.

Overview

This documentation presents detailed guides on:

Getting started provides an hands-om guide on how builders can use our testnet.

Architecture describes Miden's unique state and execution model - an actor-based model with concurrent off-chain state.

Network design describes how the architecture is implemented into a zero-knowledge rollup. It describes the software components, RPC interfaces of the Miden Nodes and Cliets.

Roadmap shows the timeline for user facing features that users can expect.

Getting started

This tutorial guides you through the process of connecting to a remote Miden node using the Miden client. The Miden node processes transactions and creates blocks for the Miden rollup. The Miden client provides a way to execute and prove transactions, facilitating the interaction with the Miden rollup. By the end of this tutorial, you will be able to configure the Miden client, connect to a Miden node, and perform basic operations like sending transactions, generating and consuming notes.

Prerequisites

Before starting, ensure you have the following:

  • Rust Installed: You must have the Rust programming language installed on your machine. If you haven't installed Rust, you can download it from the Rust website.
  • Node IP Address: Obtain the IP address of the running Miden node. This information can be acquired by contacting one of the Miden engineers.
  • Miden client Installation: You need to install the Miden client and configure it to point to the remote node.

Step 1: Configuring the Miden client

  1. Download the Miden client: First, download the Miden client from its repository. Use the following command:

    git clone https://github.com/0xPolygonMiden/miden-client
    
  2. Navigate & Configure the client: Navigate to the client directory and modify the configuration file to point to the remote Miden node. You can find the configuration file at ./miden-client.toml. In the [RPC] section replace the endpoint = { host: } field with the address provided by the Miden engineer.

    cd miden-client
    

    Configuration file example:

     [rpc]
     endpoint = { protocol = "http", host = "<NODE_IP_ADDRESS>", port = 57291 }
    
     [store]
     database_filepath = "store.sqlite3"
    
  3. Build & install the Client: install the client using cargo:

    cargo install --features testing,concurrent --path .
    

    you should now be able to use the following command:

    miden-client --help
    

Step 2: Setting-up the Miden client

  1. Creating new accounts: To be able to interact with the Miden node we will need to generate accounts. For this example we will be generating 3 accounts: basic-immutable account A, basic-immutable account B and a fungible-faucet. You can generate new accounts using the following commands:

    miden-client account new basic-immutable
    miden-client account new basic-immutable
    miden-client account new fungible-faucet [...]
    

    Please refer to the documentation of the CLI

  2. Listing accounts: To view the newly created accounts we can run the following command:

    miden-client account -l
    

    We should now see 3 available accounts listed:

    • basic-immutable account A
    • basic-immutable account B
    • fungible-faucet account
  3. Syncing node state: The client needs to periodically query the node to receive updates about entities that might be important in order to run transactions. The way to do so is by running the sync command:

    miden-client sync
    

Step 3: Minting an asset

Since we have now synced our local view of the blockchain and have account information, we are ready to execute and submit tranasctions. For a first test, we are going to mint a fungible asset for a regular account.

miden-client tx new mint <regular-account-id-A> <faucet-account-id> 1000

This will execute, prove and submit a transaction that mints assets to the node. The account that executes this transaction will be the faucet as was defined in the node's configuration file. In this case, it is minting 1000 fungible tokens to <regular-account-id-A>.

This will add a transaction and an output note (containing the minted asset) to the local store in order to track their lifecycles. You can display them by running miden-client tx list and miden-client input-notes list respectively. If you do so, you will notice that they do not show a commit height even though they were submitted to the operator. This is because our local view of the network has not yet been updated. After updating it with a sync, you should see the height at which the transaction and the note containing the asset were committed. This will allow us to prove transactions that make use of this note, as we can compute valid proofs that state that the note exists in the blockchain.

Step 4: Consuming the note

After creating the note with the minted asset, the regular account can now consume it and add the tokens to its vault. You can do this the following way:

miden-client tx new consume-notes <regular-account-id-A> <input-note-id>

This will consume the input note, which you can get by listing them as explained in the previous step. You will now be able to see the asset in the account's vault by running:

miden-client account show <regular-account-id-A> -v

Step 5: Transferring assets between accounts

Some of the tokens we minted can now be transferred to our second regular account. To do so, you can run:

miden-client sync # Make sure we have an updated view of the state
miden-client tx new p2id <regular-account-id-A> <regular-account-id-B> <faucet-account-id> 50 # Transfers 50 tokens to account ID B

This will generate a Pay-to-ID (P2ID) note containing 50 assets, transferred from one regular account to the other. If we sync, we can now make use of the note and consume it for the receiving account:

miden-client sync # Make sure we have an updated view of the state
miden-client input-notes list # Now use the second note id
miden-client tx new consume-notes <regular-account-ID-B> <input-note-id> # Consume the note

That's it! You will now be able to see 950 fungible tokens in the first regular account, and 50 tokens in the remaining regular account:

miden-client account show <regular-account-ID-B> -v # Show account B's vault assets (50 fungible tokens)
miden-client account show <regular-account-ID-A> -v # Show account A's vault assets (950 fungible tokens)

Clearing the state

All state is maintained in store.sqlite3, located in the directory defined in the miden-client.toml file. In case it needs to be cleared, the file can be deleted; it will later be created again when any command is executed.

Conclusion

Congratulations! You have successfully configured and used the Miden client to interact with a Miden node. With these steps, you can perform basic Miden rollup operations like sending transactions, generating and consuming notes.

For more information on the Miden client, refer to the Readme of the Miden Client

For more information on the Miden rollup, refer to the Miden documentation.

Architecture

The Polygon Miden Architecture describes the concepts of how the participants of the network can interact.

The architecture reflects the design goals for the rollup:

  • High throughput
  • Privacy
  • Asset safety

Inspired by the Actor model

The Actor Model inspired Miden to achieve concurrent and local state changes. In the model, actors are little state machines with inboxes, meaning each actor is responsible for their own state. Actors can send and receive messages to communicate with other actors. Messages can be read asynchronously.

Core Concepts in Miden

In Miden, there are accounts and notes which can hold assets. Accounts consume and produce notes in transactions. Transactions describe 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. This chapter describes the design, the storage types, and the creation of an account.

Notes

Notes are messages that accounts send to each other. A note stores assets and a script that defines how this note can be consumed. This 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. This chapter describes asset issuance, customization, and storage.

Transactions

Transactions describe production and consumption of notes by a single account. Executing a transaction always results in a STARK proof. This chapter describes the transaction design and the implementation thereof. At it's core, transaction execution happens in the transaction kernel program which is explained in depth.

Accounts produce and consume notes to communicate

State and Execution

The state model captures all individual states of all accounts and notes. Finally, 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 onchain and offchain. This chapter describes the three different state databases in Miden.

Execution model

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

Operators capture and progress state

Architecture tradeoffs

Want to know more on why we designed Miden as is?

Polygon Miden's architecture

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. User generated zero-knowledge proofs allow state transitions, e.g. transactions, to be verifiable without being transparent.

Actor-based execution model

The actor model inspires Polygon Miden’s execution model. This is a well-known 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.

Hybrid state model

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.

Accounts

Miden aims to support expressive smart contracts via a Turing-complete language. For smart contracts the go-to solution is account-based state. In Miden, an account is an entity which holds assets and defines rules of how these assets can be transferred. They are basic building blocks representing a user or an autonomous smart contract.

Account Design

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

In the above picture, you can see:

  • Account ID → a unique identifier of an account which does not change throughout its lifetime
  • Storage → user-defined data which can be stored in an account
  • Nonce → a counter which must be incremented 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

~63 bits (1 field element) long identifier for the account. The four most significant bits specify its account type - regular, immutable, faucet - and the storage mode - public or private.

Account Storage

Storage for user-defined data. AccountStorage is composed of two components.

The first component is a simple Sparse Merkle Tree of depth 8 which is index addressable. This provides the user with 256 Word slots.

Users requiring additional storage can use the second component a MerkleStore. It allows users to store any Merkle structures they need. The root of the Merkle structure can be stored as a leaf in a simple Sparse Merkle Tree. When AccountStorage is serialized it will check if any of the leafs in the simple Sparse Merkle Tree are Merkle roots of other Merkle structures. If any Merkle roots are found then the Merkle structures will be persisted in the AccountStorage MerkleStore.

Nonce

Counter which must be incremented whenever the account state changes. Nonce values must be strictly monotonically increasing and can be incremented by any value smaller than 2^{32} for every account update.

Vault

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

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, 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 (Regular updatable account)

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

Want to see the code?
  use.miden::contracts::wallets::basic->basic_wallet
  use.miden::contracts::auth::basic

  export.basic_wallet::receive_asset
  export.basic_wallet::send_asset
  export.basic::auth_tx_rpo_falcon512

Note scripts or transaction scripts can call receive_asset and send_asset and in doing so, the account can receive and send assets. Transaction scripts can also call auth_tx_rpo_falcon512 and authenticate the transaction. It is important to know, that without correct authentication, i.e. knowing the correct private key, a note cannot successfully invoke receive and send asset.

Basic fungible faucet (Faucet for fungible assets)

There is also a standard for a basic fungible faucet.

Want to see the 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, 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, RECIPIENT, ...]

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

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

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

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

    # create a note containing the asset
    exec.tx::create_note
    # => [note_ptr, ZERO, 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, tag, RECIPIENT.

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

Info: The difference is that 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 DB kept by the Miden node(s). However, new accounts can be created locally by users using a wallet.

The process is as follows:

  • Alice grinds a new Account ID (according to the account types) using a wallet
  • Alice's Miden client requests the Miden node to check if new Account ID already exists
  • Alice shares the new Account ID to 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 receives the new Account ID eventually and - if the transaction is correct - adds the ID to the Account DB

For a user to create an account we have 2 solutions at the moment:

  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 four types of accounts in Miden:

Regular updatable accountRegular immutable accountFaucet for fungible assetsFaucet for non-fungible assets
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

Account storage modes

Account data - stored by the Miden node - can be public, private, or encrypted. The third and fourth most significant bits of the account ID specifies whether the account data is public 00, encrypted 01, or private 11.

  • Accounts with public state, where the actual state is stored onchain. These would be similar to how accounts work in public blockchains. Smart contracts that depend on public shared state should be stored public on Miden, e.g., DEX contract.
  • Accounts with private state, where only the hash of the account is stored onchain. Users who want stay private and take care of their own data should choose this mode. The hash is defined as: hash([account ID, 0, 0, nonce], [vault root], [storage root], [code root]).

In the future we will also support encrypted state which will be onchain but encrypted. * Depending on the account storage mode (private vs. encrypted vs. public) and transaction type (local vs. network) the operator receives the new Account ID eventually and - if the transaction is correct - adds the ID to the Account DB

Notes

Miden aims to achieve parallel transaction execution and privacy. The UTXO-model combined with client-side proofs provide those features. That means, in Miden exist notes as a way of transferring assets between and to interact with accounts. Notes can be consumed and produced asynchronously and privately. The concept of notes is a key difference between Ethereum’s Account-based model and Polygon Miden, which uses a hybrid UTXO- and Account-based state-model.

Note design

In Polygon Miden, accounts communicate with one another by producing and consuming notes. A note stores assets and a script that defines how this note can be consumed.

The diagram below illustrates the contents of a note:

As shown in the above picture:

  • Assets → serves as 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 → will 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 for the note script execution. They can be accessed by the note script via transaction kernel procedures. The number is limited to 16 and they must be defined at note creation.
  • Serial number → a note's unique identifier to break linkability 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's 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 DB. 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 DB to "consumed".

The following sections will explain, how notes are created, stored, discovered and consumed.

Note creation

Notes are created as outputs (OutputNotes) of Miden transactions. Operators record those notes to the Note DB, 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 the transferring of assets, they 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 would change. 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 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 exist 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.

Example note script Pay to ID (P2ID)

Want to know more how to ensure a note can only be consumed by a specified account?

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.

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

As discussed in detail in transaction kernel procedures certain procedures can only be invoked in certain contexts. The note script is being executed in the note context of the transaction kernel.

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).

# 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

Every note script starts with the note script root on top of the stack. After the dropw, the stack is cleared. Next, the script stored the note inputs at pos 0 in the relative note context memory by push.0 exec.note::get_inputs. 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. 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.

#! 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 are represented by Words, so we need to pad the stack with four 0s 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, a note script cannot directly call an account function to add the asset. The account must expose this function in its interface.

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. Notes can be stored on-chain in the Note DB with all data publicly visible for everyone. Alternatively, notes can be stored off-chain by committing only the note hash to the Note DB.

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 discovery describes the process of Miden clients finding notes they want to consume. There are two ways to receive new relevant notes - getting notes via an off-chain channel or querying the Miden operator to request newly recorded relevant notes. The latter is done via note tags. Tags are part of the note's metadata and are represented by a Felt. The SyncState API of the Miden node requires the Miden client to provide a note_tag value which is used as a filter in the operator's response. Tags are useful for note discovery enabling an easy collection of all notes matching a certain tag.

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 DB 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 on off-chain by 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.

Assets

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.

Recording of native assets in Polygon Miden suffices four goals:

  • Asset exchange should be parallelizable
  • Asset ownership should be private
  • Asset usage should be indeed 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 ownership of assets using global hashmaps, e.g., ERC20 contracts. Storage of assets locally in accounts provides privacy and the ability for client-side proofs. That is because ownership changes always involve only one account and not the change of a global hashmap. Thus, they 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 it cost-efficient to determine the type of an asset inside and outside Miden VM. And, representing the asset in a Word means the representation is always a commitment to the asset data itself. That is particularly interesting for non-fungible assets.

Issuance

Only specialized accounts called faucets can issue assets. Just like with regular accounts, anyone can create a faucet account. Faucets can issue only either fungible or non-fungible assets - but not both. The faucet_id identifies the faucet and is starts with a different sequence depending on the asset type, see here. 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. But, 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.

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 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 were to be 2, 100 MATIC are encoded as [100, 0, 0, 2] - whereas the 0s in the middle help to quickly 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. It looks like [e0, faucet_id, e2, e3]. Note that the second element is guaranteed to be non-Zero.

Examples of non-fungible assets are all NFTs, e.g., a DevCon ticket. The ticket's data might be represented in a JSON string - 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 only store up to 255 distinct assets.

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 fully replicate Ethereum's 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.

Transactions

Transactions in Miden can be understood as facilitating account state changes. Asset transfers between accounts are done by executing transactions. They take a single account and some notes as input and output the same account at a new state together with some other notes.

Miden aims for parallel and private transaction execution. Because a transaction is always performed against a single account, Miden obtains asynchronicity. And, because every transaction causes a provable state-change with a STARK proof, it provides privacy when executed locally.

Transaction design

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


\


\

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.

The next sections will explain the details of a Miden transaction

Asset transfer using two transactions

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

The first transaction invokes a function on account_a (e.g., "send_asset" function) 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., "receive_asset" function), which also updates the internal state of account_b.

It is important to note that both transactions can be executed asynchronously: first transaction1 is executed, and then, some time later, transaction2 can be 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 could be used to make sure funds sent to non-existent accounts are not lost.
  • 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.

Transaction Execution

Transactions are being executed by the Miden Transaction Executor. Transaction execution results in a ExecutedTransaction object and consists of the following steps:

  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.

One of the main reasons for splitting execution and proving is that it allows to have "stateless provers" - i.e., the executed transaction contains all data needed to re-execute and prove a transaction (no database access is needed). This is very powerful and allows the distribution of proof generation much more easily.

The Data Store and Transaction Inputs

The data store defines the interface that transaction objects use to fetch data required for transaction execution. It stores account, chain, and input note data required to execute a transaction against the account with the specified ID.

Specifically, it must provide the following inputs to the transaction

  • the Account including the AccountID and the AccountCode which will be executed during the transaction.
  • the 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.
  • the ChainMmr, which allows for efficient authentication of consumed notes during transaction execution. Authentication is achieved by providing an inclusion proof for the consumed notes in the transaction against the ChainMmr-root associated with the latest block known at the time of transaction execution.
  • the InputNotes that are being consumed in the transaction (InputNotes), including the corresponding note data, e.g. the note script and serial number.

Note: The InputNotes must all be already recorded on-chain in order for the transaction to succeed. And there is no Nullifier-check during a transaction. Nullifiers are being checked by the Miden Operator during transaction verification. So at the transaction level, there is "double spending".

The Transaction Compiler

Every transaction must be 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 programs - MASM programs - can then be executed on the Miden VM which generates a zkProof. In addition to transaction compilation, the transaction compiler provides methods which can be used to compile Miden account code, note scripts, and transaction scripts.

Compilation results in an executable MASM Program, including 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. A detailed description can be found in the next section.

Finally, after the transaction program has been compiled and the inputs including the advice provider were correctly populated, the transaction can be executed.

The Executed Transaction and the Transaction Outputs

The ExecutedTransaction object represents the result of a transaction - not its proof yet. From it, the account, and storage delta can be extracted. Furthermore, it serves as an input of the transaction prover to generate the proof. A successfully executed transaction results in a new state of the provided account, a vector of all created Notes (OutputNotes) and a vector of all the consumed Notes (InputNotes) together with their Nullifiers.

The Transaction Prover

The Transaction Prover proves the provided ExecutedTransaction and returns a ProvenTransaction object. This object can be verified by the Miden Node using the Transaction Verifier and if valid updating the State databases.

The Transaction Kernel Program

The transaction kernel program is responsible to execute a Miden rollup transaction within the Miden VM. Therefore, it is written in MASM and it is defined as MASM kernel. The kernel provides context-sensitive security preventing unwanted read and write access. It defines a set of procedures which can be invoked from other contexts - e.g., notes - to be executed in the root context.

In general, the kernel's procedures must reflect everything users might want to do in executing transactions, from transferring assets to complex smart contract interactions with custom code. Learn more about available procedures and contexts here.

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

  1. 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. Epilogue: finalizes the transaction by computing the created notes commitment, the final account hash, asserting asset invariant conditions and asserting the nonce rules are upheld.

The Inputs

The transaction kernel program receives two type of inputs, public inputs via the operand_stack and secret inputs via the advice_provider. The stack holds the global inputs. They serve as a commitment to the data being provided via the advice provider. The advice stack holds data of the last known block, account and input note data. The details are layed out in the next paragraph.

The Prologue

The transaction prologue is executed at the beginning of a transaction. It needs to accomplish the following tasks:

  1. "Unhash" the inputs and lay them out in root context's memory.
  2. Build a single vault ("tx vault") containing assets of all inputs (input notes and initial account state).
  3. Verify 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 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 memory layout looks as follows. The kernel context has access to all of those memory slots.

The book keeping section is needed to keep track of variables which are used internally by the transaction kernel.

First, all global inputs are being stored in the pre-defined memory slots. Global inputs are being provided via the operand_stack to the VM at transaction execution. The 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.

Second, the block data is being processed. This involves reading the block data from the advice provider and storing it at the appropriate memory addresses. Block data is provided from the latest known block and consists of note, state and batch root, the block;s the 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.

Third, the chain data is being processed in a similar way as the block data. In this case the chain root is being recomputed and compared against the chain root stored in the block data section.

Fourth, the account data is being processed. This involves reading the data from the advice provider and storing it at the appropriate memory addresses. The account data consists of roots of the account's vault, its 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 it is asserted that the computed account hash matches 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).

Fifth, the input notes are being processed. This involves per note reading the data from the advice provider and storing it at the appropriate memory addresses. Next to the total number of consumed notes, input note data consists of its serial number, the roots of the script, inputs and asset vault, its metadata and all its assets. As each note is consumed its hash and nullifier is computed. The transaction Nullifier commitment is computed via a sequential hash of all (nullifier, ZERO) pairs for all consumed notes. This step involves authentication that the input note data provided via the advice provider is consistent with the chain history.

Note: One needs to provide the note data to compute the Nullifier, e.g. the note script and the serial number. So one 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 needs to provide the pre-image data to the recipient at the time of execution.

Lastly, if a transaction script is provided, its root is being stored at the pre-defined memory address.

The Note Processing

If there are input notes they are being consumed in a loop. For every note, the MAST root of the note script is being loaded onto the stack. Then, by calling a dyncall the note script is being executed in a new context to prevent unwanted memory access.

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

        # 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_consumed_note_ptr
        loc_load.0
        neq
        # => [should_loop]
    end

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

Note: 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.

The Transaction Script Processing

If there is a transaction script provided with the transaction, it will be processed after all notes are being 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 again being 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, see 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.

The Epilogue

The Epilogue finalizes the transaction. It

  1. computes the final account hash
  2. if the account has changed, assert that the final account nonce is greater than the initial account nonce
  3. computes the created 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 that case input and output vault roots are not equal.

The 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.

Transaction Contexts

Miden assembly program execution can span multiple isolated contexts. An execution context defines its own memory space which is not accessible from other execution contexts. Note scripts cannot directly write into account data. This should be possible if only if the account exposes respective functions.

The kernel program always starts executing in a root context. Thus, the prologue sets the memory for the root context. To move execution into a different context, we can invoke a procedure using the call or dyncall instruction. In fact, any time we invoke a procedure using the call instruction, the procedure is executed in a new context.

While executing in a note, account, or tx script context, we can request to execute some procedures in the kernel context, which is where all necessary information was stored during the prologue. Switching to the kernle context can be done via the syscall instruction. The set of procedures which can be invoked via the syscall instruction is limited by the transaction kernel API. Once the procedure call via syscall returns, the execution moves back to the note, account, or tx script from which it was invoked.


\


\

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 in 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 and in doing so, invoking 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. And in doing so, it 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 for accounts to recieve 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). syscall can incoke all procedures defined in the Kernel API.

#! Add the specified asset to the vault.
#! ...
export.add_asset
    syscall.account_vault_add_asset
end

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

Transaction Procedures

There are user-facing procedures and kernel procedures. Users don't directly invoke kernel procedures, but indirectly via account code, note or transaction scripts. In that case, kernel procedures can only be 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 if being the wrong context procedures must be invoked by call. Users will never need to invoke syscall procedures themselves.

Note: If capitalized, a variable represents 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, can only 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
ViewReturns the account id. acct_id is the account id.
get_nonce[][nonce]account, note
ViewReturns the account nonce. nonce is the account nonce.
get_initial_hash[][H]account, note
ViewReturns the initial account hash. H is the initial account hash.
get_current_hash[][ACCT_HASH]account, note
ViewComputes and returns the account hash from account data stored in memory. ACCT_HASH is the hash of the account data.
incr_nonce[value][]account
ViewIncrements the account nonce by the provided value. value is the value to increment the nonce by. value can be at most 2^32 - 1 otherwise this procedure panics.
get_item[index][VALUE]account, note
ViewGets an item from the account storage. Panics if the index is out of bounds. index is the index of the item to get. VALUE is the value of the item.
set_item[index, V'][R', V]account
ViewSets an item in the account storage. Panics if the index is out of bounds. index is the index of the item to set. V' is the value to set. V is the previous value of the item. R' is the new storage root.
set_code[CODE_ROOT][]account
ViewSets the code of the account the transaction is being executed against. This procedure can only be executed on regular accounts with updatable code. Otherwise, this procedure fails. CODE_ROOT is the hash of the code to set.
get_balance[faucet_id][balance]account, note
ViewReturns the balance of a fungible asset associated with a faucet_id. Panics if the asset is not a fungible asset. faucet_id is the faucet id of the fungible asset of interest. balance is the vault balance of the fungible asset.
has_non_fungible_asset[ASSET][has_asset]account, note
ViewReturns a boolean indicating whether the non-fungible asset is present in the vault. Panics if the ASSET is a fungible asset. ASSET is the non-fungible asset of interest. has_asset is a boolean indicating whether the account vault has the asset of interest.
add_asset[ASSET][ASSET']account
ViewAdd the specified asset to the vault. Panics under various conditions. ASSET' final asset in the account vault defined as follows: If ASSET is a non-fungible asset, then ASSET' is the same as ASSET. If ASSET is a fungible asset, then ASSET' is the total fungible asset in the account vault after ASSET was added to it.
remove_asset[ASSET][ASSET]account
ViewRemove the specified asset from the vault. Panics under various conditions. ASSET is the asset to remove from the vault.
get_vault_commitment[][COM]account, note
ViewReturns a commitment to the account vault. COM is a commitment 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
ViewWrites the assets of the currently executing note into memory starting at the specified address. dest_ptr is the memory address to write the assets. num_assets is the number of assets in the currently executing note.
get_inputs[dest_ptr][dest_ptr]note
ViewWrites the inputs of the currently executed note into memory starting at the specified address. dest_ptr is the memory address to write the inputs.
get_sender[][sender]note
ViewReturns the sender of the note currently being processed. Panics if a note is not being processed. sender is the sender of the note currently being processed.

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
ViewReturns the block number of the last known block at the time of transaction execution. num is the last known block number.
get_block_hash[][H]account, note
ViewReturns the block hash of the last known block at the time of transaction execution. H is the last known block hash.
get_input_notes_hash[][COM]account, note
ViewReturns the input notes hash. This is computed as a sequential hash of (nullifier, script_root) tuples over all input notes. COM is the input notes hash.
get_output_notes_hash[0, 0, 0, 0][COM]account, note
ViewReturns the output notes hash. This is computed as a sequential hash of (note_hash, note_metadata) tuples over all output notes. COM is the output notes hash.
create_note[ASSET, tag, RECIPIENT][ptr]account
ViewCreates 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
ViewBuilds a fungible asset for the specified fungible faucet and amount. faucet_id is the faucet to create the asset for. amount is the amount of the asset to create. ASSET is the built fungible asset.
create_fungible_asset[amount][ASSET]faucet
ViewCreates a fungible asset for the faucet the transaction is being executed against. amount is the amount of the asset to create. ASSET is the created fungible asset.
build_non_fungible_asset[faucet_id, DATA_HASH][ASSET]faucet
ViewBuilds a non-fungible asset for the specified non-fungible faucet and DATA_HASH. faucet_id is the faucet to create the asset for. DATA_HASH is the data hash of the non-fungible asset to build. ASSET is the built non-fungible asset.
create_non_fungible_asset[DATA_HASH][ASSET]faucet
ViewCreates a non-fungible asset for the faucet the transaction is being executed against. DATA_HASH is the data hash of the non-fungible asset to create. ASSET is the created non-fungible asset.

Faucet

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

Procedure nameStackOutputsContextDescription
mint[ASSET][ASSET]faucet
ViewMint an asset from the faucet the transaction is being executed against. Panics under various conditions. ASSET is the asset that was minted.
burn[ASSET][ASSET]faucet
ViewBurn an asset from the faucet the transaction is being executed against. Panics under various conditions. ASSET is the asset that was burned.
get_total_issuance[][total_issuance]faucet
ViewReturns 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. total_issuance is the total issuance of the fungible faucet the transaction is being executed against.

Kernel Procedures

WIP - we will add those later.

Transaction Modes

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

For local transactions, 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 ZKPs 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 ZKP.

For network transactions, the operator will execute the transaction and generate 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 would be challenging as the account state would change after every transaction. In this case, the Miden Node / Operator acts as a "synchronizer" as they can execute transactions sequentially and feed the output of the previous transaction into the subsequent one.

State

The state of the Miden rollup describes the current condition of all accounts and note states. It describes what is currently the case. With its state model, using concurrent off-chain state, Polygon Miden aims to realise private, and parallel transaction execution and state bloat minimization. Midens aims to realise:

  • Notes and nullifiers 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 allows block production without knowing the full state

Privacy is realised from 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 challenges via its state model that enables concurrent off-chain execution and off-chain storage. Simply put, in Miden users can store their own data locally which reduces the burden on the network - integrity is ensured using zero-knowledge.

State components

The Miden Node(s) maintain three databases to describe the state:

  1. A database of accounts.
  2. A database of notes.
  3. A database of nullifiers for already consumed notes.

These databases are represented by authenticated data structures, such that we can easily prove that items were added to or removed from a database, and a commitment to the database would be 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 previsously 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 onchain accounts - are recorded in a Sparse Merkle Tree which maps account IDs to account hashes and account data if needed.

As described in Accounts, there are two types of accounts:

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

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

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

In the future we also want to enable Encrypted accounts where the account data is stored onchain 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.

As described in Notes, 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 bytes to the state ( bytes when it is produced, and bytes when it is consumed).

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

  1. Membership witnesses (a note exists in the database) against such an accumulator needs to be updated very infrequently.
  2. Old membership witnesses can be extended to be used with 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, this 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 would grow very quickly: at K TPS there would be about 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 all 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 were inserted into the chain (or to 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.

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 bytes each, and thus, the height of the Sparse Merkle Tree need to be .

To be able to add new nullifiers to the database, operators needs 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 month 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 can remain manageable, even at high usage for extended periods of time.

Execution Model

Polygon Miden is an Ethereum Rollup. It batches transactions - or more precisely, proofs thereof - that happen together in the same time period into a block. The Execution Model describes how the state progresses on an individual level via transactions and at the global level expressed as aggregated state updates in blocks.

Transaction Execution

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

As mentioned in transactions, 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 by 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 a pretty costly operation (we aim for 216 cycles).

Block Production

Several batch proofs are being aggregated together into one block. This can not 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

At the beginning, Miden will have a centralized Operator running a Miden Node.

Users will send either transaction proofs (using local execution) or transaction data (for network execution) to the Miden Node. Later on, the Miden Node will use 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 somewhat like this:

  • state updates contain only the hashes of changes. For example, for each account which was updated, we record a tuple ([account id], [new account hash]).
  • The included 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 also output included state updates.
  • The block also contains full account and note data for public accounts and notes. For example, if account 123 is a public account which was updated, in the state updates section we'd have 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.

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.

This 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 ZKPs in the downloaded blocks. This will be 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.

Polygon Miden Network [WIP]

Polygon Miden is a bi-directional token bridge and state machine. Miden Nodes act as operators that keep the 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 Polygon Miden are:

  • Miden Clients - represent Miden users
  • Miden Nodes - manage the Miden rollup and compress proofs
  • Verifier Contract - keeps and verifies state on Ethereum
  • Bridge Contract - entry and exit point for users

Network Slide

Miden Architecture Overview

Miden Clients

Users will run Miden Clients. They are designed to provide an interface for wallets representing accounts on Miden. Miden Clients can execute and prove transactions in the Tx Prover. They can handle arbitrary signature schemes - whereas the default is Falcon. The wallet interface serves a user interface, a wallet database to be able to store account data locally, and the required smart contract code that represents the account on Miden.

Want to learn more about Miden Clients? See here.

Miden Nodes

Operators will run Miden Nodes. Operators ensure integrity of the Account, Note and Nullifier State - which represent the state of Polygon Miden. Operators can execute and proof transactions against single accounts and they can verify proofs of locally executed transactions. Furthermore, the operator compresses the proofs in several steps up to a single proofs that gets published and verified on the Verifier contract. Operators also watch events emitted by the Bridge Contract to detect deposits and withdrawals.

To manage all of this, Miden Nodes have different modules. The Node orchestrates a Tx Prover, a Tx Aggregator and a Block Producer. The Tx Prover executes and proves transactions, like in the Miden Client. The Tx Aggregator can batch multiple proofs together to reduce the final state proof size using recursive proving. The Block Producer exposes the RPC interface to the user. The Block Producer collects transactions in the Tx Pool and stores the state of Polygon Miden in its three databases (Accounts, Notes, Nullifiers).

Want to learn more about Miden Nodes? See here.

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.

Want to learn more about Miden Nodes? See here.

[This is a dummy text, we need to explain the contract in detail]

Bridge Contract

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

Want to learn more about the bridge? See here.

[This is a dummy text, we need to explain the contract in detail]

Miden Clients

Users use Miden Clients to interact in the network. The backend of any wallet that is used in Miden will be a Miden Client. Miden Clients consist of several components.

  • Transaction Prover
  • Signature module
  • Wallet interface
  • Wallet database

[We need a diagram to show the Miden Client]

Transaction Prover

The Transaction Prover is able to execute transactions and create transaction execution proofs. It runs a Transaction Kernel at its heart.

Signature module

[Unclear if this is a separate module]

Wallet interface

At the beginning we only have a basic wallet interface which we implement for the testnet. It is rather simplistic.

The interface defines three methods:

receive_asset
send_asset
auth_tx

The first two of the above methods should probably be an interface on their own, and we should recommend that most accounts implement these methods.

The goal is to provide a wallet with the following capabilities:

The wallet is controlled by a single key. The signature scheme is assumed to be Falcon. However, sending assets to the wallet does not require knowing which signature scheme is used by the recipient. The user can send, receive, and exchange assets stored in the wallet with other users. All operations (including receiving assets) must be authenticated by the account owner.

Interface method description Below, we provide high-level details about each of the interface methods.

receive_asset method

The purpose of this method is to add a single asset to an account's vault. Pseudo-code for this method could look like so:

receive_asset(asset)
    self.add_asset(asset)
end

In the above, add_asset is a kernel procedure miden::account::add_asset of the Tx Kernel.

Note: this method does not increment account nonce. The nonce will be incremented in auth_tx method described below. Thus, receiving assets requires authentication.

send_asset method

The purpose of this method is to create a note which sends a single asset to the specified recipient. Pseudo-code for this method could look like so:

send_asset(asset, recipient)
    self.remove_asset(asset)
    tx.create_note(recipient, asset)
end

In the above, remove_asset is a kernel procedure miden::account::remove_asset and create_note is a kernel procedure miden::tx::create_note, both in the Tx Kernel.

recipient is a partial hash of the created note computed outside the VM as hash(hash(hash(serial_num), script_hash), input_hash). This allows computing note hash as hash(recipient, vault_hash) where the vault_hash can be computed inside the VM based on the specified asset.

Note: this method also does not increment account nonce. The nonce will be incremented in auth_tx method described below. Thus, sending assets requires authentication.

auth_tx method

The purpose of this method is to authenticate a transaction. For the purposes of this method we make the following assumptions:

Public key of the account is stored in account storage at index 0. To authenticate a transaction we sign hash(account_id || account_nonce || input_note_hash || output_note_hash) using Falcon signature scheme. Pseudo-code for this method could look like so:

auth_tx()
    # compute the message to sign
    let account_id = self.get_id()
    let account_nonce = self.get_nonce()
    let input_notes_hash = tx.get_input_notes_hash()
    let output_notes_hash = tx.get_output_notes_hash()
    let m = hash(account_id, account_nonce, input_notes_hash, output_notes_hash)

    # get public key from account storage and verify signature
    let pub_key = self.get_item(0)
    falcon::verify_sig(pub_key, m)

    # increment account nonce
    self.increment_nonce()
end

It is assumed that the signature for falcon::verify_sig procedure will be provided non-deterministically via the advice provider. Thus, the above procedure can succeed only if the prover has a valid Falcon signature over hash(account_id || account_nonce || input_note_hash || output_note_hash) for the public key stored in the account.

All procedures invoked as a part of this method, except for falcon::verify_sig have equivalent kernel procedures defined in the Tx Kernel. We assume that falcon::verify_sig is a part of Miden standard library.

Wallet database

[Unclear yet how this database looks like. It should at least have assets/vault, code, nonce, storage]

Miden node

The Miden node is the software that processes transactions and creates blocks for the Miden rollup. It manages the network state and orchestrates three different modules.

Status

The Miden node is still under heavy development and the project can be considered to be in an alpha stage. Many features are yet to be implemented and there is a number of limitations which we will lift in the near future.

At this point, we are developing the Miden node for a centralized operator. Thus, the work does not yet include such components as P2P networking and consensus. These will also be added in the future.

Architecture

The Miden node is made up of three main components, which communicate over gRPC:

  • RPC: an externally-facing component through which clients can interact with the node. It receives client requests (e.g., to synchronize with the latest state of the chain, or to submit transactions), performs basic validation, and forwards the requests to the appropriate internal components.
  • Store: maintains the state of the chain. It serves as the "source of truth" for the chain - i.e., if it is not in the store, the node does not consider it to be part of the chain.
  • Block Producer: accepts transactions from the RPC component, creates blocks containing those transactions, and sends them to the store.

All three components can either run as one process, or each component can run in its own process.

The diagram below illustrates high-level design of each component as well as basic interactions between them (components in light-grey are yet to be built).

Architecture diagram

Miden node RPC

The RPC is an externally-facing component through which clients can interact with the node. It receives client requests (e.g., to synchronize with the latest state of the chain, or to submit transactions), performs basic validation, and forwards the requests to the appropriate components.

RPC is one of components of the Miden node.

Architecture

TODO

API

The RPC serves connections using the gRPC protocol on a port, set in the previously mentioned configuration file. Here is a brief description of supported methods.

CheckNullifiers

Gets a list of proofs for given nullifier hashes, each proof as Sparse Merkle Trees

Parameters:

  • nullifiers: [Digest] – array of nullifier hashes.

Returns:

  • proofs: [NullifierProof] – array of nullifier proofs, positions correspond to the ones in request.

GetBlockHeaderByNumber

Retrieves block header by given block number.

Parameters

  • block_num: uint32 (optional) – the block number of the target block. If not provided, the latest known block will be returned.

Returns:

  • block_header: BlockHeader – block header.

SyncState

Returns info which can be used by the client to sync up to the latest state of the chain for the objects (accounts, notes, nullifiers) the client is interested in.

This request returns the next block containing requested data. It also returns chain_tip which is the latest block number in the chain. Client is expected to repeat these requests in a loop until response.block_header.block_num == response.chain_tip, at which point the client is fully synchronized with the chain.

Each request also returns info about new notes, nullifiers etc. created. It also returns Chain MMR delta that can be used to update the state of Chain MMR. This includes both chain MMR peaks and chain MMR nodes.

For preserving some degree of privacy, note tags and nullifiers filters contain only high part of hashes. Thus, returned data contains excessive notes and nullifiers, client can make additional filtering of that data on its side.

Parameters

  • block_num: uint32 – send updates to the client starting at this block.
  • account_ids: [AccountId] – accounts filter.
  • note_tags: [uint32] – note tags filter. Corresponds to the high 16 bits of the real values.
  • nullifiers: [uint32] – nullifiers filter. Corresponds to the high 16 bits of the real values.

Returns

  • chain_tip: uint32 – number of the latest block in the chain.
  • block_header: BlockHeader – block header of the block with the first note matching the specified criteria.
  • mmr_delta: MmrDelta – data needed to update the partial MMR from block_num to block_header.block_num.
  • block_path: MerklePath – Merkle path in the updated chain MMR to the block at block_header.block_num.
  • accounts: [AccountHashUpdate] – a list of account hashes updated after block_num but not after block_header.block_num.
  • notes: [NoteSyncRecord] – a list of all notes together with the Merkle paths from block_header.note_root.
  • nullifiers: [NullifierUpdate] – a list of nullifiers created between block_num and block_header.block_num.

SubmitProvenTransaction

Submits proven transaction to the Miden network.

Parameters

  • transaction: bytes - transaction encoded using Miden's native format.

Returns

This method doesn't return any data.

Miden node store

The Store maintains the state of the chain. It serves as the "source of truth" for the chain - i.e., if it is not in the store, the node does not consider it to be part of the chain. Store is one of components of the Miden node.

Architecture

TODO

API

The Store serves connections using the gRPC protocol on a port, set in the previously mentioned configuration file. The API cannot directly be called by the Miden Client.

Here is a brief description of supported methods.

ApplyBlock

Applies changes of a new block to the DB and in-memory data structures.

Parameters

  • block: BlockHeader – block header (src).
  • accounts: [AccountUpdate] – a list of account updates.
  • nullifiers: [Digest] – a list of nullifier hashes.
  • notes: [NoteCreated] – a list of notes created.

Returns

This method doesn't return any data.

CheckNullifiers

Get a list of proofs for given nullifier hashes, each proof as Sparse Merkle Trees

Parameters:

  • nullifiers: [Digest] – array of nullifier hashes.

Returns:

  • proofs: [NullifierProof] – array of nullifier proofs, positions correspond to the ones in request.

GetBlockHeaderByNumber

Retrieves block header by given block number.

Parameters

  • block_num: uint32 (optional) – the block number of the target block. If not provided, the latest known block will be returned.

Returns:

  • block_header: BlockHeader – block header.

GetBlockInputs

Returns data needed by the block producer to construct and prove the next block.

Parameters

  • account_ids: [AccountId] – array of account IDs.
  • nullifiers: [Digest] – array of nullifier hashes (not currently in use).

Returns

  • block_header: [BlockHeader] – the latest block header.
  • mmr_peaks: [Digest] – peaks of the above block's mmr, The forest value is equal to the block number.
  • account_states: [AccountBlockInputRecord] – the hashes of the requested accounts and their authentication paths.
  • nullifiers: [NullifierBlockInputRecord] – the requested nullifiers and their authentication paths.

GetTransactionInputs

Returns the data needed by the block producer to check validity of an incoming transaction.

Parameters

  • account_id: AccountId – ID of the account against which a transaction is executed.
  • nullifiers: [Digest] – array of nullifiers for all notes consumed by a transaction.

Returns

  • account_state: AccountTransactionInputRecord – account's descriptors.
  • nullifiers: [NullifierTransactionInputRecord] – the block numbers at which corresponding nullifiers have been consumed, zero if not consumed.

SyncState

Returns info which can be used by the client to sync up to the latest state of the chain for the objects (accounts, notes, nullifiers) the client is interested in.

This request returns the next block containing requested data. It also returns chain_tip which is the latest block number in the chain. Client is expected to repeat these requests in a loop until response.block_header.block_num == response.chain_tip, at which point the client is fully synchronized with the chain.

Each request also returns info about new notes, nullifiers etc. created. It also returns Chain MMR delta that can be used to update the state of Chain MMR. This includes both chain MMR peaks and chain MMR nodes.

For preserving some degree of privacy, note tags and nullifiers filters contain only high part of hashes. Thus, returned data contains excessive notes and nullifiers, client can make additional filtering of that data on its side.

Parameters

  • block_num: uint32 – send updates to the client starting at this block.
  • account_ids: [AccountId] – accounts filter.
  • note_tags: [uint32] – note tags filter. Corresponds to the high 16 bits of the real values.
  • nullifiers: [uint32] – nullifiers filter. Corresponds to the high 16 bits of the real values.

Returns

  • chain_tip: uint32 – number of the latest block in the chain.
  • block_header: BlockHeader – block header of the block with the first note matching the specified criteria.
  • mmr_delta: MmrDelta – data needed to update the partial MMR from block_num to block_header.block_num.
  • block_path: MerklePath – Merkle path in the updated chain MMR to the block at block_header.block_num.
  • accounts: [AccountHashUpdate] – a list of account hashes updated after block_num but not after block_header.block_num.
  • notes: [NoteSyncRecord] – a list of all notes together with the Merkle paths from block_header.note_root.
  • nullifiers: [NullifierUpdate] – a list of nullifiers created between block_num and block_header.block_num.

Methods for testing purposes

ListNullifiers

Lists all nullifiers of the current chain.

Parameters

This request doesn't have any parameters.

Returns

  • nullifiers: [NullifierLeaf] – lists of all nullifiers of the current chain.

ListAccounts

Lists all accounts of the current chain.

Parameters

This request doesn't have any parameters.

Returns

  • accounts: [AccountInfo] – list of all accounts of the current chain.

ListNotes

Lists all notes of the current chain.

Parameters

This request doesn't have any parameters.

Returns

  • notes: [Note] – list of all notes of the current chain.

Miden block producer

The Block producer receives transactions from the RPC component, processes them, creates block containing those transactions before sending created blocks to the store.

Block Producer is one of components of the Miden node.

Architecture

TODO

API

The Block Producer serves connections using the gRPC protocol on a port, set in the previously mentioned configuration file. The API cannot directly be called by the Miden Client.

Here is a brief description of supported methods.

SubmitProvenTransaction

Submits proven transaction to the Miden network.

Parameters

  • transaction: bytes - transaction encoded using Miden's native format.

Returns

This method doesn't return any data.

Verifier Contract

[WIP]

Bridge

[WIP]