Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
- [Components](how-it-works/air/components.md)
- [Prover Components](how-it-works/air/prover_components.md)

- [Circle FRI](how-it-works/circle-fri/index.md)
- [Technical Overview](how-it-works/circle-fri/overview.md)
- [FRI Prover](how-it-works/circle-fri/fri_prover.md)
- [FRI Verifier](how-it-works/circle-fri/fri_verifier.md)

- [Lookups](how-it-works/lookups.md)

- [Awesome Stwo](awesome-stwo/index.md)
Expand Down
150 changes: 150 additions & 0 deletions src/how-it-works/circle-fri/fri_prover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# FRI Prover


In this section, we examine the FRI prover implementation, beginning with the FRI protocol configuration.

## FRI Protocol Configuration


We configure the FRI protocol using the following parameters:
- Log of blowup factor $\beta$
- Log of last layer degree bound (determines the number of rounds $r$ in the FRI protocol)
- Number of queries $s$ made by the verifier in the query phase

It is implemented as follows:
```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/core/fri.rs 29:34}}
```

We calculate the security bits of our protocol as follows:
```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/core/fri.rs 68:70}}
```
This is as we discussed in the [Security Analysis section](./overview.md#security-analysis).

## Proving

Let us look into how the FRI prover struct is implemented.
```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/prover/fri.rs 77:82}}
```

Here, `FriOps` is a trait which implements functionality for the commit phase of FRI, such as folding the evaluations, and `MerkleOps` is the trait used in the [Merkle commitment scheme](../vcs/hash_functions.md#merkleops-trait). The generic `B` refers to a specific backend, for example either `CpuBackend` or `SimdBackend`, which implements the `FriOps` and `MerkleOps` traits.


We described [FRI](./overview.md#protocol) as an interactive protocol between the prover and the verifier. To make the protocol non-interactive, we use the [Fiat-Shamir transform](https://en.wikipedia.org/wiki/Fiat%E2%80%93Shamir_heuristic), where both the prover and verifier use a channel to hash the transcript and generate random challenges. These functionalities are defined by the `MerkleChannel` trait. In the non-interactive protocol, oracles to functions are replaced by Merkle commitments to their evaluations, and queries to the oracle by the verifier are replaced by Merkle decommitments, which the prover appends to the channel.


The `FRIProver` struct is composed of several layers. Each layer contains a Merkle tree that commits to the evaluations of a polynomial for that layer. The main components are:

**• `config`**: The `FriConfig` discussed in the previous section, which holds protocol parameters.

**• `first_layer`**: The first layer of the FRI protocol, containing the commitment to the initial set of columns.

```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/prover/fri.rs 278:281}}
```
For example, the `columns` are the array of evaluations $[h_{0}, h_{1}, h_{2}]$, and `merkle_tree` commits to $h_{0} \in F^{H_0}$, $h_{1} \in F^{H_1}$, and $h_{2} \in F^{H_2}$ using a single Merkle tree.

**• `inner_layers`**: The inner layers of FRI, each representing a folding round and its corresponding Merkle commitment.

```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/prover/fri.rs 363:366}}
```
In our example, there are two FRI inner layers: the first contains evaluations of $g_0$ over the "line" domain $F^{I_0}$ with a Merkle commitment to $g_0$, and the second contains evaluations of $g_1$ over $F^{I_1}$ with its Merkle commitment.

**• `last_layer_poly`**: The last layer polynomial, which the prover sends in clear to the verifier.

```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/core/poly/line.rs 108:116}}
```
For our example, this is the polynomial $g_2$ in coefficient representation.

### Commitment

The `commit` function corresponds to the commitment phase of our protocol and outputs the `FriProver` struct. This function handles multiple mixed-degree polynomials, each evaluated over domains of different sizes. We will now give a high-level overview of the function as it is implemented in Stwo.

```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/prover/fri.rs 102:129}}
```
It takes the following inputs:
- `channel`: The Merkle channel used for the Fiat-Shamir transform to generate random challenges and maintain the transcript.
- `config`: The `FriConfig` containing protocol parameters.
- `columns`: The array of evaluations of the functions. For our example, this will contain $[h_0, h_1, h_2]$ over their respective domains $[H_0, H_1, H_2]$.
- `twiddles`: The [precomputed twiddle](../circle-fft/twiddles.md#twiddle-tree) values needed for folding.


The commitment phase consists of the following steps, corresponding to the protocol rounds described in the [overview](./overview.md#protocol):

1. **First Layer Commitment** (`commit_first_layer`):
- Takes the input functions $[h_0, h_1, h_2]$ and creates a Merkle commitment to all of them using a single Merkle tree.
- Commits to the root of the Merkle tree by appending it to the channel as part of the transcript.
- Returns the `FriFirstLayerProver` containing the columns and their Merkle commitment.

2. **Inner Layers Commitment** (`commit_inner_layers`):
- Performs the folding rounds as described in the protocol.
- In each round $i$:
- Decomposes the previous round "line" polynomial $g_{i-1}$ into $g_{i-1, 0}$ and $g_{i-1, 1}$.
- Decomposes the current round "circle" polynomial $h_i$ into $h_{i, 0}$ and $h_{i, 1}$.
- Receives random challenge $\lambda_i$ from the channel.
- Folds the decomposed functions to compute $g_i$ over domain $I_i$.
- Creates Merkle commitment to $g_i$ and adds the root of the Merkle tree to the channel.
- For our example with $r=2$, this creates two inner layers containing $g_0$ and $g_1$.
- Returns the following two objects:
- Two `FriInnerLayerProver` corresponding to $g_0$ and $g_1$.
- Final `last_layer_evaluation`, i.e., evaluations of $g_2$ over the domain $I_2$.

3. **Last Layer Commitment** (`commit_last_layer`):
- Takes the final evaluation $g_r$ (which will be sent to the verifier in clear).
- Interpolates it to coefficient form and appends the coefficients into the channel as protocol transcript.
- For our example, this converts $g_2 \in F^{I_2}$ to polynomial coefficient representation.
- Returns the `last_layer_poly`.


The function then constructs and returns the complete `FriProver` struct containing all layers, which will be used later for decommitment during the query phase.

### Decommitment


Now we will look at the decommit function. It is implemented as follows:

```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/prover/fri.rs 232:240}}
```

It takes the following input:
1. `self`: The `FriProver` containing the Merkle tree commitments to all the FRI layers.
2. `channel`: The Fiat-Shamir channel used to hash the transcript and generate the random query points.

Let us walk through the function step by step.
1. **Setup Query Generation**: Use the Fiat-Shamir channel to generate `n_queries` random positions on the maximum domain.
2. **Map Query Positions by Domain Size**: The function `get_query_positions_by_log_size` takes `queries` and `column_log_sizes` as input and maps each domain size to its respective query position in the column.
3. **Generate Proof**: The function `decommit_on_queries` generates the proof `FriProof` using the queries. The struct `FriProof` contains the Merkle decommitments for each layer with respect to the query positions.
```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/core/fri.rs 422:426}}
```


For our example, the components of `FriProof` will be as follows:
- `first_layer`: The decommitments to query positions for $h_0$, $h_1$, and $h_2$.
- `inner_layers`: There will be two inner layer proofs, i.e., one for the decommitments of $g_0$ and another for decommitments of $g_1$.
- `last_layer_poly`: This will be the $g_2$ polynomial represented in coefficient form.


4. Return the following objects:
- `proof`: The `FriProof` struct with all layer decommitments.
- `query_positions_by_log_size`: The query mapping from domain log sizes to their respective query positions.



Now let us look at the key function `decommit_on_queries` in detail.
```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/prover/fri.rs 245:272}}
```

The function `decommit_on_queries` generates the `FriProof` struct by decommitting all layers. Suppose there is a single query corresponding to point $P_0 = (x_0, y_0) \in H_0$ and let $P_i = \pi^i(x_i, y_i) \in H_i$.
1. **Decommit First Layer**: This provides Merkle tree decommitments for queried positions with respect to the first layer. This provides evaluations $h_0(P_0), h_0(-P_0)$, $h_1(P_1), h_1(-P_1)$, and $h_2(P_2), h_2(-P_2)$ along with their Merkle decommitments in the Merkle tree containing the first layer.
2. **Process Inner Layers with Folding**: We process the decommitment layer by layer. For our example, this proceeds as follows:
- For the first inner layer: Provide the evaluation $g_0(x_0), g_0(-x_0)$ along with their Merkle decommitments.
- For the second inner layer: Provide the evaluation $g_1(x_1), g_1(-x_1)$ along with their Merkle decommitments.
3. **Assemble Final Proof**: Combines all layer decommitments with the last layer polynomial $g_2$ into `FriProof`.
119 changes: 119 additions & 0 deletions src/how-it-works/circle-fri/fri_verifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# FRI Verifier


In this section, we describe the implementation of the FRI verifier in Stwo, continuing the example from the technical overview and prover documentation. The verifier's role is to check that all folding steps have been performed correctly by the prover.

The `FriVerifier` struct verifies the FRI proof generated by the prover.
```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/core/fri.rs 84:97}}
```

The main components are:

- **config**: The `FriConfig` containing protocol parameters.
- **first_layer**: The first layer verifier, which checks Merkle decommitments for all initial circle polynomials (i.e., $h_0$, $h_1$, $h_2$).
- **inner_layers**: A vector of inner layer verifiers, each corresponding to a folding round and its Merkle decommitment (i.e., two layers, corresponding to $g_0$ and $g_1$).
- **last_layer_domain**: The "line" domain for the final layer polynomial (i.e., $I_2$).
- **last_layer_poly**: The final "line" polynomial $g_2$ sent in clear by the prover.
- **queries**: The set of queries used for spot-checking the folding identities.

## Initialization


The `commit` function initializes the `FriVerifier` using the FRI proof, the Fiat-Shamir channel, and protocol parameters. It sets up the verifier for the query phase.

```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/core/fri.rs 116:186}}
```


The inputs are as follows:
- `channel`: The Fiat-Shamir channel for randomness and transcript hashing.
- `config`: The `FriConfig` with protocol parameters.
- `proof`: The `FriProof` struct containing Merkle decommitments and the last layer polynomial.
- `column_bounds`: The degree bounds for each committed circle polynomial, in descending order.



At a high level, it proceeds as follows:
1. Initializes the first layer verifier with the Merkle decommitments for circle polynomials $h_0$, $h_1$, $h_2$, their respective domains $H_0$, $H_1$, $H_2$, their degree bounds, and folding randomness $\lambda_0$.
2. Initializes each inner layer verifier with its Merkle decommitment, domain, degree bounds, and folding randomness. For our example:
- The first inner FRI verifier layer will store decommitments for $g_0$, its domain $I_0$, degree bound, and folding randomness $\lambda_1$.
- Similarly, the second inner FRI verifier layer will store decommitments for $g_1$, its domain $I_1$, degree bound, and folding randomness $\lambda_2$.
3. Stores the last layer polynomial $g_2$ and its domain $I_2$.
4. Initializes the `queries` as `None` and prepares the verifier for the query phase.
5. Outputs the `FriVerifier` struct.

## Query generation


The function `sample_query_positions` uses the Fiat-Shamir channel to generate random query positions for checking the folding equations. It maps each domain size to its respective query positions, ensuring that queries are adapted to the domain of each polynomial.

```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/core/fri.rs 306:320}}
```


It takes the following input:
- `&mut self`: This will be used to update the `queries` in `FriVerifier`, which was initialized to `None`.
- `channel`: The Fiat-Shamir channel.


At a high level, it proceeds as follows:
1. Samples `n_queries` random positions on the largest domain using the `channel`.
2. Returns a mapping from domain log sizes to their respective query positions using the function `query_positions_by_log_size`.


Suppose $s=1$ query is sampled at $P_0 = (x_0, y_0) \in H_0$. The function computes the corresponding positions $P_1 = \pi(P_0)$ in $H_1$ and $P_2 = \pi(P_1)$ in $H_2$, mapping each to the correct domain size.

## Verification


The function `decommit` verifies the `FriVerifier` after it has been initialized with the `FriProof` by verifying the Merkle decommitments and folding equations for all layers. It ensures that the prover's folding steps were performed correctly and that the final polynomial is close to the expected polynomial space.

```rust,no_run,noplayground
{{#webinclude https://raw.githubusercontent.com/starkware-libs/stwo/0790eba46b8af5697083d84fb75bd34b08a0b31f/crates/stwo/src/core/fri.rs 199:218}}
```


It takes the following as input:
- `mut self`: The `FriVerifier` struct after initialization using the `commit` function.
- `first_layer_query_evals`: The evaluations of the circle polynomials at the sampled query points.

It proceeds as follows:
1. **First Layer Verification:**
- Verifies Merkle decommitments for $h_0$, $h_1$, $h_2$ at the queried points $P_0$, $P_1$, and $P_2$, respectively.
- Extracts the necessary evaluations for folding into the next layer.
2. **Inner Layer Verification:**
- For each inner layer, verifies Merkle decommitments for $g_0$, $g_1$ at the folded query points $x_0$ and $x_1$, respectively.
- Checks the following two folding equations using the folding randomness and the evaluations from previous layers.
$$
\begin{aligned}
g_0(x_0) &=
\lambda_0^2 \cdot
\left(
\frac{h_0(x_0,y_0) + h_0(x_0,-y_0)}{2}
+ \lambda_0 \cdot
\frac{h_0(x_0,y_0) - h_0(x_0,-y_0)}{2 \cdot y_0}
\right)
\end{aligned}
$$
$$
\begin{aligned}
g_1(x_1) &=
\frac{g_{0}(x_{0}) + g_{0}(-x_{0})}{2}
+ \lambda_1 \cdot
\frac{g_{0}(x_{0}) - g_{0}(-x_{0})}{2 \cdot x_{0}} \\
&\quad + \lambda_1^2 \cdot
\left(
\frac{h_1(x_1,y_1) + h_1(x_1,-y_1)}{2}
+ \lambda_1 \cdot
\frac{h_1(x_1,y_1) - h_1(x_1,-y_1)}{2 \cdot y_1}
\right)
\end{aligned}
$$
where $P_0 = (x_0, y_0)$ and $P_1 = \pi((x_0, y_0)) = (x_1, y_1)$.

3. **Last Layer Verification:**
- Checks that the final polynomial $g_2$ matches the evaluations at the last layer's query positions.
4. Returns success if all checks pass; otherwise, returns an error indicating which layer failed.
10 changes: 10 additions & 0 deletions src/how-it-works/circle-fri/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Circle FRI

> This section introduces the Circle FRI protocol, which is used to check proximity to low-degree polynomials. We provide a technical overview, walk through a concrete example, and detail the implementation of both the prover and verifier.

This section is organized as follows:

- **[Technical Overview](./overview.md)**: Explains the mathematical foundations and protocol steps of Circle FRI, including a multi-table example and security analysis.
- **[FRI Prover](./fri_prover.md)**: Describes the implementation of the prover, including protocol configuration, commitment, and decommitment phases.
- **[FRI Verifier](./fri_verifier.md)**: Details the verifier's implementation, covering query generation and verification logic.

Loading