Validium Docs
  • Overview
  • Connect to Validium
  • Start Coding 🚀
    • Quickstart
      • Overview
      • Deploy using Validium CLI
      • Deploy using Quickstart Repository
      • Deploy your first contract
      • Create an ERC20 token
  • Tooling
    • Block Explorers
    • Hardhat-Validium
      • Overview
      • Installation
      • Guides
        • Getting started
        • Migrating Hardhat project to Validium
        • Compiling non-inlinable libraries
      • Plugins
        • hardhat-zksync
        • hardhat-zksync-solc
        • hardhat-zksync-vyper
        • hardhat-zksync-deploy
        • hardhat-zksync-upgradable
        • hardhat-zksync-verify
        • hardhat-zksync-verify-vyper
        • hardhat-zksync-ethers
        • hardhat-zksync-node
        • Hardhat Community Plugins
    • Foundary
      • Overview
      • Installation
      • Getting Started
      • Migration Guide
        • Overview
        • Compilation
        • Deployment
        • Testing
  • Test and Debug
    • Getting Started
    • Docker L1 - L2 Nodes
    • In-Memory Node
    • Continuous Integration
    • Hardhat
    • Foundry
  • API Reference
    • Overview
    • Conventions
    • ZKs JSON-RPC API
    • Debug JSON-RPC API
    • Ethereum JSON-RPC API
    • PubSub JSON-RPC API
  • Concepts
    • Transaction Lifecycle
    • Blocks and Batches
    • Validium Network Fee Mechanism
    • Finality
    • System Upgrades
    • ZK Chains
    • Data Availability
      • Overview
      • Recreating L2 state from L1 pubdata
      • Validiums
    • Account Abstraction
    • L1 <-> L2 Communication
  • Components
    • Overview
    • Smart & System Contracts
      • Smart Contracts
      • System Contracts
    • Shared Bridges
    • Sequencer / Server
    • Validium Network EVM
      • Overview
      • Bootloader
      • Precompiles
      • Virtual Machine Specification
        • ZKsync Virtual Machine primer
        • VM Formal Specification
    • Prover
      • Overview
      • ZK Terminology
      • Running the Prover
      • Circuits
        • Overview
        • Circuit Testing
        • CodeDecommitter
        • DemuxLogQueue
        • ECRecover
        • KeccakRoundFunction
        • L1MessagesHasher
        • LogSorter
        • Main VM
        • RAMPermutation
        • Sha256RoundFunction
        • StorageApplication
        • Sorting and Deduplicating
          • Overview
          • SortDecommitments
          • StorageSorter
          • LogSorter
      • Boojum Gadgets
      • Boojum Function - `check_if_satisfied`
    • Compiler
      • Compiler Toolchain Overview
        • Compiler Toolchain Overview
        • Solidity Compiler
        • Vyper Compiler
        • LLVM Framework
      • Specification
        • Overview
        • Code Separation
        • System Contracts
        • Exception Handling
        • EVM Legacy Assembly Translator
        • Instructions
          • Instruction Reference
          • EVM
            • Native EVM Instructions
            • Arithmetic
            • Bitwise
            • Block
            • Call
            • Create
            • Environment
            • Logging
            • Logical
            • Memory
            • Return
            • Sha3
            • Stack
          • Extensions
            • Overview
            • Validium Network Extension Simulation (call)
            • Validium Network Extension Simulation (verbatim)
          • EVM Legacy Assembly
          • Yul
        • EraVM Binary Layout
    • Fee Withdrawer
    • Portal - Wallet + Bridge
    • Block Explorer
    • Transaction filtering
Powered by GitBook
On this page
  • L1MessagesHasher PI
  • Input
  • Output
  • FSM Input and FSM Output
  • Main circuit logic
  • First part
  • Main part
  • Final part
  1. Components
  2. Prover
  3. Circuits

L1MessagesHasher

PreviousKeccakRoundFunctionNextLogSorter

