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
:
contract_address
inprivate_call
.call_stack_item
.contract_instance
contract_class
function_data
inprivate_call
.call_stack_item
.
This circuit validates the existence of the function in the contract through the following checks:
Verify that the
contract_address
can 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_id
can 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_start
must be0
.- This check can be skipped for inner private kernel circuit.
- The
counter_end
must be strictly greater than thecounter_start
.
The counter range (
counter_start
tocounter_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_end
of each request must be strictly greater than itscounter_start
. - The
counter_start
of the first request must be strictly greater than thecounter_start
of thecall_stack_item
. - The
counter_start
of the second and each of the subsequent requests must be strictly greater than thecounter_end
of the previous request. - The
counter_end
of the last request must be strictly less than thecounter_end
of thecall_stack_item
.
When a
request
is 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_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.- The
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
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 thecounter_start
of thecall_stack_item
. - The
counter
of each subsequent element must be strictly greater than thecounter
of the previous item. - The
counter
of the last element must be strictly less than thecounter_end
of 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
. IfNE
is greater than0
, the circuit checks the following:For each
note_hash
at indexi
innote_hashes
:- Find the
request_index
athints
.note_hash_range_hints[i]
, which is the index of theprivate_call_requests
with the smallestcounter_start
that was emitted after thenote_hash
. - If
request_index
equalsNM
, indicating no request was emitted after thenote_hash
, its counter must be greater the thecounter_end
of the last request. - If
request_index
equals0
, indicating no request was emitted before thenote_hash
. Its counter must be less the thecounter_start
of 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:
nullifiers
unencrypted_log_hashes
encrypted_log_hashes
encrypted_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_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
Check that the values in
private_call_request_stack
align with the values inprivate_call_requests
withinprivate_function_public_inputs
, but in reverse order.It's important that the
private_call_requests
are "pushed" to theprivate_call_request_stack
in reverse order to ensure that they are executed in chronological order.For each non-empty call request in both
private_call_request_stack
andpublic_call_request_contexts
withinpublic_inputs.transient_accumulated_data
:- The
caller_contract_address
equals theprivate_call
.call_stack_item
[.contract_address
]. - The following values in
caller_context
are 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_call
flag 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_address
must equal thestorage_contract_address
inprivate_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.
For each non-empty item in
l2_to_l1_message_contexts
, itsportal_contract_address
must equal theportal_contract_address
defined inprivate_function_public_inputs.call_context
.For each
note_hash_context: NoteHashContext
in thenote_hash_contexts
, validate itsnullifier_counter
. The value of thenullifier_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 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_data
also contains values from the previous iterations.
Verifying the constant data.
It verifies that:
- The
tx_context
in theconstant_data
matches thetx_context
in thetransaction_request
. - The
block_header
must 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 | The Aztec address of the transaction sender. |
function_data | FunctionData | Data of the function being called. |
args_hash | field | Hash of the function arguments. |
tx_context | TransactionContext | Information about the transaction. |
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_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
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_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
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. |
registerer_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. |