LogoLogo
LogoLogo
  • Introduction
    • About Us
    • Notations & Definitions
      • MPC
      • ZK
    • Contribute to this Site!
  • Primitives
    • Multiplication
      • Karatsuba Multiplication
      • Toom-Cook Multiplication
    • NAF (Non-adjacent form)
    • Chinese Remainder Theorem (CRT)
    • Euclidean Algorithm
      • Extended Euclidean Algorithm
      • Binary Euclidean Algorithm
      • Extended Binary Euclidean Algorithm
    • Coding Theory
      • Linear Code
    • Number Theoretic Transform
    • Abstract Algebra
      • Group
        • -Morphisms
        • Batch Inverse
      • Elliptic Curve
        • Weierstrass Curve
          • Coordinate Forms
          • Fast Elliptic Curve Arithmetic and Improved WEIL Pairing Evaluation
        • Edwards Curve
          • Coordinate Forms
          • Twisted Edwards ↔ Short Weierstrass Transformation
        • Batch Inverse for Batch Point Additions
        • Scalar Multiplication
          • Double-and-add
          • GLV Decomposition
        • MSM
          • Pippenger's Algorithm
          • Signed Bucket Index
          • CycloneMSM
          • EdMSM
          • cuZK
        • 2-Chain and 2-Cycle of Elliptic Curves
    • Encryption Scheme
      • ElGamal Encryption
    • Modular Arithmetic
      • Modular Reduction
        • Barrett Reduction
        • Montgomery Reduction
      • Modular Inverse
        • Bernstein-Yang's Inverse
    • Multiset Check
    • Sumcheck
    • Commitment Scheme
      • Fflonk
      • SHPlonk
      • Zeromorph
  • MPC
    • Yao's Garbled Circuits
    • GMW
    • BMR
  • ZK
    • Arithmetization
      • R1CS
      • PLONK
      • AIR
    • Folding
      • LatticeFold
      • Nova
        • Nova over Cycles of Curves
    • Lookup
      • Lasso
      • LogUp-GKR
    • SNARK
      • Groth16
      • HyperPlonk
      • Spartan
        • SPARK
    • STARK
      • Additive NTT
      • Basefold
      • Binius
      • Brakedown
      • CircleSTARK
      • FRI
        • FRI Security Features and Optimizations
      • DEEP FRI
      • STIR
      • WHIR
    • Distributed ZK
      • Ryan's Trick for Distributed Groth16
  • Application
    • zkLogin
    • zkHoldem
    • zkTLS
      • DECO
      • Proxying is enough
  • zkVM
Powered by GitBook
On this page
  • Background
  • Reed-Solomon Encoding
  • Low-Degree Testing
  • The FRI Protocol
  • The Input
  • The Commit Phase
  • The Query Phase
  • Conclusion
  • References
Export as PDF
  1. ZK
  2. STARK

FRI

PreviousCircleSTARKNextFRI Security Features and Optimizations

Last updated 3 months ago

Short for “Fast Reed-Solomon Interactive Oracle Proofs of Proximity”, the FRI Protocol is a designed for zkSTARKS. All Interactive Oracle Proofs of Proximity (IOPPs) are designed to verify that a large dataset or computation closely matches a specific structure using only a few queries. FRI, in particular, addresses the challenge of low-degree testing within this framework. But what is “Low Degree Testing,” you ask? Let’s talk about the “Reed-Solomon” portion in FRI’s title to explain.

Background

Reed-Solomon Encoding

We’ll pull from and Vitalik’s article “” to explain how Reed-Solomon Encoding works and introduce the idea of “low-degree testing.” Feel free to take an additional look at to understand more about the technicalities behind Reed-Solomon codes.

The goal of Reed-Solomon encoding is to securely pass a message successfully to another party. Let’s look at an example:

Say User A wants to send the message “ABC” to a User B with Reed-Solomon encoding…

First, User A converts this message into a polynomial of degree "total values−1\text{total values} - 1total values−1." In our case, that’s len(ABC)−1=3−1=2\mathsf{len}(ABC) - 1 = 3-1 = 2len(ABC)−1=3−1=2.