Last updated 8 months ago


L1MessagesHasher PI

Input

pub struct LinearHasherInputData<F: SmallField> {
    pub queue_state: QueueState<F, QUEUE_STATE_WIDTH>,
}

Output

pub struct LinearHasherOutputData<F: SmallField> {
    pub keccak256_hash: [UInt8<F>; 32],
}

FSM Input and FSM Output

() // this circuit has big capacity, so we don't need several instances

Main circuit logic

It takes a queue of L1 messages and hash everything with keccak.

It can be spited into 3 parts:

First part

Firstly, we allocate the “input” part of PI (start flag, Input and FSM Input):

let mut structured_input =
    LinearHasherInputOutput::alloc_ignoring_outputs(cs, closed_form_input.clone());

let start_flag = structured_input.start_flag;
let queue_state_from_input = structured_input.observable_input.queue_state;

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

Also, we do some checks for them and allocate empty hash state:

let keccak_accumulator_state =
    [[[zero_u8; keccak256::BYTES_PER_WORD]; keccak256::LANE_WIDTH]; keccak256::LANE_WIDTH];

let mut keccak_accumulator_state =
    keccak_accumulator_state.map(|el| el.map(|el| el.map(|el| el.get_variable())));

Main part

This part is the main one. We run a loop with some limit, where on each iteration we try to pop the next element from the queue, if it’s not empty.

let queue_is_empty = queue.is_empty(cs);
let should_pop = queue_is_empty.negated(cs);

let (storage_log, _) = queue.pop_front(cs, should_pop);

Then we absorb it to the buffer, and if it’s full we run a round function.

if buffer.len() >= 136 {
    let buffer_for_round: [UInt8<F>; KECCAK_RATE_BYTES] = buffer[..136].try_into().unwrap();
    let buffer_for_round = buffer_for_round.map(|el| el.get_variable());
    let carry_on = buffer[136..].to_vec();

    buffer = carry_on;

    // absorb if we are not done yet
    keccak256_conditionally_absorb_and_run_permutation(
        cs,
        continue_to_absorb,
        &mut keccak_accumulator_state,
        &buffer_for_round,
    );
}

If this element was the last one, we create a padding and run a round function.

if tail_len == KECCAK_RATE_BYTES - 1 {
    // unreachable, but we set it for completeness
    last_round_buffer[tail_len] = UInt8::allocated_constant(cs, 0x81);
} else {
    last_round_buffer[tail_len] = UInt8::allocated_constant(cs, 0x01);
    last_round_buffer[KECCAK_RATE_BYTES - 1] = UInt8::allocated_constant(cs, 0x80);
}

let last_round_buffer = last_round_buffer.map(|el| el.get_variable());

// absorb if it's the last round
keccak256_conditionally_absorb_and_run_permutation(
    cs,
    absorb_as_last_round,
    &mut keccak_accumulator_state,
    &last_round_buffer,
);

Final part

Firstly, we verify that the queue is empty now.

let completed = queue.is_empty(cs);
Boolean::enforce_equal(cs, &completed, &boolean_true);

Then we compute the final hash and create an output.

// squeeze
let mut keccak256_hash = [MaybeUninit::<UInt8<F>>::uninit(); keccak256::KECCAK256_DIGEST_SIZE];
for (i, dst) in keccak256_hash.array_chunks_mut::<8>().enumerate() {
    for (dst, src) in dst.iter_mut().zip(keccak_accumulator_state[i][0].iter()) {
        let tmp = unsafe { UInt8::from_variable_unchecked(*src) };
        dst.write(tmp);
    }
}

let mut observable_output = LinearHasherOutputData::placeholder(cs);
observable_output.keccak256_hash = keccak256_hash;

Finally, we compute a commitment to PI and allocate it as witness variables.

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);
}

The main logic is implemented in linear_hasher_entry_point function .

GitHub
GitHub
here
GitHub
GitHub
GitHub