# How the Light Client Works

The source code for the light client contract can be found on GitHub.

The `LightClient`

contract validates the HotShot state through cryptographic proofs (SNARK proofs). Rollups can use the new `finalizedState`

from the `LightClient`

contract to confirm the validity and finality of transactions that have been bundled and processed.

## Light Client State

This `LightClientState`

includes information such as:

the Merkle root of finalized block commitments,

`blockCommRoot`

the fee ledger commitment,

`feeLedgerComm`

the active stake table commitment,

`stakeTable*Comm`

the latest view number,

`viewNum`

, and block height,`blockHeight`

, of the finalized HotShot chain

For the next release, a fixed stake table is used so stake table commitments will not be updated.

The most important part of the `LightClientState`

is the `blockCommRoot`

, which is the root of an append-only Merkle tree of all blocks finalized by HotShot. This root is public, allowing other contracts to use succinct Merkle proofs to verify the inclusion of a certain block commitment at a certain height and to update the root when new blocks are appended.

At any given time, the `LightClientState`

contains Espresso block commitments from height 0 up to (but not including) the current block height, `blockHeight`

. All commitments for heights greater or equal to `blockHeight`

are not present—the corresponding leaf is empty in the tree.

### Rollups and the Light Client Contract

Rollup contracts on L1 must use the `blockCommRoot`

when validating a state transition to ensure that the rollup block claimed to have been executed is indeed the next block in the canonical sequence.

The proposer of a rollup state transition must provide a proof, relative to `blockCommRoot`

, showing that the Espresso state commitment at a specified height is consistent with the rollup block commitment.

For rollups to integrate with Espresso, they need to modify their contract on L1 to prove that their state is derived from the Espresso state. For instance, consider a scenario where the Espresso prover pushes a new Espresso state commitment to the light client contract every 1 minute, and the rollup prover submits a new rollup block commitment every 10 minutes to their rollup contract. The rollup prover needs to prove that the block commitment it publishes to the rollup contract corresponds to all its rollup transactions contained in the Espresso blocks during the 10-minute period.

Each Espresso block commitment also commits to a list of rollup transactions (among other metadata) which facilitates lightweight proofs with transaction granularity for arbitrarily old rollup blocks. This approach allows for the verification of these rollups blocks on Espresso's chain and enables the light client contract to operate with a constant amount of storage, irrespective of the HotShot chain's length.

### Data Availability

Since the actual block commitments (let alone the full blocks) are not stored on-chain, it is important to understand the data availability properties ensuring that clients can always retrieve an old block, block commitment, or a block's Merkle proof.

Clients can fetch a Merkle proof for any block from an archival query service and authenticate it against the block root in the light client state. Failing that, they can fetch the individual blocks from HotShot DA and extract the proof themselves.

### ZK Proofs, SNARK Proofs, and ZK Circuits

A circuit defines the computation to be proven. Zero-knowledge proof (ZKP) systems can generate cryptographic proofs attesting to the validity of statements described by the circuit without revealing underlying witness. In our context, we rely on SNARK proofs (a special kind of ZKP), whose succinct nature is especially valuable for verifying computation in smart contracts where gas costs are a critical concern. Irrespective of the number of signatures/consensus votes, the size and verification cost of the proof remain constant.

In a zero-knowledge protocol there are two main roles: (i) the *prover* and (ii) the *verifier*.

The *prover* uses a combination of secret inputs (HotShot nodes' Schnorr signatures), also called witnesses, public inputs (the `LightClientState`

), and a circuit description in order to generate a SNARK proof.

The *verifier* uses the public inputs and the SNARK proof to verify that the rules defined by the ZK circuit are satisfied. In this case, the `LightClient`

contract acts as the verifier for this ZK proof via the `verifyProof`

method which is invoked within the `newFinalizedState`

function.

Finally, both the prover and verifier use some public parameters. These public parameters are derived from the circuit and a structured reference string (SRS) that requires a trusted setup to be generated but can be reused for other circuits.

### The Light Client Circuit

Public input:

Secret witness:

Relation:

we use Rescue-based commitment, thus all operations are native

### Updating and Verifying LightClientState

The `LightClientState`

is updated by any state prover that submits valid updates to the `LightClient`

contract via the `newFinalizedState`

method which sets the latest `finalizedState`

.

We assume an *altruistic, honest prover* for now and leave the design of prover market to future work.

For replicas:

upon receiving new QC from the leader, generate a Schnorr signature over the updated finalized HotShot state, and send it over to the CDN

*and*store it locally for a while.the local storage for the Schnorr signatures (for each block) can be a sliding window of a fixed size where older signatures got pruned. The window size can be set based on the expected interval for on-chain update plus some buffer accounting for temporarily failing prover.

For the altruistic prover:

will continuously listen passively for the HotShot state changes, e.g. when a new block has been decided

For convenience of signature collection, prover will first fetch from CDN (we refer to as the relay server) for the list of Schnorr signatures, if failed, then ask each individual replica (note: not small DA committee, or VID, but each node individually).

Once a sufficient amount of valid signatures is collected, some provers can then generate a SNARK proof which is submitted alongside the new finalized state to the light client contract.

For the next release, only a permissioned prover doing the computations will call this function.

Replica nodes update the snapshot of the stake table at the beginning of an epoch and this snapshot is used to define the set of stakers for the next epoch. The light client state must be updated at least once per epoch.

For the next release, we are not using epochs so `numBlockPerEpoch`

is set to `type(uint32).max`

during deployment.

## HotShot state authentication via Schnorr signatures

When a set of HotShot nodes reach consensus and the finalized HotShot state has been updated, they each sign a Schnorr signature on this updated HotShot state. These signatures assert that the signer agrees with the state of each proposed block. The signatures are stored locally on the DA layer, and to save space, older signatures are pruned using a sliding window mechanism. The window size can be set based on the expected interval for on-chain update plus some buffer accounting for a temporarily failing prover.

When a prover (an entity that confirms the truth of a claim) retrieves these signatures, a SNARK proof is then generated. This proof, is used by the `LightClient`

contract to efficiently verify these Schnorr signatures. The state of the sequencer contract can be updated only if a correct SNARK proof is provided. This is a critical step that ensures the validity and security of state updates in Espresso's consensus protocol.

The proof of the Schnorr signatures is sent to the `newFinalizedState`

function of the `LightClient`

contract.

## Verifying the Signatures and Light Client State

The `LightClient`

contract also does the work of verifying the proof that is sent by the prover on L1. The `verifyProof`

method accepts the proof and a set of public inputs (the `LightClientState`

) to check whether the proof correctly verifies the new state being submitted.

Verifying a SNARK proof requires a constant amount of space and computation, no matter how many HotShot node signatures are involved. This is unlike verifying the signatures directly, which would require space and computation proportional to the number of signers.

The proof itself contains the HotShot state, the stake table info and the list of Schnorr signatures of the HotShot nodes that formed a Quorum and came to consensus on that state.

This `verifyProof`

method is executed when the `newFinalizedState`

method is called so that the new state is accepted only if the proof succeeds.

## Public Write Methods

### newFinalizedState

This method updates the latest finalized light client state. It is updated per epoch. An update for the last block for every epoch has to be submitted before any newer state can be accepted since the stake table commitments of that block become the snapshots used for vote verifications later on.

*in the next launch, only a permissioned prover doing the computations will call this function*

**Parameters**

### computeStakeTableComm

Given the light client state, compute the short commitment of the stake table

## Light Client Contract UML

## Light Client Contract Interaction Diagram

Last updated