Skip to main content

Private Kernel Circuit - Initial

Requirements

In the initial kernel iteration, the process involves taking a transaction_request and private call data , performing checks on this data (see below), and preparing the necessary data for subsequent circuits to operate. This "initial" circuit is an optimization over the inner private kernel circuit, as there is no "previous kernel" to verify at the beginning of a transaction. Additionally, this circuit executes tasks that need only occur once per transaction.

Key Checks within this Circuit

This first function call of the transaction must match the caller's intent

The following data in the private_inputs.private_call must match the corresponding fields of the user's private_inputs.transaction_request:

  • contract_address
  • function_data
  • args_hash: Hash of the function arguments.

Notice: a transaction_request doesn't explicitly contain a signature. Aztec implements account abstraction, so the process for authorizing a transaction (if at all) is dictated by the logic of the functions of that transaction. In particular, an account contract can be called as an 'entrypoint' to a transaction, and there, custom authorization logic can be executed.

It must be a standard synchronous function call

For the private_inputs.private_call.call_stack_item.public_inputs.call_context: CallContext, the circuit checks that:

  • It must not be a delegate call:
    • call_context.is_delegate_call == false
  • It must not be a static call:
    • call_context.is_static_call == false

The transaction_request must be unique

It must emit the hash of the private_inputs.transaction_request as the first nullifier.

The hash is computed as:

let { origin, function_data, args_hash, tx_context } =
private_inputs.transaction_request;
let tx_hash = hash(origin, function_data.hash(), args_hash, tx_context.hash());

Where function_data.hash() and tx_context.hash() are the hashes of the serialized field elements.

This nullifier serves multiple purposes:

  • Identifying a transaction.
  • Non-malleability. Preventing the signature of a transaction request from being reused in another transaction.
  • Generating values that should be maintained within the transaction's scope. For example, it is utilized to compute the note nonces for all the note hashes in a transaction.

Note that the final transaction data is not deterministic for a given transaction request. The production of new notes, the destruction of notes, and various other values are likely to change based on the time and conditions when a transaction is being composed. However, the intricacies of implementation should not be a concern for the entity initiating the transaction.

Processing a Private Function Call

The function being called must exist within the contract class of the called contract_address

With the following data provided from private_inputs.private_call:

This circuit validates the existence of the function in the contract through the following checks:

  1. Verify that the contract_address can be derived from the contract_instance:

    Refer to the details here for the process of computing the address for a contract instance.

  2. Verify that the contract_instance.contract_class_id can be derived from the given contract_class:

    Refer to the details here for the process of computing the contract_class_id.

  3. Verify that contract_class_data.private_functions includes the function being called:

    1. Compute the hash of the verification key:
      • vk_hash = hash(private_call.vk)
    2. Compute the function leaf:
      • hash(function_data.selector, vk_hash, private_call.bytecode_hash)
    3. Perform a membership check; that the function leaf exists within the function tree, where:
      • The index and sibling path are provided through private_call.function_leaf_membership_witness.
      • The root is contract_class.private_functions.

The private function proof must verify

It verifies that the private function was executed successfully with the provided proof data, verification key, and the public inputs of the private function circuit. I.e. private_inputs.private_call.vk, private_inputs.private_call.proof, and private_inputs.private_call.call_stack_item.public_inputs.

Validate the counters.

In ensuring the integrity of emitted data, counters play a crucial role in establishing the chronological order of elements generated during a transaction. This validation process not only guards against misinterpretations but also reinforces trust in the sequence of transactions.

Counters are employed to validate the following aspects:

  1. Read Requests: Verify that a read request is reading a value created before the request is submitted.
  2. Ordered Emission: Ensure that side effects (note hashes, nullifiers, logs) are emitted in the same order as they were created.

For these operations to be effective, specific requirements for the counters must be met:

  • Each counter for values of the same type must be unique, avoiding confusion about the order.
  • Values emitted within a function call must not be mistaken for values emitted from another function call.

