Skip to content

Commit

Permalink
refactor: public kernel (#8061)
Browse files Browse the repository at this point in the history
Rearranging code for readability. No new features or bug fixes were
added.
The gate count of public_kernel_app_logic increases a lot because of
`previous_kernel.verify`, which was commented out before.
  • Loading branch information
LeilaWang authored Aug 19, 2024
1 parent 5f2a9bd commit 617a69c
Show file tree
Hide file tree
Showing 32 changed files with 1,315 additions and 1,900 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ use dep::public_kernel_lib::PublicKernelAppLogicCircuitPrivateInputs;
use dep::types::PublicKernelCircuitPublicInputs;

unconstrained fn main(input: PublicKernelAppLogicCircuitPrivateInputs) -> pub PublicKernelCircuitPublicInputs {
input.public_kernel_app_logic()
input.execute()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ use dep::types::PublicKernelCircuitPublicInputs;

#[recursive]
fn main(input: PublicKernelAppLogicCircuitPrivateInputs) -> pub PublicKernelCircuitPublicInputs {
input.public_kernel_app_logic()
input.execute()
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod previous_kernel_validator;
mod public_call_data_validator;
mod public_kernel_output_composer;
mod public_tail_output_composer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::public_kernel_phase::PublicKernelPhase;
use dep::types::abis::public_kernel_data::PublicKernelData;

struct PreviousKernelValidator {
previous_kernel: PublicKernelData,
}

impl PreviousKernelValidator {
pub fn new(previous_kernel: PublicKernelData) -> Self {
PreviousKernelValidator { previous_kernel }
}

pub fn validate_proof<let N: u32>(self, _allowed_indices: [u32; N]) {
if !dep::std::runtime::is_unconstrained() {
// Recursively verify the tube proof or a previous public kernel proof
self.previous_kernel.verify();
// TODO(#7410) currently stubbed out until tube vk handled
// self.previous_kernel.validate_in_vk_tree(allowed_indices);
}
}

pub fn validate_phase(self, phase: u8) {
let public_inputs = self.previous_kernel.public_inputs;

let needs_setup = !public_inputs.end_non_revertible.public_call_stack[0].item.contract_address.is_zero();
if phase == PublicKernelPhase.SETUP {
assert_eq(needs_setup, true, "Cannot run unnecessary setup circuit");
}

let needs_app_logic = !public_inputs.end.public_call_stack[0].item.contract_address.is_zero();
if phase == PublicKernelPhase.APP_LOGIC {
assert_eq(needs_setup, false, "Cannot run app logic circuit before setup circuit");
assert_eq(needs_app_logic, true, "Cannot run unnecessary app logic circuit");
}

let needs_teardown = !public_inputs.public_teardown_call_stack[0].item.contract_address.is_zero();
if phase == PublicKernelPhase.TEARDOWN {
assert_eq(needs_setup, false, "Cannot run teardown circuit before setup circuit");
assert_eq(needs_app_logic, false, "Cannot run teardown circuit before app logic circuit");
assert_eq(needs_teardown, true, "Cannot run unnecessary teardown circuit");
}

if phase == PublicKernelPhase.TAIL {
assert_eq(
needs_setup, false, "Revertible call stack must be empty when executing the tail circuit"
);
assert_eq(
needs_app_logic, false, "Non-revertible call stack must be empty when executing the tail circuit"
);
assert_eq(
needs_teardown, false, "Teardown call stack must be empty when executing the tail circuit"
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
use crate::public_kernel_phase::PublicKernelPhase;
use dep::types::{
abis::{
kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, public_call_data::PublicCallData,
public_call_request::PublicCallRequest
},
constants::MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, utils::arrays::array_length
};

struct PublicCallDataValidator {
data: PublicCallData,
phase: u8,
}

impl PublicCallDataValidator {
pub fn new(data: PublicCallData, phase: u8) -> Self {
PublicCallDataValidator { data, phase }
}

pub fn validate(self) {
self.validate_common_inputs();
self.validate_revert_code();
self.validate_call();
self.validate_call_requests();
}

pub fn validate_against_previous_kernel(self, previous_kernel: PublicKernelCircuitPublicInputs) {
self.validate_global_variables(previous_kernel);
self.validate_start_gas(previous_kernel);
self.validate_transaction_fee(previous_kernel);
self.validate_against_call_request(previous_kernel);
}

fn validate_common_inputs(self) {
let call_stack_item = self.data.call_stack_item;
assert(!call_stack_item.contract_address.is_zero(), "Contract address cannot be zero");
assert(call_stack_item.function_data.selector.to_field() != 0, "Function signature cannot be zero");
assert_eq(
call_stack_item.function_data.is_private, false, "Cannot execute a private function with the public kernel circuit"
);
assert_eq(
call_stack_item.function_data.selector, call_stack_item.public_inputs.call_context.function_selector, "function selector in call context does not match call stack item"
);
assert(self.data.bytecode_hash != 0, "Bytecode hash cannot be zero");
}

fn validate_revert_code(self) {
if self.phase == PublicKernelPhase.SETUP {
assert_eq(self.data.call_stack_item.public_inputs.revert_code, 0, "Public call cannot be reverted");
}
}

fn validate_call(self) {
let call_stack_item = self.data.call_stack_item;
let public_inputs = call_stack_item.public_inputs;

let call_context = public_inputs.call_context;
if call_context.is_delegate_call {
assert(
!call_stack_item.contract_address.eq(call_context.storage_contract_address), "curent contract address must not match storage contract address for delegate calls"
);
} else {
assert(
call_context.storage_contract_address.eq(call_stack_item.contract_address), "call stack storage address does not match expected contract address"
);
}

if call_context.is_static_call {
// No state changes are allowed for static calls:
let note_hashes_length = array_length(public_inputs.note_hashes);
assert(note_hashes_length == 0, "note_hashes must be empty for static calls");

let nullifiers_length = array_length(public_inputs.nullifiers);
assert(nullifiers_length == 0, "nullifiers must be empty for static calls");

let update_requests_length = array_length(public_inputs.contract_storage_update_requests);
assert(
update_requests_length == 0, "No contract storage update requests are allowed for static calls"
);

let l2_to_l1_msgs_length = array_length(public_inputs.l2_to_l1_msgs);
assert(l2_to_l1_msgs_length == 0, "l2_to_l1_msgs must be empty for static calls");

let new_unencrypted_logs_length = array_length(public_inputs.unencrypted_logs_hashes);
assert(new_unencrypted_logs_length == 0, "No unencrypted logs are allowed for static calls");
}
}

fn validate_call_requests(self) {
let call_requests = self.data.call_stack_item.public_inputs.public_call_requests;
let this_context = self.data.call_stack_item.public_inputs.call_context;
for i in 0..call_requests.len() {
let request = call_requests[i];
if !request.item.contract_address.is_zero() {
let target_context = request.item.call_context;
let target_contract = request.item.contract_address;

if target_context.is_delegate_call {
assert_eq(
target_context.msg_sender, this_context.msg_sender, "incorrect msg_sender for delegate call request"
);
assert_eq(
target_context.storage_contract_address, this_context.storage_contract_address, "incorrect storage_contract_address for delegate call request"
);
} else {
assert_eq(
target_context.msg_sender, this_context.storage_contract_address, "incorrect msg_sender for call request"
);
assert_eq(
target_context.storage_contract_address, target_contract, "incorrect storage_contract_address for call request"
);
}
if !target_context.is_static_call {
assert(this_context.is_static_call == false, "static call cannot make non-static calls");
}
}
}
}

fn validate_against_call_request(self, previous_kernel: PublicKernelCircuitPublicInputs) {
let call_stack = if self.phase == PublicKernelPhase.SETUP {
previous_kernel.end_non_revertible.public_call_stack
} else if self.phase == PublicKernelPhase.APP_LOGIC {
previous_kernel.end.public_call_stack
} else if self.phase == PublicKernelPhase.TEARDOWN {
previous_kernel.public_teardown_call_stack
} else {
assert(false, "Unknown phase");
[PublicCallRequest::empty(); MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX]
};

let call_request = call_stack[array_length(call_stack) - 1];
assert(
self.data.call_stack_item.get_compressed() == call_request.item, "call stack item does not match item at the top of the call stack"
);
}

fn validate_global_variables(self, previous_kernel: PublicKernelCircuitPublicInputs) {
let prev_global_variables = previous_kernel.constants.global_variables;
if !prev_global_variables.is_empty() { // It's empty when the previous kernel is from private_kernel_tail_to_pubic.
let public_call_globals = self.data.call_stack_item.public_inputs.global_variables;
assert_eq(
public_call_globals, prev_global_variables, "Global variables injected into the public call do not match constants"
);
}
}

// Validates that the start gas injected into the app circuit matches the remaining gas.
fn validate_start_gas(self, previous_kernel: PublicKernelCircuitPublicInputs) {
// If this is a nested call (not an execution request), the start gas is correct as long as the
// call being processed by this kernel iteration matches the call at the top of the callstack
// as per the previous kernel's outputs.
// An execution request's start gas is the remaining gas left in the transaction after the previous kernel.
// A nested call's start gas is the gas allocated to it by its caller and placed in the callstack.
if self.data.call_stack_item.is_execution_request {
let public_call_start_gas = self.data.call_stack_item.public_inputs.start_gas_left;
if self.phase != PublicKernelPhase.TEARDOWN {
let tx_gas_limits = previous_kernel.constants.tx_context.gas_settings.gas_limits;
let computed_start_gas = tx_gas_limits.sub(previous_kernel.end.gas_used).sub(previous_kernel.end_non_revertible.gas_used);
assert_eq(
public_call_start_gas, computed_start_gas, "Start gas for public phase does not match transaction gas left"
);
} else {
let teardown_gas_limit = previous_kernel.constants.tx_context.gas_settings.teardown_gas_limits;
assert_eq(
public_call_start_gas, teardown_gas_limit, "Start gas for teardown phase does not match teardown gas allocation"
);
}
}
}

fn validate_transaction_fee(self, previous_kernel: PublicKernelCircuitPublicInputs) {
let transaction_fee = self.data.call_stack_item.public_inputs.transaction_fee;
if self.phase != PublicKernelPhase.TEARDOWN {
assert_eq(transaction_fee, 0, "Transaction fee must be zero on setup and app phases");
} else {
// Note that teardown_gas is already included in end.gas_used as it was injected by the private kernel
let total_gas_used = previous_kernel.end.gas_used + previous_kernel.end_non_revertible.gas_used;
let block_gas_fees = self.data.call_stack_item.public_inputs.global_variables.gas_fees;
let inclusion_fee = previous_kernel.constants.tx_context.gas_settings.inclusion_fee;
let computed_transaction_fee = total_gas_used.compute_fee(block_gas_fees) + inclusion_fee;
assert(
transaction_fee == computed_transaction_fee, "Transaction fee on teardown phase does not match expected value"
);
}
}
}
Loading

0 comments on commit 617a69c

Please sign in to comment.