Skip to main content
Version: 0.16 (unstable)

Account Code

A collection of procedures defining the Account's programmable interface.

Every Miden Account is essentially a smart contract. The Code defines the account's procedures, which can be invoked through both note scripts and transaction scripts. Key characteristics include:

  • Mutable access: Only the Account's own procedures can modify its storage and vault. All state changes — such as updating storage slots or transferring assets — must occur through these procedures.
  • Function commitment: Each function can be called by its MAST root. The root represents the underlying code tree as a 32-byte commitment. This ensures integrity which means a function's behavior cannot change without changing the MAST root.
  • Asset creation: Faucet Accounts can create assets.

Interface

An account's code is typically the result of merging multiple account components. This results in a set of procedures that make up the interface of the account. As an example, a typical wallet uses the so-called basic wallet interface, which is defined in miden::components::wallets::basic_wallet. It consists of the receive_asset, move_asset_to_note and create_note procedures. If an account has this interface, i.e. this set of procedures, it can consume standard P2ID notes. If it doesn't, it can't consume this type of note. So, adhering to standard interfaces such as the basic wallet will generally make an account more interoperable.

Authentication

Authenticating a transaction, and therefore the changes to the account, is done with an authentication procedure. Every account's code must provide exactly one authentication procedure. It is automatically called during the transaction epilogue, i.e. after all note scripts and the transaction script have been executed.

Such an authentication procedure typically inspects the transaction and then decides whether a signature is required to authenticate the changes. It does this by:

  • checking which account procedures have been called
    • Example: Authentication is required if the distribute procedure was called but not if burn was called.
  • inspecting the account delta.
    • Example: Authentication is required if a cryptographic key in storage was updated.
    • Example: Authentication is required if an asset was removed from the vault.
  • checking whether notes have been consumed.
  • checking whether notes have been created.

Recall that an account's nonce must be incremented whenever its state changes. Only authentication procedures are allowed to do so, to prevent accidental or unintended authorization of state changes.

Signature schemes and privacy

The standard signature-based authentication components support two schemes, and they differ in what they reveal at proving time:

  • falcon512_poseidon2 is verified entirely in-circuit. The public key is supplied non-deterministically and checked against the on-chain Poseidon2(pk) commitment inside the proof, so neither the public key nor the signature leaves the prover.
  • ecdsa_k256_keccak is verified via a precompile. Under the current native re-verification model, the precompile calldata - the raw compressed secp256k1 public key and the signature - must be carried inside the transaction proof for it to verify: the verifier recomputes the precompile transcript from that calldata and binds it into the proof's public inputs, so it cannot be stripped or withheld. As a consequence, the public key and signature are disclosed to the node operator and to any party on the transaction submission or gossip path, even though the account commits on-chain only to Poseidon2(pk).

In other words, ecdsa_k256_keccak does not provide the public-key privacy that commitment-based storage otherwise implies. Integrators requiring signer-key privacy should select falcon512_poseidon2.

Procedure invocation checks

The authentication procedure can base its authentication decision on whether a specific account procedure was called during the transaction. A procedure invocation is tracked by the kernel only if it invokes account-restricted kernel APIs (procedures that are only allowed to be called from the account context, e.g. exec.faucet::mint). Invocation of procedures that execute only local instructions (e.g., a noop push.0 drop) will not be tracked by the kernel.

Reentrancy

The transaction kernel ensures that an authentication procedure cannot be called by note scripts or transaction scripts before the epilogue. However, it is theoretically possible for an authentication procedure to re-enter itself.

In practice, most authentication procedures call native_account::incr_nonce on all successful execution paths. Since incr_nonce can only be called once per transaction, a re-entrant call would abort when attempting to increment the nonce a second time, effectively preventing reentrancy as a side effect.

If an authentication procedure does not call incr_nonce on all successful execution paths, the author should ensure that the procedure does not re-enter itself if this would result in unintended behavior, as the transaction kernel does not enforce this.