The circuit undergoes the following validations for data within private_inputs.private_call.public_inputs:

  1. Validate the counter range of call_stack_item.

    • The counter_start must be 0.
    • The counter_end must be strictly greater than the counter_start.

    The counter range (counter_start to counter_end) is later used to restrict counters emitted within the call.

  2. Validate the counter ranges of non-empty requests in private_call_requests.

    • The counter_end of each request must be strictly greater than its counter_start.
    • The counter_start of the first request must be strictly greater than the counter_start of the call_stack_item.
    • The counter_start of the second and each of the subsequent requests must be strictly greater than the counter_end of the previous request.
    • The counter_end of the last request must be strictly less than the counter_end of the call_stack_item.

    When a request is popped in a nested iteration, its counter range is checked against the call_stack_item, as described here. By enforcing that the counter ranges of all nested private_call_requests do not overlap with one another in this step, a function circuit will not be able to emit a value whose counter falls in the range of another call.

  3. Validate the counters of the non-empty elements in the following arrays:

    • note_hashes
    • nullifiers
    • l2_to_l1_messages
    • unencrypted_log_hashes
    • encrypted_log_hashes
    • encrypted_note_preimage_hashes
    • note_hash_read_requests
    • nullifier_read_requests
    • public_call_requests
    1. For each of the above "ordered" array, the counters of the non-empty elements must be in a strictly-increasing order:

      • The counter of the first element must be strictly greater than the counter_start of the call_stack_item.
      • The counter of each subsequent element must be strictly greater than the counter of the previous item.
      • The counter of the last element must be strictly less than the counter_end of the call_stack_item.
    2. Additionally, the counters must not fall within the counter range of any nested private call.

      Get the value NE, which is the number of non-empty requests in private_call_requests. If NE is greater than 0, the circuit checks the following:

      For each note_hash at index i in note_hashes:

      • Find the request_index at hints.note_hash_range_hints[i], which is the index of the private_call_requests with the smallest counter_start that was emitted after the note_hash.
      • If request_index equals NM, indicating no request was emitted after the note_hash, its counter must be greater the the counter_end of the last request.
      • If request_index equals 0, indicating no request was emitted before the note_hash. Its counter must be less the the counter_start of the first request.
      • Otherwise, the request was emitted after the note_hash, and its immediate previous request was emitted before the note_hash. Its counter must fall between those two requests.

      The code simplifies as:

      let NE = count_non_empty_elements(private_call_requests);
      for i in 0..note_hashes.len() {
      let note_hash = note_hashes[i];
      if !note_hash.is_empty() {
      let request_index = note_hash_range_hints[i];
      if request_index != NE {
      note_hash.counter < private_call_requests[request_index].counter_start;
      }
      if request_index != 0 {
      note_hash.counter > private_call_requests[request_index - 1].counter_end;
      }
      }
      }

      Repeat the above process for emitted data, including:

      • nullifiers
      • unencrypted_log_hashes
      • encrypted_log_hashes
      • encrypted_note_preimage_hashes

Validating Public Inputs

Verifying the TransientAccumulatedData.

The various side effects of the private_inputs.private_call.call_stack_item.public_inputs: PrivateFunctionPublicInputs are formatted and pushed to the various arrays of the public_inputs.transient_accumulated_data: TransientAccumulatedData.

This circuit verifies that the values in private_inputs.private_call.call_stack_item.public_inputs: PrivateFunctionPublicInputs are aggregated into the various arrays of the public_inputs.transient_accumulated_data: TransientAccumulatedData correctly.

  1. Ensure that the specified values in the following arrays match those in the corresponding arrays in the private_function_public_inputs:
  • note_hash_contexts
    • value, counter
  • nullifier_contexts
    • value, counter
  • l2_to_l1_message_contexts
    • value, counter
  • note_hash_read_requests
    • value, contract_address, counter
  • nullifier_read_requests
    • value, contract_address, counter
  • nullifier_key_validation_request_contexts
    • parent_public_key, hardened_child_secret_key
  • unencrypted_log_hash_contexts
    • hash, length, counter
  • encrypted_log_hash_contexts
    • hash, length, randomness, counter
  • encrypted_note_preimage_hash_contexts
    • hash, length, counter, note_hash_counter
  • public_call_request_contexts
    • call_stack_item_hash, counter
  1. Check that the values in private_call_request_stack align with the values in private_call_requests within private_function_public_inputs, but in reverse order.

    It's important that the private_call_requests are "pushed" to the private_call_request_stack in reverse order to ensure that they are executed in chronological order.

  2. For each non-empty call request in both private_call_request_stack and public_call_request_contexts within public_inputs.transient_accumulated_data:

    • The caller_contract_address equals the private_call.call_stack_item[.contract_address].
    • The following values in caller_context are either empty or align with the values in the private_inputs.private_call.call_stack_item.public_inputs.call_context:
      • (caller_context.msg_sender == 0) & (caller_context.storage_contract_address == 0)
      • Or (caller_context.msg_sender == call_context.msg_sender) & (caller_context.storage_contract_address == call_context.storage_contract_address)
    • The is_static_call flag must be propagated:
      • caller_context.is_static_call == call_context.is_static_call

