aztec-nr - noir_aztec::macros::functions

Function initializer

pub comptime fn initializer(f: FunctionDefinition)

An initializer function is where a contract initializes its state.

Initializer functions are similar to constructors:

  • can only be called once
  • external non-initializer functions cannot be called until one of the initializers has been called

The only exception are noinitcheck functions, which can be called even if none of the initializers has been called.

Initialization Commitment

All contract instances have their address include a commitment to one of their initializer functions, along with parameters and calling address. Any of the following will therefore fail:

  • calling the wrong initializer function
  • calling the initializer function with incorrect parameters
  • calling the initializer function from the incorrect address

It is possible however to allow for any account to call the specified initializer by setting the intended caller to the zero address. These are called 'universal deployments'.

Multiple Initializers

A contract can have multiple initializer functions, but it is not possible to call multiple initializers on the same instance: all initializers become disabled once any of them executes. Each individual instance can call any of the initializers.

Initializers can be either private or public. If a contract needs to initialize both private and public state, then it should have an external private function marked as initializer which then enqueues a call to an external public function not marked as initializer and instead marked as only_self (so that it can only be called in this manner).

Lack of Initializers

If a contract has no initializer function, initialization is then not required and all functions can be called at any time. Contracts that do have initializers can also make some of their functions available prior to initialization by marking them with the #[noinitcheck] attribute - though any contract state initialization will of course not have taken place.

How It Works

Initializers emit nullifiers to mark the contract as initialized. Two separate nullifiers are used (a private initialization nullifier and a public initialization nullifier) because private nullifiers are committed before public execution begins: if a single nullifier were used, public functions enqueued by the initializer would see it as existing before the public initialization code had a chance to run.

The private initialization nullifier is computed from the contract address and the contract's init_hash. This means that address knowledge alone is insufficient to check whether a contract has been initialized, preventing a privacy leak for fully private contracts.

  • Private initializers emit the private initialization nullifier. For contracts that also have external public functions, they auto-enqueue a call to an auto-generated public function that emits the public initialization nullifier during public execution. This function name is reserved and cannot be used by contract developers.
  • Public initializers emit both nullifiers directly.
  • Private external functions check the private initialization nullifier.
  • Public external functions check the public initialization nullifier.

For private non-initializer functions, the cost of this check is equivalent to a call to PrivateContext::assert_nullifier_exists. For public ones, it is equivalent to a call to PublicContext::nullifier_exists_unsafe.

The noinitcheck attribute can be used to skip these checks. only_self functions also implicitly skip them.