Next, User A evaluates this polynomial on a larger domain of a coset and adds more values to hide the original message.

This set of evaluations is sent all together to User B and is named the “codeword.”

Let’s say User A was instead a malicious user and attempted to send a faulty codeword instead. We’ll change f(3)f(3)f(3) to PPP instead to illustrate how User B could successfully catch this mistake.

But what does a rejection actually mean? If we look at figure 2, we can see that if the codeword was valid, all the values should belong to the same line or to the polynomial of the same degree 2. In comparison, in figure 3, we see that one faulty value would jeopardize the degree of the resulting total polynomial by increasing it to a degree higher than 2. This thus introduces the problem of low-degree testing. Low-degree testing tries to ensure a polynomial is of low-degree, which would insinuate that the original polynomial or codeword is valid.

Low-Degree Testing

As you can now see, low-degree testing refers to the process of ensuring a polynomial is of low-degree or smaller than a certain degree. But why is FRI needed specifically for low-degree testing? Well, this ties back to the original goal of IOPPs to use fewer queries or reduce the overall query load. In our example earlier we mentioned that if User B queries a value, a total of d+1d+1d+1 other values must be needed to interpolate the polynomial at the given value, with d being the degree of the original polynomial. This results in the naive complexity of each query equaling O(d+1)O(d+1)O(d+1). However, as an IOPP, The FRI protocol manages to achieve low-degree testing with only O(log⁡(d))O(\log(d))O(log(d)) queries, as instead of using expensive interpolation, FRI folds the polynomial into a constant to check the degree.

The FRI Protocol

The Input

The input to the protocol is a polynomial f0(x)f_0(x)f0​(x) linked with a bounding degree by which the polynomial’s degree should be at or less than.

The Commit Phase

As aforementioned, the commit phase follows the prover folding the polynomial to a digestible size. Going forward, we’ll refer to example-specific sections with “(EX)”.

    1. (EX) The prover calculates f0(ω00)=A,f0(ω01)=B,f0(ω02)=C,f0(ω03)=Df_0({\omega_0}^0)=A, f_0({\omega_0}^1)=B,f_0({\omega_0}^2)=C,f_0({\omega_0}^3)=Df0​(ω0​0)=A,f0​(ω0​1)=B,f0​(ω0​2)=C,f0​(ω0​3)=D

    1. (EX) The prover creates a Merkle tree from A,B,C,DA, B, C, DA,B,C,D and commits the root RRR:

  1. After receiving the Merkle Root, the verifier replies with a challenge β\betaβ in response.

  2. With this β\betaβ, the prover calculates the folded polynomial. Let’s look at how this works:

First, f0(x)f_0(x)f0​(x) is split into two different parts: the terms with a degree of an even number and the terms with a degree of an odd number.

fo(x)=f0_even(x2)+X⋅f0_odd(x2)f_o(x) = f_{0\_\mathsf{even}}(x^2) + X \cdot f_{0\_\mathsf{odd}}(x^2)fo​(x)=f0_even​(x2)+X⋅f0_odd​(x2)

Then the folded polynomial is created from these two parts along with the challenge:

f1(x)=f0_even(x)+β0⋅f0_odd(x)f_1(x) = f_{0\_\mathsf{even}}(x)+\beta_0\cdot f_{0\_\mathsf{odd}}(x)f1​(x)=f0_even​(x)+β0​⋅f0_odd​(x)

Let’s check how this works with an example:

f0(x)=x4+5x3+9x2+8x+7f_{0}(x)=x^4+5x^3+9x^2+8x+7f0​(x)=x4+5x3+9x2+8x+7

Our even and odd degree terms would then be split up as such:

f0_even(x2)=x4+5x2+7f0_even(x)=x2+9x+7 x⋅f0_odd(x2)=5x3+8xf0_odd(x)=5x+8f_{0\_\mathsf{even}}(x^2) = x^4+5x^2+7\\f_{0\_\mathsf{even}}(x)=x^2+9x+7\\ \ \\x\cdot f_{0\_\mathsf{odd}}(x^2)=5x^3+8x\\f_{0\_\mathsf{odd}}(x)=5x+8f0_even​(x2)=x4+5x2+7f0_even​(x)=x2+9x+7 x⋅f0_odd​(x2)=5x3+8xf0_odd​(x)=5x+8