The caller context in a call request may be empty for standard calls. This precaution is crucial to prevent information leakage, particularly as revealing the msg_sender of this private function when calling a public function could pose security risks.

  1. For each non-empty item in the following arrays, its contract_address must equal the storage_contract_address in private_inputs.private_call.call_stack_item.public_inputs.call_context:

    • note_hash_contexts
    • nullifier_contexts
    • l2_to_l1_message_contexts
    • nullifier_key_validation_request_contexts
    • unencrypted_log_hash_contexts
    • encrypted_log_hash_contexts
    • encrypted_note_preimage_hash_contexts

    Ensuring the alignment of the contract addresses is crucial, as it is later used to silo the values and to establish associations with values within the same contract.

  2. For each non-empty item in l2_to_l1_message_contexts, its portal_contract_address must equal the portal_contract_address defined in private_function_public_inputs.call_context.

  3. For each note_hash_context: NoteHashContext in the note_hash_contexts, validate its nullifier_counter. The value of the nullifier_counter can be:

    • Zero: if the note is not nullified in the same transaction.
    • Strictly greater than note_hash.counter: if the note is nullified in the same transaction.

    Nullifier counters are used in the reset private kernel circuit to ensure a read happens before a transient note is nullified.

    Zero can be used to indicate a non-existing transient nullifier, as this value can never serve as the counter of a nullifier. It corresponds to the counter_start of the first function call.

Note that the verification process outlined above is also applicable to the inner private kernel circuit. However, given that the transient_accumulated_data for the inner private kernel circuit comprises both values from previous iterations and the private_call, the above process specifically targets the values stemming from the private_call. The inner kernel circuit performs an extra check to ensure that the transient_accumulated_data also contains values from the previous iterations.

Verifying the constant data.

It verifies that:

Verifying the min_revertible_side_effect_counter.

It verifies that the min_revertible_side_effect_counter equals the value in the public_inputs of the private function circuit.

Diagram

This diagram flows from the private inputs (which can be considered "inputs") to the public inputs (which can be considered "outputs").


Key:


The diagram:

PrivateInputs

FieldTypeDescription
transaction_requestTransactionRequest
private_callPrivateCall

TransactionRequest

Data that represents the caller's intent.

FieldTypeDescription
originAztecAddressThe Aztec address of the transaction sender.
function_dataFunctionDataData of the function being called.
args_hashfieldHash of the function arguments.
tx_contextTransactionContextInformation about the transaction.

PrivateCall

Data that holds details about the current private function call.

FieldTypeDescription
call_stack_itemPrivateCallStackItemInformation about the current private function call.
proofProofProof of the private function circuit.
vkVerificationKeyVerification key of the private function circuit.
bytecode_hashfieldHash of the function bytecode.
contract_instanceContractInstanceData of the contract instance being called.
contract_classContractClassData of the contract class.
function_leaf_membership_witnessMembershipWitnessMembership witness for the function being called.

Hints

FieldTypeDescription
note_hash_range_hints[field, MAX_NEW_NOTE_HASHES_PER_CALL]Indices of the next emitted private call requests for note hashes.
nullifier_range_hints[field, MAX_NEW_NULLIFIERS_PER_CALL]Indices of the next emitted private call requests for nullifiers.
unencrypted_log_range_hints[field, MAX_UNENCRYPTED_LOG_HASHES_PER_CALL]Indices of the next emitted private call requests for unencrypted logs.
encrypted_log_range_hints[field, MAX_ENCRYPTED_LOG_HASHES_PER_CALL]Indices of the next emitted private call requests for encrypted logs.
encrypted_note_range_hints[field, MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_CALL]Indices of the next emitted private call requests for encrypted notes.

PublicInputs

FieldTypeDescription
constant_dataConstantData
transient_accumulated_dataTransientAccumulatedData
min_revertible_side_effect_counteru32

ConstantData

Data that remains the same throughout the entire transaction.

FieldTypeDescription
headerHeaderHeader of a block which was used when assembling the tx.
tx_contextTransactionContextContext of the transaction.

TransientAccumulatedData

