zkevm_circuits 0.153.12

ZKsync Era circuits for EraVM
use std::sync::Arc;

use boojum::algebraic_props::round_function::AlgebraicRoundFunction;
use boojum::cs::gates::PublicInputGate;
use boojum::cs::traits::cs::ConstraintSystem;
use boojum::field::SmallField;
use boojum::gadgets::num::Num;
use boojum::gadgets::queue::full_state_queue::FullStateCircuitQueue;
use boojum::gadgets::queue::{
    CircuitQueue, CircuitQueueRawWitness, CircuitQueueWitness, QueueState,
};
use boojum::gadgets::traits::allocatable::{CSAllocatable, CSAllocatableExt, CSPlaceholder};
use boojum::gadgets::traits::auxiliary::PrettyComparison;
use boojum::gadgets::traits::encodable::{CircuitVarLengthEncodable, WitnessVarLengthEncodable};
use boojum::gadgets::traits::round_function::CircuitRoundFunction;
use boojum::gadgets::traits::selectable::Selectable;
use boojum::gadgets::traits::witnessable::WitnessHookable;
use boojum::gadgets::{boolean::Boolean, u160::UInt160, u256::UInt256, u32::UInt32, u8::UInt8};

use crate::base_structures::log_query::LogQuery;
use crate::base_structures::memory_query::{MemoryQuery, MemoryQueue};
use crate::base_structures::precompile_input_outputs::{
    PrecompileFunctionInputData, PrecompileFunctionOutputData,
};
use crate::demux_log_queue::StorageLogQueue;
use crate::fsm_input_output::{
    commit_variable_length_encodable_item, ClosedFormInput, ClosedFormInputCompactForm,
};
use crate::storage_application::ConditionalWitnessAllocator;

// Add checks that log queries have the precompile aux byte and the expected precompile address.
pub fn check_precompile_meta<F: SmallField, CS: ConstraintSystem<F>>(
    cs: &mut CS,
    should_process: Boolean<F>,
    precompile_address: UInt160<F>,
    query: LogQuery<F>,
    aux_byte_for_precompile: UInt8<F>,
) {
    Num::conditionally_enforce_equal(
        cs,
        should_process,
        &Num::from_variable(query.aux_byte.get_variable()),
        &Num::from_variable(aux_byte_for_precompile.get_variable()),
    );
    for (a, b) in query
        .address
        .inner
        .iter()
        .zip(precompile_address.inner.iter())
    {
        Num::conditionally_enforce_equal(
            cs,
            should_process,
            &Num::from_variable(a.get_variable()),
            &Num::from_variable(b.get_variable()),
        );
    }
}

// Take a list of read values, allocate variables for them, and add the memory query, to
// the memory queue.
pub fn add_read_values_to_queue<
    F: SmallField,
    CS: ConstraintSystem<F>,
    R: CircuitRoundFunction<F, 8, 12, 4> + AlgebraicRoundFunction<F, 8, 12, 4>,
>(
    cs: &mut CS,
    should_process: Boolean<F>,
    read_values: &mut [UInt256<F>],
    read_queries_allocator: &ConditionalWitnessAllocator<F, UInt256<F>>,
    memory_queue: &mut FullStateCircuitQueue<F, MemoryQuery<F>, 8, 12, 4, 8, R>,
    timestamp_to_use_for_read: UInt32<F>,
    input_page: UInt32<F>,
    input_offset: &mut UInt32<F>,
    boolean_false: Boolean<F>,
    one_u32: UInt32<F>,
) where
    [(); <LogQuery<F> as CSAllocatableExt<F>>::INTERNAL_STRUCT_LEN]:,
    [(); <MemoryQuery<F> as CSAllocatableExt<F>>::INTERNAL_STRUCT_LEN]:,
    [(); <UInt256<F> as CSAllocatableExt<F>>::INTERNAL_STRUCT_LEN]:,
    [(); <UInt256<F> as CSAllocatableExt<F>>::INTERNAL_STRUCT_LEN + 1]:,
{
    let mut bias_variable = should_process.get_variable();

    for dst in read_values.iter_mut() {
        let read_query_value =
            read_queries_allocator.conditionally_allocate_biased(cs, should_process, bias_variable);
        bias_variable = read_query_value.inner[0].get_variable();

        *dst = read_query_value;

        let read_query = MemoryQuery {
            timestamp: timestamp_to_use_for_read,
            memory_page: input_page,
            index: *input_offset,
            rw_flag: boolean_false,
            is_ptr: boolean_false,
            value: read_query_value,
        };

        let _ = memory_queue.push(cs, read_query, should_process);

        *input_offset = input_offset.add_no_overflow(cs, one_u32);
    }
}

// Compute the requests/memory states, adding the memory state select to the input's final memory state
pub fn compute_final_requests_and_memory_states<
    F: SmallField,
    CS: ConstraintSystem<F>,
    R: CircuitRoundFunction<F, 8, 12, 4> + AlgebraicRoundFunction<F, 8, 12, 4>,
    T: Clone
        + std::fmt::Debug
        + CSAllocatable<F>
        + CircuitVarLengthEncodable<F>
        + WitnessVarLengthEncodable<F>
        + WitnessHookable<F>
        + PrettyComparison<F>,
