The motivation here was to try and work out how to index the rows in a Plonky2-style FRI protocol. More generally, other details like the FFT folding verifier are also worked out.
### Evaluation domains
Plonky2 uses cosets for evaluation domains (instead of subgroups). This is presumably the "out of domain sampling" idea (DEEP-FRI?).
where $\mathbf{g}\in\mathbb{F}^\times$ is a generator of $\mathbb{F}^\times$, and $\omega\in\mathbb{F}^\times$ is an $N\cdot 2^r=2^{(n+r)}$-th root of unity, where $n=\log_2(N)$ is the logarithm of the size $N$ of the original data, and $r=-\log_2(\rho)$ is the "rate bits".
Then if we fold by a factor of $K=2^\kappa$, the next domain will be
And so on. Of course we don't have to use the same $k$ at every folding step (this is called "reduction strategies" in Plonky2)
### Basic FRI verification workflow
Given a query index $\mathsf{idx}$, we
- first check the opened row against the Merkle root of the LDE (low-degree extended) matrix with a Merkle proof ("initial tree proof" in Plonky2 lingo)
- combine the opened row with $\alpha\in\widetilde{\mathbb{F}}$ to get the "upstream value"
- then repeatedly fold:
- open the "folding coset" from the commit phase Merkle tree (checking the corresponding Merkle proof)
- check the upstream value against the corresponding element in the coset
- calculate a small FFT on this coset (see below for details)
- combine the transformed values with the step's $\beta\in\widetilde{\mathbb{F}}$ (a new $\beta$ is sampled for each folding step, during the commit phase)
- this will be the next step's "upstream value"
- loop until the folded degree becomes small enough
- check the final polynomial (sent as coefficients) against the last upstream value by evaluating at the given location.
### Discrete Fourier Transform (DFT)
Given a subgroup $H=\langle \omega \rangle\subset \mathbb{F}^\times$ of size $|H|=K$, the DFT and its inverse converts between the coefficient form and evaluation form of a polynomial $f(x)\in\mathbb{F}^{<K}[x]$ofdegree$K-1$:
So this what the prover does, internally; meantime the verifier, in each query round, gets the values of these polynomials on a coset $\mathcal{C}\subset \mathcal{D}$ of size $|\mathcal{C}|=K=2^\kappa$ in the evaluation domain $\mathcal{D}=\{\mathbf{h}\cdot \omega^i\}$ of size $N$:
Now substituting the coset elements $x_m=\mathbf{h}\cdot \omega^{\mathsf{idx}}\mu^m$ and the corresponding vlaues $y_m=\mathcal{P}(x_m)$ into our formula for $p_l(x^K$), we get:
in which we can recognize the inverse coset DFT formula from above.
TODO: maybe make the indices notation more consinstent...
**Winograd small FFT**
For small sized FFTs with specific sizes, up to maybe size $2^5=32$, there are specialized FFT algorithms with a bit fewer number of multiplications than the standard one.
This may be worthwhile to investigate and benchmark.
### Row ordering
First a remark about FFT. Some (in-place) FFT implementations naturally give the result in the "bit-reversed" permutation. This is the permutation where the vector indices, written in binary, have their bits reversed:
As our FFT implementation gives the result in the natural order (cyclic group order), and bit-reversal just makes everything harder to understand, we always use the natural order here.
It's straightforward, if confusing, to adapt everything for a bit-reversed FFT (for example Plonky2 indices mostly everything in the bit-reversed order).
#### Matrix row ordering
As we only open single, randomly-selected rows, the Merkle tree ordering doesn't matter for efficiency.
However, to be able prove the connection of the original data and the parity data, we need these to be subtrees.
Since with FFT, we normally put the original data on a subgroup, and the parity data on its $2^r-1$ cosets, the most natural indexing is the following:
Note: using $r>1$ (so $R>2$ or $\rho<1/2$)intheFRIprotocolmaymakesenseevenifweonlykeepasmalleramountoftheparitydataattheend(tolimitthestorageoverhead),asitmayimprovesoundnessordecreaseproofsize(whilealsomakingtheprooftimelonger).**WARNING:**Thismayhavenon-trivialsecurityimplications?
Here we open cosets of size corresponding to the folding step arity $K$ (which can be different in the different folding steps).
So such cosets should be the leaves of the tree. If the size of the of the vector is $M$, then the cosets (in natural ordering) are indexed as:
$$ \mathcal{C}_i = \big\{\, i ,\, i+M/K ,\, i + 2(M/K) ,\, \dots ,\, i + (K-1)(M/K) \,\big\} $$
for $0\le i <M/K$.ThustheMerkletreeshouldhavesize$M/K$andwiththe$i$-thleafbeing$\mathcal{C}_i$.
Of course we could permute the order of the leaves as we want, but this seems to be most natural order.
Remark: Which coset we want to open is determined by the "upstream index" $0\le \mathsf{idx} <M$.Togetthecorrespondingcosetindexwecansimplycompute
$i:= \mathsf{idx}\;\textrm{mod}\;M/K$. However, to match the above FFT computation, we also have to cyclically permute the coset, so that it starts at $\mathsf{idx}$. In practice this mean rotating the opened coset by $\lfloor \mathsf{idx}/(M/K)\rfloor$.
Alternatively, instead of "rotating" the coset, we can instead calculate which element of the coset should match the upstream value. This is maybe a little bit more intuitive.