FieldTypeDescription
note_hash_contexts[NoteHashContext; MAX_NEW_NOTE_HASHES_PER_TX]Note hashes with extra data aiding verification.
nullifier_contexts[NullifierContext; MAX_NEW_NULLIFIERS_PER_TX]Nullifiers with extra data aiding verification.
l2_to_l1_message_contexts[L2toL1MessageContext; MAX_NEW_L2_TO_L1_MSGS_PER_TX]L2-to-l1 messages with extra data aiding verification.
unencrypted_log_hash_contexts[UnencryptedLogHashContext; MAX_UNENCRYPTED_LOG_HASHES_PER_TX]Hashes of the unencrypted logs with extra data aiding verification.
encrypted_log_hash_contexts[EncryptedLogHashContext; MAX_ENCRYPTED_LOG_HASHES_PER_TX]Hashes of the encrypted logs with extra data aiding verification.
encrypted_note_preimage_hash_contexts[EncryptedNotePreimageHashContext; MAX_ENCRYPTED_NOTE_PREIMAGE_HASHES_PER_TX]Hashes of the encrypted note preimages with extra data aiding verification.
note_hash_read_requests[ReadRequest; MAX_NOTE_HASH_READ_REQUESTS_PER_TX]Requests to prove the note hashes being read exist.
nullifier_read_requests[ReadRequest; MAX_NULLIFIER_READ_REQUESTS_PER_TX]Requests to prove the nullifiers being read exist.
nullifier_key_validation_request_contexts[ParentSecretKeyValidationRequestContext; MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX]Requests to validate nullifier keys.
public_call_request_contexts[PublicCallRequestContext; MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX]Requests to call publics functions.
private_call_request_stack[PrivateCallRequestContext; MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX]Requests to call private functions. Pushed to the stack in reverse order so that they will be executed in chronological order.

Types

FunctionData

FieldTypeDescription
function_selectoru32Selector of the function being called.
function_typeprivate | publicType of the function being called.

ContractClass

FieldTypeDescription
versionu8Version identifier.
registerer_addressAztecAddressAddress of the canonical contract used for registering this class.
artifact_hashfieldHash of the contract artifact.
private_functionsfieldMerkle root of the private function tree.
public_functionsfieldMerkle root of the public function tree.
unconstrained_functionsfieldMerkle root of the unconstrained function tree.

TransactionContext

FieldTypeDescription
tx_typestandard | fee_paying | fee_rebateType of the transaction.
chain_idfieldChain ID of the transaction.
versionfieldVersion of the transaction.

PrivateCallStackItem

FieldTypeDescription
contract_addressAztecAddressAddress of the contract on which the function is invoked.
function_dataFunctionDataData of the function being called.
public_inputsPrivateFunctionPublicInputsPublic inputs of the private function circuit.

PrivateCallRequestContext

FieldTypeDescription
call_stack_item_hashfieldHash of the call stack item.
counter_startu32Counter at which the call was initiated.
counter_endu32Counter at which the call ended.
caller_contract_addressAztecAddressAddress of the contract calling the function.
caller_contextCallerContextContext of the contract calling the function.

CallerContext

FieldTypeDescription
msg_senderAztecAddressAddress of the caller contract.
storage_contract_addressAztecAddressStorage contract address of the caller contract.
is_static_callboolA flag indicating whether the call is a static call.

NoteHashContext

FieldTypeDescription
valuefieldHash of the note.
counteru32Counter at which the note hash was created.
nullifier_counterfieldCounter at which the nullifier for the note was created.
contract_addressAztecAddressAddress of the contract the note was created.

NullifierContext

FieldTypeDescription
valuefieldValue of the nullifier.
counteru32Counter at which the nullifier was created.
note_hash_counteru32Counter of the transient note the nullifier is created for. 0 if the nullifier does not associate with a transient note.
contract_addressAztecAddressAddress of the contract the nullifier was created.

L2toL1MessageContext

FieldTypeDescription
valuefieldL2-to-l2 message.
counteru32Counter at which the message was emitted.
portal_contract_addressAztecAddressAddress of the portal contract to the contract.
contract_addressAztecAddressAddress of the contract the message was created.

ParentSecretKeyValidationRequestContext

FieldTypeDescription
parent_public_keyGrumpkinPointClaimed parent public key of the secret key.
hardened_child_secret_keyfqSecret key passed to the contract.
contract_addressAztecAddressAddress of the contract the request was made.

UnencryptedLogHashContext

FieldTypeDescription
hashfieldHash of the unencrypted log.
lengthfieldNumber of fields of the log preimage.
counteru32Counter at which the hash was emitted.
contract_addressAztecAddressAddress of the contract the log was emitted.

EncryptedLogHashContext

FieldTypeDescription
hashfieldHash of the encrypted log.
lengthfieldNumber of fields of the log preimage.
counteru32Counter at which the hash was emitted.
contract_addressAztecAddressAddress of the contract the log was emitted.
randomnessfieldA random value to hide the contract address.

EncryptedNotePreimageHashContext

FieldTypeDescription
hashfieldHash of the encrypted note preimage.
lengthfieldNumber of fields of the note preimage.
counteru32Counter at which the hash was emitted.
contract_addressAztecAddressAddress of the contract the log was emitted.
note_hash_counterfieldCounter of the corresponding note hash.

MembershipWitness

FieldTypeDescription
leaf_indexfieldIndex of the leaf in the tree.
sibling_path[field; H]Sibling path to the leaf in the tree. H represents the height of the tree.