From these two split polynomials we can now create our folded polynomial:

f1(x)=(x2+9x+7)+β0⋅(5x+8)f1(x)=x2+(9+5β0)x+(7+8β0)f_1(x)=(x^2+9x+7)+\beta_0\cdot (5x+8)\\f_1(x)=x^2+(9+5\beta_0)x+(7+8\beta_0)f1​(x)=(x2+9x+7)+β0​⋅(5x+8)f1​(x)=x2+(9+5β0​)x+(7+8β0​)

Finally, we can see that our polynomial f0(x)f_0(x)f0​(x) of degree 4 has been successfully folded in half to polynomial f1(x)f_1(x)f1​(x) of degree 2. Thus all polynomials of degree ddd will be folded to a polynomial of degree ⌊d/2⌋\lfloor d/2 \rfloor⌊d/2⌋.

  1. Next, the prover is evaluated on the given sub-domain. Note that the domain size is reduced by half every fold, resulting in ω00=ω10{\omega_0}^0 ={\omega_1}^0ω0​0=ω1​0 and ω02=ω11{\omega_0}^2={\omega_1}^1ω0​2=ω1​1. In other words, ωi2n=ωi+1n{\omega_i}^{2n}={\omega_{i+1}}^nωi​2n=ωi+1​n.

    1. (EX) Let’s say our folded polynomial had the following evaluations f1(ω10)=Nf_1({\omega_1}^0)=Nf1​(ω1​0)=N and f1(ω11)=Of_1({\omega_1}^1) =Of1​(ω1​1)=O.

  2. Finally, the evaluations are used to create a Merkle tree, and the root of the resulting Merkle tree is committed.

    1. (EX) The prover creates a Merkle tree from NNN and OOO and commits the root RRR:

  3. This recursive folding process continues until the polynomial is reduced to a constant and committed as the final result. Note that the evaluations of the polynomials are seen as the codewords within Reed-Solomon encoding, hence, the title of FRI.

    1. (EX) The prover folds the polynomial one more time and gets f2(ω20)=Tf_2({\omega_2}^0)=Tf2​(ω2​0)=T and commits TTT.

The Query Phase

In the query phase, the verifier ensures that the prover’s polynomial evaluations are valid through queries. The following explanations describe the process of running a single query.

  1. The verifier queries a random challenge xxx to the prover.

  2. The prover sends fi(x),fi(−x)f_i(x),f_i(-x)fi​(x),fi​(−x) where iii starts at 000 and the individual Merkle tree proof for each evaluation back to the verifier.

  3. The verifier verifies the Merkle tree proofs.

  4. The verifier uses fi(x)f_i(x)fi​(x) and fi(−x)f_i(-x)fi​(−x) to create fi+1(x2)f_{i+1}(x^2)fi+1​(x2) themselves and checks with the prover’s given answer. How does the verifier do this? Let’s take a look:

fi(x)= fi_even(x2)+X⋅fi_odd(x2)fi(−x)= fi_even(x2)−X⋅ fi_odd(x2)f_i(x) = f_{i\_\mathsf{even}}(x^2)+X\cdot f_{i\_\mathsf{odd}}(x^2)\\f_i(-x) = f_{i\_\mathsf{even}}(x^2)-X\cdot f_{i\_\mathsf{odd}}(x^2)fi​(x)= fi_even​(x2)+X⋅fi_odd​(x2)fi​(−x)= fi_even​(x2)−X⋅ fi_odd​(x2)

Through solving the system of equations of fi(x)f_i(x)fi​(x) and fi(−x)f_i(-x)fi​(−x), we can get fi_even(x2)f_{i\_ even}(x^2)fi_even​(x2) and fi_odd(x2)f_{i\_odd}(x^2)fi_odd​(x2).

