I have been reading Vitalik's series on STARKs recently (Part 1, 2 and 3). It is a nice and very understandable read for a layman like me.
Brutal summary of my current understanding
Vitalik outlines the following technique to prove the correctness of some arbitrary computation:
Encode the computation trace in the values of a polynomial P(x).
Define a constraint checking polynomial C(z) such that, if the computation is correct, we will have C(F(x)) = 0 for all values of x in the computation range. I will use Q to denote the degree of the composed polynomial C(F(x)).
(In general, C can take multiple arguments, e.g., C(z1, z2, z3) = z3 - z2 - z1 checks the correct computation of a Fibonacci sequence when we require C(F(x), F(x + 1), F(x + 2)) = 0).
Because C(F(x)) must be 0 for all x in 1..N, we define Z(x) = (x - 1) (x - 2) .. (x - N): C(F(x)) must be equal to Z(x) multiplied with some D(x). D is relatively easy to compute from C(F(_)) and P.
The prover commits to a table of the first M values of both P and D, with M >> Q (e.g., M = 100 Q).
The verifier probes the table at multiple values x, checking C(P(x)) = Z(x) D(x) at various points to be convinced that the computation was executed correctly.
So far so good. I think I understand. Vitalik goes in detail explaining two specific checks that can be done with constraint-checking polynomials:
- Range-checking (
0 <= P(x) <= H for all x in 1..N, with C(z) = z (z - 1) (z - 2) .. (Z - H).
- Fibonacci computation, with
C(z1, z2, z3) = z3 - z2 - z1.
My random attempt at generalization
Sadly, Vitalik's explanation ends there, short of explaining how STARKs can be generalized to prove arbitrary computation. I have an intuition of how it could be done, but I don't see how it could be done efficiently.
For example, I thought we could define two "operand polynomials" L(x) and R(x), then:
- Require
P(x) being either 0 or 1, for all x in 1..N (this can be easily done by range checking, which Vitalik already explains how to do).
- Additionally require
(1 - (P(L(x))P(R(x)))) - P(x) = 0 for all x in 1..N. In doing so, we require each P(x) to be equal to P(L(x)) NAND P(R(x)). Because any logic circuit can be built out of NANDs, a suitable choice of L and R should make any computation provable.
The big issue with this idea, however, is that the degree Q of the composition between P and the constraint-checking polynomial here is huge, in the order of N^2. Complexity for the prover would become unmanageable quite quickly.
Where do I read next?
I am wondering where I can read up how to define a constraint-checking polynomial that can actually verify any computation. The whole "accessing the memory at random locations" bit seems very tricky to me, and I'm eager to learn further.