Skip to content

Commit

Permalink
feat: Private Kernel Recursion (#6278)
Browse files Browse the repository at this point in the history
This PR introduces recursive verification to the private kernel
circuits. Both app circuit and previous kernel circuit proofs are
verified. This closes #5978

The changes can be largely categorised as:

1. PXE modifications to pass proofs and verification keys from the
output of a proving process as inputs to the next simulation/proving
process.
2. Serialisation of `PrivateCircuitPublicInputs` and
`PrivateKernelCircuitPublicInputs` structs to fields.
3. Aggregation of proofs using Noir's `verify_proof` api.

Additional task create
[here](#6285) to
prevent the specification of `pub` on arguments to private functions.
  • Loading branch information
PhilWindle authored May 9, 2024
1 parent 89ab8ee commit eae5822
Show file tree
Hide file tree
Showing 51 changed files with 617 additions and 267 deletions.
29 changes: 29 additions & 0 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ library Constants {
uint256 internal constant NULLIFIER_LENGTH = 3;
uint256 internal constant SCOPED_NULLIFIER_LENGTH = NULLIFIER_LENGTH + 1;
uint256 internal constant SIDE_EFFECT_LENGTH = 2;
uint256 internal constant ROLLUP_VALIDATION_REQUESTS_LENGTH = MAX_BLOCK_NUMBER_LENGTH;
uint256 internal constant STATE_REFERENCE_LENGTH =
APPEND_ONLY_TREE_SNAPSHOT_LENGTH + PARTIAL_STATE_REFERENCE_LENGTH;
uint256 internal constant TX_CONTEXT_LENGTH = 2 + GAS_SETTINGS_LENGTH;
Expand Down Expand Up @@ -157,6 +158,34 @@ library Constants {
+ 1;
uint256 internal constant PRIVATE_CALL_STACK_ITEM_LENGTH =
AZTEC_ADDRESS_LENGTH + FUNCTION_DATA_LENGTH + PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH;
uint256 internal constant SCOPED_READ_REQUEST_LEN = READ_REQUEST_LENGTH + 1;
uint256 internal constant PUBLIC_DATA_READ_LENGTH = 2;
uint256 internal constant VALIDATION_REQUESTS_LENGTH = ROLLUP_VALIDATION_REQUESTS_LENGTH
+ (SCOPED_READ_REQUEST_LEN * MAX_NOTE_HASH_READ_REQUESTS_PER_TX)
+ (SCOPED_READ_REQUEST_LEN * MAX_NULLIFIER_READ_REQUESTS_PER_TX)
+ (SCOPED_READ_REQUEST_LEN * MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX)
+ (SCOPED_NULLIFIER_KEY_VALIDATION_REQUEST_LENGTH * MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX)
+ (PUBLIC_DATA_READ_LENGTH * MAX_PUBLIC_DATA_READS_PER_TX);
uint256 internal constant PUBLIC_DATA_UPDATE_REQUEST_LENGTH = 2;
uint256 internal constant COMBINED_ACCUMULATED_DATA_LENGTH = MAX_NEW_NOTE_HASHES_PER_TX
+ MAX_NEW_NULLIFIERS_PER_TX + MAX_NEW_L2_TO_L1_MSGS_PER_TX + 4
+ (MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * PUBLIC_DATA_UPDATE_REQUEST_LENGTH) + GAS_LENGTH;
uint256 internal constant COMBINED_CONSTANT_DATA_LENGTH =
HEADER_LENGTH + TX_CONTEXT_LENGTH + GLOBAL_VARIABLES_LENGTH;
uint256 internal constant CALLER_CONTEXT_LENGTH = 2 * AZTEC_ADDRESS_LENGTH;
uint256 internal constant CALL_REQUEST_LENGTH =
1 + AZTEC_ADDRESS_LENGTH + CALLER_CONTEXT_LENGTH + 2;
uint256 internal constant PRIVATE_ACCUMULATED_DATA_LENGTH = (
SCOPED_NOTE_HASH_LENGTH * MAX_NEW_NOTE_HASHES_PER_TX
) + (SCOPED_NULLIFIER_LENGTH * MAX_NEW_NULLIFIERS_PER_TX)
+ (MAX_NEW_L2_TO_L1_MSGS_PER_TX * SCOPED_L2_TO_L1_MESSAGE_LENGTH)
+ (SIDE_EFFECT_LENGTH * MAX_ENCRYPTED_LOGS_PER_TX)
+ (SIDE_EFFECT_LENGTH * MAX_UNENCRYPTED_LOGS_PER_TX) + 2
+ (CALL_REQUEST_LENGTH * MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX)
+ (CALL_REQUEST_LENGTH * MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX);
uint256 internal constant PRIVATE_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1
+ VALIDATION_REQUESTS_LENGTH + PRIVATE_ACCUMULATED_DATA_LENGTH + COMBINED_CONSTANT_DATA_LENGTH
+ CALL_REQUEST_LENGTH;
uint256 internal constant ENQUEUE_PUBLIC_FUNCTION_CALL_RETURN_LENGTH =
2 + FUNCTION_DATA_LENGTH + CALL_CONTEXT_LENGTH;
uint256 internal constant GET_NOTES_ORACLE_RETURN_LENGTH = 674;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ contract SchnorrAccount {
// Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts file
#[aztec(private)]
#[aztec(noinitcheck)]
fn entrypoint(app_payload: pub AppPayload, fee_payload: pub FeePayload) {
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) {
let actions = AccountActions::private(
&mut context,
storage.approved_actions.storage_slot,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use dep::private_kernel_lib::PrivateKernelInitCircuitPrivateInputs;
use dep::types::PrivateKernelCircuitPublicInputs;

fn main(input: PrivateKernelInitCircuitPrivateInputs) -> pub PrivateKernelCircuitPublicInputs {
#[recursive]
fn main(input: PrivateKernelInitCircuitPrivateInputs) -> pub PrivateKernelCircuitPublicInputs {
input.native_private_kernel_circuit_initial()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use dep::private_kernel_lib::PrivateKernelInnerCircuitPrivateInputs;
use dep::types::PrivateKernelCircuitPublicInputs;

fn main(input: PrivateKernelInnerCircuitPrivateInputs) -> pub PrivateKernelCircuitPublicInputs {
#[recursive]
fn main(input: PrivateKernelInnerCircuitPrivateInputs) -> pub PrivateKernelCircuitPublicInputs {
input.native_private_kernel_circuit_inner()
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use dep::reset_kernel_lib::verify_squashed_transient_note_hashes_and_nullifiers;
use dep::types::{
abis::{
kernel_data::PrivateKernelData,
private_kernel_data::PrivateKernelData,
kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PrivateKernelCircuitPublicInputsBuilder, PublicKernelCircuitPublicInputs},
note_hash::ScopedNoteHash, nullifier::ScopedNullifier, side_effect::{SideEffect, Ordered}, gas::Gas
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{common, private_kernel_circuit_public_inputs_composer::PrivateKernelCircuitPublicInputsComposer};
use dep::types::{
abis::{
private_kernel::private_call_data::PrivateCallData,
private_kernel::private_call_data::{PrivateCallData, verify_private_call},
kernel_circuit_public_inputs::PrivateKernelCircuitPublicInputs
},
constants::MAX_NEW_NOTE_HASHES_PER_CALL, mocked::verify_private_function_proof,
Expand Down Expand Up @@ -68,14 +68,15 @@ impl PrivateKernelInitCircuitPrivateInputs {
pub fn native_private_kernel_circuit_initial(self) -> PrivateKernelCircuitPublicInputs {
let private_call_public_inputs = self.private_call.call_stack_item.public_inputs;

// verify/aggregate the private call proof
verify_private_call(self.private_call);

self.validate_inputs();

common::validate_private_call_data(self.private_call);

self.validate_this_private_call_against_tx_request();

assert(verify_private_function_proof(self.private_call.proof), "Invalid private function proof.");

PrivateKernelCircuitPublicInputsComposer::new_from_tx_request(self.tx_request, private_call_public_inputs).compose(
private_call_public_inputs,
self.hints.note_hash_nullifier_counters,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::{common, private_kernel_circuit_public_inputs_composer::PrivateKernelCircuitPublicInputsComposer};
use dep::types::{
abis::{
kernel_data::PrivateKernelData, private_kernel::private_call_data::PrivateCallData,
private_kernel_data::{PrivateKernelData, verify_previous_kernel_proof},
private_kernel::private_call_data::{PrivateCallData, verify_private_call},
kernel_circuit_public_inputs::{PrivateKernelCircuitPublicInputs, PrivateKernelCircuitPublicInputsBuilder},
side_effect::SideEffect
},
constants::MAX_NEW_NOTE_HASHES_PER_CALL, mocked::verify_previous_kernel_state,
utils::arrays::array_length
constants::MAX_NEW_NOTE_HASHES_PER_CALL, utils::arrays::array_length
};

struct PrivateKernelInnerHints {
Expand All @@ -30,6 +30,12 @@ impl PrivateKernelInnerCircuitPrivateInputs {
let private_call_public_inputs = self.private_call.call_stack_item.public_inputs;
let previous_kernel_public_inputs = self.previous_kernel.public_inputs;

// verify/aggregate the private call proof
verify_private_call(self.private_call);

// verify/aggregate the previous kernel
verify_previous_kernel_proof(self.previous_kernel);

common::validate_previous_kernel_values(previous_kernel_public_inputs.end);

self.validate_inputs();
Expand All @@ -42,12 +48,6 @@ impl PrivateKernelInnerCircuitPrivateInputs {
let call_request = private_call_stack[private_call_stack_size - 1];
common::validate_call_against_request(self.private_call, call_request);

let (is_previous_state_valid, _updated_aggregation_object) = verify_previous_kernel_state(
previous_kernel_public_inputs.aggregation_object,
self.private_call.proof
);
assert(is_previous_state_valid);

PrivateKernelCircuitPublicInputsComposer::new_from_previous_kernel(self.previous_kernel.public_inputs).compose(
private_call_public_inputs,
self.hints.note_hash_nullifier_counters,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use crate::kernel_circuit_public_inputs_composer::KernelCircuitPublicInputsComposer;
use dep::reset_kernel_lib::{NoteHashReadRequestHints, NullifierReadRequestHints, PrivateValidationRequestProcessor};
use dep::types::{
abis::{
kernel_data::PrivateKernelData, kernel_circuit_public_inputs::KernelCircuitPublicInputs,
note_hash::ScopedNoteHash, nullifier::ScopedNullifier, side_effect::SideEffect
},
constants::{
MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX,
MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, MAX_ENCRYPTED_LOGS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX
},
abis::{
private_kernel_data::{PrivateKernelData, verify_previous_kernel_proof}, kernel_circuit_public_inputs::KernelCircuitPublicInputs,
note_hash::ScopedNoteHash, nullifier::ScopedNullifier, side_effect::SideEffect
},
constants::{
MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX,
MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, MAX_ENCRYPTED_LOGS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX
},
grumpkin_private_key::GrumpkinPrivateKey, utils::arrays::array_length
};

Expand Down Expand Up @@ -47,6 +47,9 @@ impl PrivateKernelTailCircuitPrivateInputs {
array_length(previous_public_inputs.end.public_call_stack), 0, "Public call stack must be empty when executing the tail circuit"
);

// verify/aggregate the previous kernel
verify_previous_kernel_proof(self.previous_kernel);

let note_hash_tree_root = previous_public_inputs.constants.historical_header.state.partial.note_hash_tree.root;
let nullifier_tree_root = previous_public_inputs.constants.historical_header.state.partial.nullifier_tree.root;
PrivateValidationRequestProcessor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::kernel_circuit_public_inputs_composer::KernelCircuitPublicInputsCompo
use dep::reset_kernel_lib::{NoteHashReadRequestHints, NullifierReadRequestHints, PrivateValidationRequestProcessor};
use dep::types::{
abis::{
kernel_data::PrivateKernelData, kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs,
private_kernel_data::{PrivateKernelData, verify_previous_kernel_proof}, kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs,
note_hash::ScopedNoteHash, nullifier::ScopedNullifier, side_effect::SideEffect
},
constants::{
Expand Down Expand Up @@ -47,6 +47,9 @@ impl PrivateKernelTailToPublicCircuitPrivateInputs {
array_length(previous_public_inputs.end.public_call_stack) != 0, "Public call stack must not be empty when exporting public kernel data from the tail circuit"
);

// verify/aggregate the previous kernel
verify_previous_kernel_proof(self.previous_kernel);

let note_hash_tree_root = previous_public_inputs.constants.historical_header.state.partial.note_hash_tree.root;
let nullifier_tree_root = previous_public_inputs.constants.historical_header.state.partial.nullifier_tree.root;
PrivateValidationRequestProcessor {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use dep::private_kernel_lib::PrivateKernelTailToPublicCircuitPrivateInputs;
use dep::types::PublicKernelCircuitPublicInputs;

fn main(input: PrivateKernelTailToPublicCircuitPrivateInputs) -> pub PublicKernelCircuitPublicInputs {
#[recursive]
fn main(input: PrivateKernelTailToPublicCircuitPrivateInputs) -> pub PublicKernelCircuitPublicInputs {
input.execute()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use dep::private_kernel_lib::PrivateKernelTailCircuitPrivateInputs;
use dep::types::KernelCircuitPublicInputs;

fn main(input: PrivateKernelTailCircuitPrivateInputs) -> pub KernelCircuitPublicInputs {
#[recursive]
fn main(input: PrivateKernelTailCircuitPrivateInputs) -> pub KernelCircuitPublicInputs {
input.native_private_kernel_circuit_tail()
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ impl PublicKernelTailCircuitPrivateInputs {
let end = self.propagate_accumulated_data();

KernelCircuitPublicInputs {
aggregation_object: previous_public_inputs.aggregation_object,
rollup_validation_requests: previous_public_inputs.validation_requests.for_rollup,
end,
constants: previous_public_inputs.constants,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ impl BaseRollupInputs {
// TODO(Kev): This aggregate_proof method is duplicated in a lot of places
fn aggregate_proofs(self) -> AggregationObject {
// TODO: for now we simply return the aggregation object from the first proof
self.kernel_data.public_inputs.aggregation_object
AggregationObject {}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod max_block_number;
mod private_kernel;
mod kernel_circuit_public_inputs;
mod kernel_data;
mod private_kernel_data;

mod call_request;
mod private_call_stack_item;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use crate::{
},
constants::{
MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, MAX_NEW_L2_TO_L1_MSGS_PER_TX,
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, COMBINED_ACCUMULATED_DATA_LENGTH
},
utils::arrays::array_merge, traits::Empty
utils::arrays::array_merge, traits::{Empty, Serialize}
};

struct CombinedAccumulatedData {
Expand Down Expand Up @@ -82,3 +82,27 @@ impl Empty for CombinedAccumulatedData {
}
}
}

impl Serialize<COMBINED_ACCUMULATED_DATA_LENGTH> for CombinedAccumulatedData {
fn serialize(self) -> [Field; COMBINED_ACCUMULATED_DATA_LENGTH] {
let mut fields: BoundedVec<Field, COMBINED_ACCUMULATED_DATA_LENGTH> = BoundedVec::new();

fields.extend_from_array(self.new_note_hashes);
fields.extend_from_array(self.new_nullifiers);
fields.extend_from_array(self.new_l2_to_l1_msgs);
fields.push(self.encrypted_logs_hash);
fields.push(self.unencrypted_logs_hash);
fields.push(self.encrypted_log_preimages_length);
fields.push(self.unencrypted_log_preimages_length);

for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX {
fields.extend_from_array(self.public_data_update_requests[i].serialize());
}

fields.extend_from_array(self.gas_used.serialize());

assert_eq(fields.len(), COMBINED_ACCUMULATED_DATA_LENGTH);

fields.storage
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ use crate::{
call_request::CallRequest, gas::Gas, note_hash::ScopedNoteHash, nullifier::ScopedNullifier,
side_effect::SideEffect
},
messaging::l2_to_l1_message::ScopedL2ToL1Message
traits::Serialize, messaging::l2_to_l1_message::ScopedL2ToL1Message
};
use crate::constants::{
MAX_NEW_NOTE_HASHES_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX,
MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_NEW_L2_TO_L1_MSGS_PER_TX, MAX_ENCRYPTED_LOGS_PER_TX,
MAX_UNENCRYPTED_LOGS_PER_TX
MAX_UNENCRYPTED_LOGS_PER_TX, PRIVATE_ACCUMULATED_DATA_LENGTH
};

struct PrivateAccumulatedData {
Expand All @@ -27,3 +27,44 @@ struct PrivateAccumulatedData {
private_call_stack: [CallRequest; MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX],
public_call_stack: [CallRequest; MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX],
}

impl Serialize<PRIVATE_ACCUMULATED_DATA_LENGTH> for PrivateAccumulatedData {
fn serialize(self) -> [Field; PRIVATE_ACCUMULATED_DATA_LENGTH] {
let mut fields: BoundedVec<Field, PRIVATE_ACCUMULATED_DATA_LENGTH> = BoundedVec::new();

for i in 0..MAX_NEW_NOTE_HASHES_PER_TX {
fields.extend_from_array(self.new_note_hashes[i].serialize());
}

for i in 0..MAX_NEW_NULLIFIERS_PER_TX {
fields.extend_from_array(self.new_nullifiers[i].serialize());
}

for i in 0..MAX_NEW_L2_TO_L1_MSGS_PER_TX {
fields.extend_from_array(self.new_l2_to_l1_msgs[i].serialize());
}

for i in 0..MAX_ENCRYPTED_LOGS_PER_TX {
fields.extend_from_array(self.encrypted_logs_hashes[i].serialize());
}

for i in 0..MAX_UNENCRYPTED_LOGS_PER_TX {
fields.extend_from_array(self.unencrypted_logs_hashes[i].serialize());
}

fields.push(self.encrypted_log_preimages_length);
fields.push(self.unencrypted_log_preimages_length);

for i in 0..MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX {
fields.extend_from_array(self.private_call_stack[i].serialize());
}

for i in 0..MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX {
fields.extend_from_array(self.public_call_stack[i].serialize());
}

assert_eq(fields.len(), PRIVATE_ACCUMULATED_DATA_LENGTH);

fields.storage
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::address::AztecAddress;
use dep::std::cmp::Eq;
use crate::traits::Empty;
use crate::traits::{Empty, Serialize};
use crate::abis::caller_context::CallerContext;
use crate::constants::CALL_REQUEST_LENGTH;

struct CallRequest {
hash: Field,
Expand Down Expand Up @@ -38,3 +39,19 @@ impl CallRequest {
self.hash == 0
}
}

impl Serialize<CALL_REQUEST_LENGTH> for CallRequest {
fn serialize(self) -> [Field; CALL_REQUEST_LENGTH] {
let mut fields: BoundedVec<Field, CALL_REQUEST_LENGTH> = BoundedVec::new();

fields.push(self.hash);
fields.extend_from_array(self.caller_contract_address.serialize());
fields.extend_from_array(self.caller_context.serialize());
fields.push(self.start_side_effect_counter as Field);
fields.push(self.end_side_effect_counter as Field);

assert_eq(fields.len(), CALL_REQUEST_LENGTH);

fields.storage
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::address::AztecAddress;
use dep::std::cmp::Eq;
use crate::traits::Empty;
use crate::traits::{Empty, Serialize};
use crate::constants::CALLER_CONTEXT_LENGTH;

struct CallerContext {
msg_sender: AztecAddress,
Expand Down Expand Up @@ -28,3 +29,16 @@ impl CallerContext {
self.msg_sender.is_zero() & self.storage_contract_address.is_zero()
}
}

impl Serialize<CALLER_CONTEXT_LENGTH> for CallerContext {
fn serialize(self) -> [Field; CALLER_CONTEXT_LENGTH] {
let mut fields: BoundedVec<Field, CALLER_CONTEXT_LENGTH> = BoundedVec::new();

fields.extend_from_array(self.msg_sender.serialize());
fields.extend_from_array(self.storage_contract_address.serialize());

assert_eq(fields.len(), CALLER_CONTEXT_LENGTH);

fields.storage
}
}
Loading

0 comments on commit eae5822

Please sign in to comment.