fi_even(x2)=fi(x)+fi(−x)2fi_odd(x2)=fi(x)−fi(−x)2xf_{i\_\mathsf{even}}(x^2)= \frac{f_i(x) + f_i(-x)}{2}\\f_{i\_\mathsf{odd}}(x^2)= \frac{f_i(x) - f_i(-x)}{2x}fi_even​(x2)=2fi​(x)+fi​(−x)​fi_odd​(x2)=2xfi​(x)−fi​(−x)​

Finally, with fi_even(x2)f_{i\_ even}(x^2)fi_even​(x2) and fi_odd(x2)f_{i\_ odd}(x^2)fi_odd​(x2) , we can create fi+1(x2)f_{i+1}(x^2)fi+1​(x2) or the next folded polynomial’s evaluations.

fi+1(x2)=fi_even(x2)+βi⋅fi_odd(x2) fi+1(x2)=fi(x)+fi(−x)2+βi⋅fi(x)−fi(−x)2x=xfi(x)+xfi(−x)+βifi(x)−βifi(−x)2x=(x+βi)fi(x)+(x−βi)fi(−x)2x=(1+βix−1)fi(x)+(1−βix−1)fi(−x)2f_{i+1}(x^2) = f_{i\_\mathsf{even}}(x^2)+\beta_i\cdot f_{i\_\mathsf{odd}}(x^2)\\ \ \\\begin{align*}f_{i+1}(x^2) &=\frac{f_i(x)+f_i(-x)}{2}+\beta_i\cdot\frac{f_i(x)-f_i(-x)}{2x}\\ &=\frac{xf_i(x)+xf_i(-x)+\beta_if_i(x)-\beta_if_i(-x)}{2x}\\&=\frac{(x+\beta_i)f_i(x)+(x-\beta_i)f_i(-x)}{2x}\\&=\frac{(1+\beta_ix^{-1})f_i(x)+(1-\beta_ix^{-1})f_i(-x)}{2}\end{align*}fi+1​(x2)=fi_even​(x2)+βi​⋅fi_odd​(x2) fi+1​(x2)​=2fi​(x)+fi​(−x)​+βi​⋅2xfi​(x)−fi​(−x)​=2xxfi​(x)+xfi​(−x)+βi​fi​(x)−βi​fi​(−x)​=2x(x+βi​)fi​(x)+(x−βi​)fi​(−x)​=2(1+βi​x−1)fi​(x)+(1−βi​x−1)fi​(−x)​​

Thus with the received fi(x)f_i(x)fi​(x) and fi(−x)f_i(-x)fi​(−x) and knowing xxx and βi\beta_iβi​, the verifier can create fi+1(x2)f_{i+1}(x^2)fi+1​(x2).

  1. The prover continues to send the fi(x)f_i(x)fi​(x) and fi(−x)f_i(-x)fi​(−x)evaluations incrementing iii by 111 each round, while the verifier continues calculating and verifying until the final evaluation is verified (i.e. recursively run steps 2-4).

  2. This query process is repeated to the desired amount of queries.

Conclusion

References

User B first receives the codeword from User A, and tries to check a random value in the codeword, otherwise known as querying. By chance, let’s say, User B tries to check f(3)f(3)f(3). User B uses User A’s codeword values excluding f(3)=Pf(3) = Pf(3)=P to create a Lagrange-based form of a polynomial through interpolation (See for more details on interpolation). In our case, this should be a polynomial of degree 2. Using this interpolated polynomial, User B can now calculate the value at x=3x=3x=3 themselves and find that f(3)f(3)f(3) should equal QQQ. Finally, they check this self-calculated value f(3)=Qf(3) = Qf(3)=Q against User A’s previously asserted value of f(3)=Pf(3) = Pf(3)=P and reject the User A’s codeword since the values are not equal.

