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_addressfunction_dataargs_hash: Hash of the function arguments.
Notice: a
transaction_requestdoesn'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:
contract_addressinprivate_call.call_stack_item.contract_instancecontract_classfunction_datainprivate_call.call_stack_item.
This circuit validates the existence of the function in the contract through the following checks:
-
Verify that the
contract_addresscan be derived from thecontract_instance:Refer to the details here for the process of computing the address for a contract instance.
-
Verify that the
contract_instance.contract_class_idcan be derived from the givencontract_class:Refer to the details here for the process of computing the contract_class_id.
-
Verify that contract_class_data.private_functions includes the function being called:
- Compute the hash of the verification key:
vk_hash = hash(private_call.vk)
- Compute the function leaf:
hash(function_data.selector, vk_hash, private_call.bytecode_hash)
- 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 index and sibling path are provided through
- Compute the hash of the verification key:
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:
- Read Requests: Verify that a read request is reading a value created before the request is submitted.
- Refer to Read Request Reset Private Kernel Circuit for verification details.
- Ordered Emission: Ensure that side effects (note hashes, nullifiers, logs) are emitted in the same order as they were created.
- Refer to Tail Private Kernel Circuit for order enforcement.
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:
-
Validate the counter range of
call_stack_item.- The
counter_startmust be0.- This check can be skipped for inner private kernel circuit.
- The
counter_endmust be strictly greater than thecounter_start.
The counter range (
counter_starttocounter_end) is later used to restrict counters emitted within the call. - The
-
Validate the counter ranges of non-empty requests in
private_call_requests.- The
counter_endof each request must be strictly greater than itscounter_start. - The
counter_startof the first request must be strictly greater than thecounter_startof thecall_stack_item. - The
counter_startof the second and each of the subsequent requests must be strictly greater than thecounter_endof the previous request. - The
counter_endof the last request must be strictly less than thecounter_endof thecall_stack_item.
When a
requestis popped in a nested iteration, its counter range is checked against thecall_stack_item, as described here. By enforcing that the counter ranges of all nestedprivate_call_requestsdo 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. - The
-
Validate the counters of the non-empty elements in the following arrays:
note_hashesnullifiersl2_to_l1_messagesunencrypted_log_hashesencrypted_log_hashesencrypted_note_preimage_hashesnote_hash_read_requestsnullifier_read_requestspublic_call_requests
-
For each of the above "ordered" array, the counters of the non-empty elements must be in a strictly-increasing order:
- The
counterof the first element must be strictly greater than thecounter_startof thecall_stack_item. - The
counterof each subsequent element must be strictly greater than thecounterof the previous item. - The
counterof the last element must be strictly less than thecounter_endof thecall_stack_item.
- The
-
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 inprivate_call_requests. IfNEis greater than0, the circuit checks the following:For each
note_hashat indexiinnote_hashes:- Find the
request_indexathints.note_hash_range_hints[i], which is the index of theprivate_call_requestswith the smallestcounter_startthat was emitted after thenote_hash. - If
request_indexequalsNE, indicating no request was emitted after thenote_hash, its counter must be greater than thecounter_endof the last request. - If
request_indexequals0, indicating no request was emitted before thenote_hash. Its counter must be less than thecounter_startof the first request. - Otherwise, the request was emitted after the
note_hash, and its immediate previous request was emitted before thenote_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:
nullifiersunencrypted_log_hashesencrypted_log_hashesencrypted_note_preimage_hashes
- Find the
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.
- Ensure that the specified values in the following arrays match those in the corresponding arrays in the
private_function_public_inputs:
note_hash_contextsvalue,counter
nullifier_contextsvalue,counter
l2_to_l1_message_contextsvalue,counter
note_hash_read_requestsvalue,contract_address,counter
nullifier_read_requestsvalue,contract_address,counter
key_validation_request_contextsparent_public_key,hardened_child_secret_key
unencrypted_log_hash_contextshash,length,counter
encrypted_log_hash_contextshash,length,randomness,counter
encrypted_note_preimage_hash_contextshash,length,counter,note_hash_counter
public_call_request_contextscall_stack_item_hash,counter
-
Check that the values in
private_call_request_stackalign with the values inprivate_call_requestswithinprivate_function_public_inputs, but in reverse order.It's important that the
private_call_requestsare "pushed" to theprivate_call_request_stackin reverse order to ensure that they are executed in chronological order. -
For each non-empty call request in both
private_call_request_stackandpublic_call_request_contextswithinpublic_inputs.transient_accumulated_data:- The
caller_contract_addressequals theprivate_call.call_stack_item[.contract_address]. - The following values in
caller_contextare either empty or align with the values in theprivate_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_callflag must be propagated:caller_context.is_static_call == call_context.is_static_call
- The
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.
-
For each non-empty item in the following arrays, its
contract_addressmust equal thestorage_contract_addressinprivate_inputs.private_call.call_stack_item.public_inputs.call_context:note_hash_contextsnullifier_contextsl2_to_l1_message_contextskey_validation_request_contextsunencrypted_log_hash_contextsencrypted_log_hash_contextsencrypted_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.
-
For each non-empty item in
l2_to_l1_message_contexts, itsportal_contract_addressmust equal theportal_contract_addressdefined inprivate_function_public_inputs.call_context.
-
For each
note_hash_context: NoteHashContextin thenote_hash_contexts, validate itsnullifier_counter. The value of thenullifier_countercan 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_startof 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_datafor the inner private kernel circuit comprises both values from previous iterations and theprivate_call, the above process specifically targets the values stemming from theprivate_call. The inner kernel circuit performs an extra check to ensure that thetransient_accumulated_dataalso contains values from the previous iterations.
Verifying the constant data.
It verifies that:
- The
tx_contextin theconstant_datamatches thetx_contextin thetransaction_request. - The
block_headermust align with the one used in the private function circuit, as verified earlier.
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
| Field | Type | Description |
|---|---|---|
transaction_request | TransactionRequest | |
private_call | PrivateCall |
TransactionRequest
Data that represents the caller's intent.
| Field | Type | Description |
|---|---|---|
origin | AztecAddress | Address of the entrypoint contract. |
function_data | FunctionData | Data of the function being called. |
args_hash | field | Hash of the function arguments. |
tx_context | TransactionContext | Information about the transaction. |
gas_settings | GasSettings | User-defined gas limits and max fees. |
PrivateCall
Data that holds details about the current private function call.
| Field | Type | Description |
|---|---|---|
call_stack_item | PrivateCallStackItem | Information about the current private function call. |
proof | Proof | Proof of the private function circuit. |
vk | VerificationKey | Verification key of the private function circuit. |
bytecode_hash | field | Hash of the function bytecode. |
contract_instance | ContractInstance | Data of the contract instance being called. |
contract_class | ContractClass | Data of the contract class. |
function_leaf_membership_witness | MembershipWitness | Membership witness for the function being called. |
Hints
| Field | Type | Description |
|---|---|---|
note_hash_range_hints | [field, MAX_NOTE_HASHES_PER_CALL] | Indices of the next emitted private call requests for note hashes. |
nullifier_range_hints | [field, MAX_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
| Field | Type | Description |
|---|---|---|
constant_data | ConstantData | |
transient_accumulated_data | TransientAccumulatedData | |
min_revertible_side_effect_counter | u32 |
ConstantData
Data that remains the same throughout the entire transaction.
| Field | Type | Description |
|---|---|---|
header | Header | Header of a block which was used when assembling the tx. |
tx_context | TransactionContext | Context of the transaction. |
TransientAccumulatedData
| Field | Type | Description |
|---|---|---|
note_hash_contexts | [NoteHashContext; MAX_NOTE_HASHES_PER_TX] | Note hashes with extra data aiding verification. |
nullifier_contexts | [NullifierContext; MAX_NULLIFIERS_PER_TX] | Nullifiers with extra data aiding verification. |
l2_to_l1_message_contexts | [L2toL1MessageContext; MAX_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. |
key_validation_request_contexts | [ParentSecretKeyValidationRequestContext; MAX_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
| Field | Type | Description |
|---|---|---|
function_selector | u32 | Selector of the function being called. |
function_type | private | public | Type of the function being called. |
ContractClass
| Field | Type | Description |
|---|---|---|
version | u8 | Version identifier. |
registry_address | AztecAddress | Address of the canonical contract used for registering this class. |
artifact_hash | field | Hash of the contract artifact. |
private_functions | field | Merkle root of the private function tree. |
public_functions | field | Merkle root of the public function tree. |
unconstrained_functions | field | Merkle root of the unconstrained function tree. |
TransactionContext
| Field | Type | Description |
|---|---|---|
tx_type | standard | fee_paying | fee_rebate | Type of the transaction. |
chain_id | field | Chain ID of the transaction. |
version | field | Version of the transaction. |
PrivateCallStackItem
| Field | Type | Description |
|---|---|---|
contract_address | AztecAddress | Address of the contract on which the function is invoked. |
function_data | FunctionData | Data of the function being called. |
public_inputs | PrivateFunctionPublicInputs | Public inputs of the private function circuit. |
PrivateCallRequestContext
| Field | Type | Description |
|---|---|---|
call_stack_item_hash | field | Hash of the call stack item. |
counter_start | u32 | Counter at which the call was initiated. |
counter_end | u32 | Counter at which the call ended. |
caller_contract_address | AztecAddress | Address of the contract calling the function. |
caller_context | CallerContext | Context of the contract calling the function. |
CallerContext
| Field | Type | Description |
|---|---|---|
msg_sender | AztecAddress | Address of the caller contract. |
storage_contract_address | AztecAddress | Storage contract address of the caller contract. |
is_static_call | bool | A flag indicating whether the call is a static call. |
NoteHashContext
| Field | Type | Description |
|---|---|---|
value | field | Hash of the note. |
counter | u32 | Counter at which the note hash was created. |
nullifier_counter | field | Counter at which the nullifier for the note was created. |
contract_address | AztecAddress | Address of the contract the note was created. |
NullifierContext
| Field | Type | Description |
|---|---|---|
value | field | Value of the nullifier. |
counter | u32 | Counter at which the nullifier was created. |
note_hash_counter | u32 | Counter of the transient note the nullifier is created for. 0 if the nullifier does not associate with a transient note. |
contract_address | AztecAddress | Address of the contract the nullifier was created. |
L2toL1MessageContext
| Field | Type | Description |
|---|---|---|
value | field | L2-to-l2 message. |
counter | u32 | Counter at which the message was emitted. |
portal_contract_address | AztecAddress | Address of the portal contract to the contract. |
contract_address | AztecAddress | Address of the contract the message was created. |
ParentSecretKeyValidationRequestContext
| Field | Type | Description |
|---|---|---|
parent_public_key | GrumpkinPoint | Claimed parent public key of the secret key. |
hardened_child_secret_key | fq | Secret key passed to the contract. |
contract_address | AztecAddress | Address of the contract the request was made. |
UnencryptedLogHashContext
| Field | Type | Description |
|---|---|---|
hash | field | Hash of the unencrypted log. |
length | field | Number of fields of the log preimage. |
counter | u32 | Counter at which the hash was emitted. |
contract_address | AztecAddress | Address of the contract the log was emitted. |
EncryptedLogHashContext
| Field | Type | Description |
|---|---|---|
hash | field | Hash of the encrypted log. |
length | field | Number of fields of the log preimage. |
counter | u32 | Counter at which the hash was emitted. |
contract_address | AztecAddress | Address of the contract the log was emitted. |
randomness | field | A random value to hide the contract address. |
EncryptedNotePreimageHashContext
| Field | Type | Description |
|---|---|---|
hash | field | Hash of the encrypted note preimage. |
length | field | Number of fields of the note preimage. |
counter | u32 | Counter at which the hash was emitted. |
contract_address | AztecAddress | Address of the contract the log was emitted. |
note_hash_counter | field | Counter of the corresponding note hash. |
MembershipWitness
| Field | Type | Description |
|---|---|---|
leaf_index | field | Index 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. |