>(
    cs: &mut CS,
    requests_queue: CircuitQueue<F, LogQuery<F>, 8, 12, 4, 4, 20, R>,
    structured_input: &mut ClosedFormInput<
        F,
        T,
        PrecompileFunctionInputData<F>,
        PrecompileFunctionOutputData<F>,
    >,
    memory_queue: FullStateCircuitQueue<F, MemoryQuery<F>, 8, 12, 4, 8, R>,
) -> (QueueState<F, 4>, QueueState<F, 12>)
where
    <T as CSAllocatable<F>>::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq,
{
    requests_queue.enforce_consistency(cs);

    let done = requests_queue.is_empty(cs);
    structured_input.completion_flag = done;
    structured_input.observable_output = PrecompileFunctionOutputData::<F>::placeholder(cs);

    let final_memory_state = memory_queue.into_state();
    let final_requests_state = requests_queue.into_state();

    structured_input.observable_output.final_memory_state = QueueState::conditionally_select(
        cs,
        structured_input.completion_flag,
        &final_memory_state,
        &structured_input.observable_output.final_memory_state,
    );

    (final_requests_state, final_memory_state)
}

// Add all input commitment variables to constraint system and returns input commitment.
pub fn generate_input_commitment<
    F: SmallField,
    CS: ConstraintSystem<F>,
    R: CircuitRoundFunction<F, 8, 12, 4> + AlgebraicRoundFunction<F, 8, 12, 4>,
    T: Clone
        + std::fmt::Debug
        + CSAllocatable<F>
        + CircuitVarLengthEncodable<F>
        + WitnessVarLengthEncodable<F>
        + WitnessHookable<F>
        + PrettyComparison<F>,
>(
    cs: &mut CS,
    round_function: &R,
    structured_input: ClosedFormInput<
        F,
        T,
        PrecompileFunctionInputData<F>,
        PrecompileFunctionOutputData<F>,
    >,
) -> [Num<F>; 4]
where
    <T as CSAllocatable<F>>::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq,
{
    let compact_form =
        ClosedFormInputCompactForm::from_full_form(cs, &structured_input, round_function);
    let input_commitment = commit_variable_length_encodable_item(cs, &compact_form, round_function);
    for el in input_commitment.iter() {
        let gate = PublicInputGate::new(el.get_variable());
        gate.add_to_cs(cs);
    }

    input_commitment
}

// Creates memory and requests queue states, adding conditional select constraints to constraint system.
pub fn create_requests_state_and_memory_state<
    F: SmallField,
    CS: ConstraintSystem<F>,
    R: CircuitRoundFunction<F, 8, 12, 4> + AlgebraicRoundFunction<F, 8, 12, 4>,
    T: Clone
        + std::fmt::Debug
        + CSAllocatable<F>
        + CircuitVarLengthEncodable<F>
        + WitnessVarLengthEncodable<F>
        + WitnessHookable<F>
        + PrettyComparison<F>,
>(
    cs: &mut CS,
    structured_input: &ClosedFormInput<
        F,
        T,
        PrecompileFunctionInputData<F>,
        PrecompileFunctionOutputData<F>,
    >,
    requests_queue_state_from_input: &QueueState<F, 4>,
    requests_queue_state_from_fsm: &QueueState<F, 4>,
    memory_queue_state_from_fsm: &QueueState<F, 12>,
    start_flag: Boolean<F>,
    requests_queue_witness: CircuitQueueRawWitness<F, LogQuery<F>, 4, 20>,
) -> (
    CircuitQueue<F, LogQuery<F>, 8, 12, 4, 4, 20, R>,
    FullStateCircuitQueue<F, MemoryQuery<F>, 8, 12, 4, 8, R>,
)
where
    <T as CSAllocatable<F>>::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq,
{
    let requests_queue_state = QueueState::conditionally_select(
        cs,
        structured_input.start_flag,
        requests_queue_state_from_input,
        requests_queue_state_from_fsm,
    );

    let mut requests_queue = StorageLogQueue::<F, R>::from_state(cs, requests_queue_state);
    let queue_witness = CircuitQueueWitness::from_inner_witness(requests_queue_witness);
    requests_queue.witness = Arc::new(queue_witness);

    let memory_queue_state_from_input =
        structured_input.observable_input.initial_memory_queue_state;
    memory_queue_state_from_input.enforce_trivial_head(cs);

    let memory_queue_state = QueueState::conditionally_select(
        cs,
        start_flag,
        &memory_queue_state_from_input,
        &memory_queue_state_from_fsm,
    );

    let memory_queue = MemoryQueue::<F, R>::from_state(cs, memory_queue_state);

    (requests_queue, memory_queue)
}

// Adds memory queries to memory queue.
pub fn add_query_to_queue<
    F: SmallField,
    CS: ConstraintSystem<F>,
    R: CircuitRoundFunction<F, 8, 12, 4> + AlgebraicRoundFunction<F, 8, 12, 4>,
>(
    cs: &mut CS,
    should_process: Boolean<F>,
    memory_queue: &mut FullStateCircuitQueue<F, MemoryQuery<F>, 8, 12, 4, 8, R>,
    timestamp: UInt32<F>,
    memory_page: UInt32<F>,
    index: &mut UInt32<F>,
    rw_flag: Boolean<F>,
    is_ptr: Boolean<F>,
    value: UInt256<F>,
    one_u32: UInt32<F>,
) where
    [(); <MemoryQuery<F> as CSAllocatableExt<F>>::INTERNAL_STRUCT_LEN]:,
{
    let query = MemoryQuery {
        timestamp,
        memory_page,
        index: *index,
        rw_flag,
        is_ptr,
        value,
    };

    let _ = memory_queue.push(cs, query, should_process);

    *index = index.add_no_overflow(cs, one_u32);
}