As Risc Zero explains in their video the FRI protocol is a process in which a polynomial is folded a number of times to reduce the problem size and then verified by a limited number of queries. The FRI protocol is split into two phases: the Commit Phase and the Query Phase. In the commit phase, the prover recursively folds the polynomial and commits their evaluations, while in the query phase, the verifier checks the folded evaluations through their own calculations. Note that while the concept of the FRI protocol mentions folding can by done at any constant scale k, we will explain the process of 2-folding for simplicity and for its prevalence in FRI in code.

The naive version of the FRI protocol consists of the implementation outlined below, with a simple difference of making it non-interactive by introducing a separate challenger. To see naive FRI in code, refer to implementation for Rust or implementation in C++.

With knowledge of the input polynomial f0(x)f_0(x)f0​(x), the prover evaluates the polynomial on a given domain (f0(ω00),f0(ω01),...,f0(ω0n−1)f_0({\omega_0}^0), f_0({\omega_0}^1),..., f_0({\omega_0}^{n-1})f0​(ω0​0),f0​(ω0​1),...,f0​(ω0​n−1), where ω\omegaω is a and n is the domain size). This evaluation is usually done with (Fast-Fourier Transformation). These evaluations all together act as a single Reed-Solomon codeword as if a single evaluation is wrong and is caught, the polynomial is guaranteed to be of high-degree.

These evaluations are then used to create the leaves of a , and the resulting Merkle root is committed to the verifier [MT root]. These evaluations can also be interpreted as the Reed Solomon codeword of the FRI protocol.

The FRI Protocol stands as a valuable IOPP protocol to reduce the number of queries in the case of low-degree testing. While the general scheme simply involves recursively folding a polynomial, the additionally complicates the protocol. If you choose to dive more into the FRI protocol, I recommend you look at the following references for more information.

by the Risc Zero Study Club — A good intro to RS Codes

by the Risc Zero Study Club — A good intro to FRI

by the Risc Zero Study Club — A good view into the process of FRI

by — A good starter overview on FRI

by Vitalik Buterin — Provides insight to the concept behind FRI

by Elliptic Hector — A good visual of the FRI Protocol

— A good hands-on example of FRI

by LambdaClass — Provides a code and computation viewpoint of FRI

— An extensive dive into FRI

— The original FRI paper

by the StarkWare Team — Provides some valuable sections outlining security features and optimizations to add to FRI

by Matter Labs — Section 8 and Section F provide an in-depth view of added security features and optimizations

by Polygon Labs — An extensive view into Two Adic FRI

Written by from

Barycentric interpolation
“FRI in 2 minutes,”
LambdaClass’s
Kroma’s
root of unity
FFT
Merkle tree
numerous advances in adding security and optimizations
Reed-Solomon Codes
Intro to FRI
FRI Mechanics
Low Degree Testing: The Secret Sauce of Succinctness
StarkWare
STARKs, Part II: Thank Goodness It’s FRI-day
A Diagram of the FRI Protocol
Fast Reed-Solomon IOP (FRI) Proximity Test
How to code FRI from scratch
Anatomy of a STARK, Part 3: FRI
Fast Reed-Solomon Interactive Oracle Proofs of Proximity
ethSTARK Documentation — Version 1.2
RedShift: Transparent SNARKs from List Polynomial Commitments
A summary on the FRI low degree test
A41
Ashley Jeong
polynomial commitment scheme
Risc Zero’s intuitive explanation
STARKs, Part II: Thank Goodness It’s FRI-day
STIR
Figure 1. Reed Solomon Encoding — Converting a message into a polynomial
Figure 2. Reed Solomon Encoding — Extrapolating the polynomial into a larger codeword.
Figure 3. Reed Solomon Encoding — Checking a faulty value in a codeword.
Figure 4. Resulting Merkle Tree from the example evaluations of the original polynomial
Figure 5: Resulting Merkle Tree from the example’s first folded polynomial evaluations
Figure 6. The Commit Phase of the FRI protocol illustrated in a table, where n = domain size and m = the total number of folding rounds aka ⌈log(d)⌉ with d being the maximum degree
Figure 7. A single query of the Query Phase illustrated in a table, where n = domain size and m = the total number of folding rounds aka ⌈log(d)⌉ with d being the maximum degree