├── .gitignore ├── fft ├── FFT-roots.png ├── bowers & dif fft.png ├── workflow of 4-step_FFT.png ├── coefficients to evaluations.png ├── evaluations to coefficients.png ├── radix_2_bowers.ipynb ├── four_step.ipynb └── radix_2_dit.ipynb ├── merkle-tree ├── mt.png └── merkle-tree.ipynb ├── Mersenne31 ├── circlestark.png ├── cfft.ipynb ├── circlestark.ipynb └── Mersenne31.ipynb ├── LICENSE ├── README.md ├── pcs ├── multilinear and univariate polynomial.ipynb ├── tensor pcs.ipynb ├── multilinear to univariate polynomial adaptor.ipynb └── fri-based pcs.ipynb ├── Babybear └── Babybear.md ├── ReedSolomonCode └── rscode.md ├── BarycentricInterpolation └── barycentric_interpolation.ipynb └── BrakeDown └── SimpleBrakedown.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | */.ipynb_checkpoints/* -------------------------------------------------------------------------------- /fft/FFT-roots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/plonky3-python-notebook/HEAD/fft/FFT-roots.png -------------------------------------------------------------------------------- /merkle-tree/mt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/plonky3-python-notebook/HEAD/merkle-tree/mt.png -------------------------------------------------------------------------------- /fft/bowers & dif fft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/plonky3-python-notebook/HEAD/fft/bowers & dif fft.png -------------------------------------------------------------------------------- /Mersenne31/circlestark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/plonky3-python-notebook/HEAD/Mersenne31/circlestark.png -------------------------------------------------------------------------------- /fft/workflow of 4-step_FFT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/plonky3-python-notebook/HEAD/fft/workflow of 4-step_FFT.png -------------------------------------------------------------------------------- /fft/coefficients to evaluations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/plonky3-python-notebook/HEAD/fft/coefficients to evaluations.png -------------------------------------------------------------------------------- /fft/evaluations to coefficients.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coset-io/plonky3-python-notebook/HEAD/fft/evaluations to coefficients.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Antalpha Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plonky3 Python Notebook 2 | 3 | The Plonky3 Python Notebook is an educational resource designed to demystify the inner workings of the Plonky3 proof system. By leveraging interactive Python notebooks, this project provides a hands-on approach to understanding the technical principles behind Plonky3, making advanced zero-knowledge (ZK) technology more accessible to developers and enthusiasts. 4 | 5 | ## Overview 6 | 7 | [Plonky3](https://github.com/Plonky3/Plonky3) is a modular toolkit developed by Polygon for building succinct proof systems, such as PLONK and STARKs. It enables developers to create customized zkVMs and zkEVMs tailored to specific use cases, optimizing for speed, proof size, and other performance metrics. 8 | 9 | The Plonky3 Python Notebook aims to: 10 | 11 | - **Educate**: Provide comprehensive explanations of the core concepts and components of the Plonky3 proof system. 12 | 13 | - **Demonstrate**: Offer interactive code examples that illustrate how Plonky3 operates under the hood. 14 | 15 | - **Empower**: Equip developers with the knowledge to implement and utilize Plonky3 in their own projects. 16 | 17 | ## Features 18 | 19 | - **Interactive Learning**: Utilizes Jupyter Notebooks to combine explanatory text, live code, and visualizations, facilitating an engaging learning experience. 20 | 21 | - **Comprehensive Coverage**: Covers various aspects of Plonky3, including polynomial commitment schemes, field arithmetic, and proof generation. 22 | 23 | ## Getting Started 24 | 25 | ### Run on Local 26 | 27 | To begin exploring the Plonky3 Python Notebook: 28 | 29 | 1. **Clone the Repository**: 30 | 31 | ```bash 32 | git clone https://github.com/Antalpha-Labs/plonky3-python-notebook.git 33 | ``` 34 | 35 | 2. **Navigate to the Project Directory**: 36 | 37 | ```bash 38 | cd plonky3-python-notebook 39 | ``` 40 | 41 | 3. **Install Dependencies**: 42 | 43 | Ensure you have [Jupyter Notebook](https://jupyter.org/install) installed. You can install it using pip: 44 | 45 | ```bash 46 | pip install notebook 47 | ``` 48 | 49 | 4. **Launch Jupyter Notebook**: 50 | 51 | ```bash 52 | jupyter notebook 53 | ``` 54 | 55 | 5. **Open the Notebook**: 56 | 57 | In the Jupyter interface, open the `plonky3_notebook.ipynb` file to start learning. 58 | 59 | ### Run on Google Colab 60 | 61 | Or you can run the notebook on [Google Colab](https://colab.research.google.com/). 62 | 63 | ## Prerequisites 64 | 65 | A basic understanding of Python programming and familiarity with cryptographic concepts will be beneficial for users of this notebook. 66 | 67 | ## Contributing 68 | 69 | Contributions to enhance the educational content, fix issues, or expand the scope of the notebook are welcome. Please fork the repository, create a new branch for your changes, and submit a pull request for review. 70 | 71 | ## License 72 | 73 | This project is licensed under the MIT License. See the [LICENSE](https://github.com/Antalpha-Labs/plonky3-python-notebook/blob/main/LICENSE) file for details. 74 | 75 | ## Acknowledgments 76 | 77 | This project is supported by [zkBankai's grant program](https://soulforge.zkbankai.com/). 78 | 79 | For more information on Plonky3 and its applications, refer to the [official Polygon Plonky3 repository](https://github.com/Plonky3/Plonky3). 80 | 81 | 82 | ## Feedback 83 | If you have any questions or concerns about this content, please fill out the form below to contact us. [google form](https://forms.gle/3LGv4YfAdopmT53m9) 84 | 85 | 86 | -------------------------------------------------------------------------------- /pcs/multilinear and univariate polynomial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "There is no existing PCS adaptor that transforms univariate polynomials to multivariate ones. \n", 8 | "Instead, we introduce a method from logarithmic derivative that is general to both univariate and multivariate polynomials." 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "### Logarithmic derivative\n", 16 | "The logarithmic derivative of a polynomial $p(X)$ over a (general) field $\\mathbb{F}$ is the rational function\n", 17 | "$$\n", 18 | "\\frac{p'(X)}{p(X)}.\n", 19 | "$$\n", 20 | "\n", 21 | "In particular, the logarithmic derivative of a product $p(X) = \\prod_{i=1}^{n}(X + z_i)$, with each $z_i \\in \\mathbb{F}$, is equal to the sum\n", 22 | "$$\n", 23 | "\\frac{p'(X)}{p(X)} = \\sum_{i=1}^{n} \\frac{1}{X + z_i}.\n", 24 | "$$\n", 25 | "\n", 26 | "#### Permutation argument\n", 27 | "The following lemma is a simple consequence of Lemma 2 and essentially states that, under quite mild conditions on the field $\\mathbb{F}$, if two normalized polynomials have the same logarithmic derivative then they are equal.\n", 28 | "\n", 29 | "Lemma 1. Let $(a_i)_{i=1}^{n}$ and $(b_i)_{i=1}^{n}$ be sequences over a field \\mathbb{F} with characteristic $p > n$. Then $\\prod_{i=1}^{n} (X + a_i) = \\prod_{i=1}^{n} (X + b_i)$ in $\\mathbb{F}[X]$ if and only if\n", 30 | "$$\n", 31 | "\\sum_{i=1}^{n} \\frac{1}{X + a_i} = \\sum_{i=1}^{n} \\frac{1}{X + b_i}\n", 32 | "$$\n", 33 | "in the rational function field $\\mathbb{F}(X)$.\n", 34 | "\n", 35 | "We can observe that, if $(b_i)_{i=1}^{n}$ is a permutation of $(a_i)_{i=1}^{n}$, then the left and right sides of this should be the same.\n", 36 | "This is useful in the permutation arguments.\n", 37 | "\n", 38 | "\n", 39 | "#### Lookup argument\n", 40 | "Lemma 2 [Set inclusion]. Let \\mathbb{F} be a field of characteristic $p > N$, and suppose that $(a_i)_{i=1}^{N}$, $(b_i)_{i=1}^{N}$ are arbitrary sequences of field elements. Then $\\{a_i\\} \\subseteq \\{b_i\\}$ as sets (with multiples of values removed), if and only if there exists a sequence $(m_i)_{i=1}^{N}$ of field elements from $\\mathbb{F}_q \\subseteq \\mathbb{F}$ such that\n", 41 | "$$\n", 42 | "\\sum_{i=1}^{N} \\frac{1}{X + a_i} = \\sum_{i=1}^{N} \\frac{m_i}{X + b_i}\n", 43 | "$$\n", 44 | "in the function field $F(X)$. Moreover, we have equality of the sets $\\{a_i\\} = \\{b_i\\}$, if and only if $m_i \\neq 0$, for every $i = 1, \\dots, N$.\n", 45 | "\n", 46 | "We can think of $\\{b_i\\}$ as a lookup table, and $\\{a_i\\}$ as the elements of the subtable. If some element $b_j$appears in the subtable, then $m_j\\geq 1$, $m_j$denotes the number of occurrences of $b_j$." 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "### Lookups based on the logarithmic derivative\n", 54 | "\n", 55 | "For a lookup table $T $ and a subtable $F$, we interpolation them into multilinear polynomials $t(\\vec{X}), f(\\vec{X})$.\n", 56 | "\n", 57 | "Assume that $\\mathbb{F}$ is a finite field, and that $f$ and $t : H \\to \\mathbb{F}$ are functions over the Boolean hypercube $H = \\{ \\pm 1 \\}^n$. \n", 58 | "By Lemma 2, it holds that \n", 59 | "$$\n", 60 | "\\{ f(\\vec{x}) \\}_{x \\in H} \\subseteq \\{ t(\\vec{x}) \\}_{x \\in H}\n", 61 | "$$\n", 62 | "as sets, if and only if there exists a function $m : H \\to F$ such that\n", 63 | "$$\n", 64 | "\\sum_{\\vec{x} \\in H} \\frac{1}{X + f(\\vec{x})} = \\sum_{\\vec{x} \\in H} \\frac{m(\\vec{x})}{X + t(\\vec{x})},\n", 65 | "$$\n", 66 | "here $t$ is injective (which is typically the case for lookup tables) then $m$ is the multiplicity function, counting the number of occurrences for each value $t(\\vec{x})$ in $f$ altogether, i.e. \n", 67 | "$$\n", 68 | "m(\\vec{x}) = m_f(t(\\vec{x})) = |\\{ \\vec{y} \\in H : f(\\vec{y}) = t(\\vec{x}) \\}|.\n", 69 | "$$\n", 70 | "\n", 71 | "Given a random challenge $\\alpha \\gets \\mathbb{F}$ from the verifier, the prover shows that the rational identity (13) holds at $X = \\alpha$, i.e.\n", 72 | "$$\n", 73 | "\\sum_{\\vec{x} \\in H} \\frac{1}{\\alpha + f(\\vec{x})} - \\frac{m(\\vec{x})}{\\alpha + t(\\vec{x})} = 0. $$" 74 | ] 75 | } 76 | ], 77 | "metadata": { 78 | "language_info": { 79 | "name": "python" 80 | } 81 | }, 82 | "nbformat": 4, 83 | "nbformat_minor": 2 84 | } 85 | -------------------------------------------------------------------------------- /Babybear/Babybear.md: -------------------------------------------------------------------------------- 1 | # BabyBear 2 | 3 | The field’s order is $15 \cdot 2^{27} + 1$ (equivalently, $2^{31} - 2^{27} + 1$ ). 4 | 5 | This choice of field offers two key benefits: 6 | 7 | 1. It allows for 32-bit addition without overflow. 8 | 9 | 2. It maximizes the power of 2 as a factor of P-1 , which is beneficial for certain cryptographic and computational applications. 10 | 11 | ## two-adicity 12 | 13 | Two-adicity is a property of a finite field that describes the size of the largest multiplicative subgroup whose order is a power of 2. Specifically, if a field has a two-adicity of n , it means there exists a subgroup of size 2^n in its multiplicative group 14 | 15 | ```rust 16 | // Plonky3/baby-bear/src/baby_bear.rs 17 | impl TwoAdicData for BabyBearParameters { 18 | // The subgroup whose order is 2 power by 27 19 | const TWO_ADICITY: usize = 27; 20 | 21 | type ArrayLike = &'static [BabyBear]; 22 | // ... 23 | } 24 | ``` 25 | 26 | ### Subgroups 27 | 28 | For any prime field , the multiplicative group (non-zero elements under multiplication modulo $P$) always has an order of $P - 1$. This group is cyclic, meaning there exists a generator $g$ such that every element in the group can be expressed as a power of $g$. 29 | 30 | Example: Subgroups in $F_{13}$ 31 | 32 | 1. Multiplicative Group: 33 | - Elements: $\{1, 2, \cdots, 12\}$ (excluding 0 because it does not have a multiplicative inverse). 34 | - Order: 12. 35 | - Since 12 = 2 * 2 * 3, the multiplicative group  has subgroups of orders 1, 2, 3, 4, 6, and 12. 36 | 2. Subgroups: 37 | - Order 12 (Full Group): 38 | - Generator g = 2, such that: $G=\{2^0,2^1,2^2,...,2^{11}\}\bmod13=\{1,2,4,8,3,6,12,11,9,5,10,7\}.$ 39 |  40 | - Order 4: 41 | - Subgroup: $H=\{1,g^3,g^6,g^9\},$ where g = 2 42 | - Elements: $H=\{1,8,12,5\}.$ 43 | - Generator: $h = g^3 = 8$ 44 | 45 | - Order 3: 46 | - Subgroup: $H=\{1,g^4,g^8\},$ where g = 2 47 | - Elements: $H=\{1, 3, 9\}.$ 48 | - Generator: $h = g^4 = 3$ 49 | 50 | In this case the two-adicity of $F_{13}$ is 2 51 | 52 | ### Importance in FFT/FRI 53 | 54 | Two-adicity is really helpful in FRI because it allows us to efficiently reduce the size of the domain where we evaluate the polynomial. During each step of the FRI protocol, we compute the polynomial on a new domain that is half the size of the previous one. This process relies on having subgroups in the field whose sizes are powers of 2, thanks to its two-adicity. 55 | 56 | The beauty of this is that two-adicity guarantees we have enough smaller subgroups (like $2^n, 2^{n-1}, \dots$ ) to keep halving the domain until it’s small enough to verify easily. This step-by-step reduction is a key part of FRI’s design and makes it efficient to create and verify proofs. 57 | 58 | ## Montgomery Form 59 | 60 | Modular reduction is computationally expensive. [Montgomery multiplication](https://en.wikipedia.org/wiki/Montgomery_modular_multiplication) provides an efficient solution for performing modular arithmetic operations. 61 | 62 | ### Key Insight 63 | While humans find division by powers of 10 (1, 10, 100, ..., 10^n) natural, computers perform more efficiently with powers of 2. Montgomery arithmetic leverages this by transforming calculations into a domain where divisions become simple bit shifts. 64 | 65 | ### The Algorithm 66 | The Montgomery multiplication (Mon) of two numbers A and B is defined as: 67 | 68 | $$ 69 | \text{Mon}(A, B, \text{N}) = \text{Mon}(A, B) = 70 | \begin{cases} 71 | \frac{A \cdot B + (A \cdot B \cdot \text{N'} \bmod R) \cdot \text{N}}{R} - \text{N} & \text{if Result} \geq \text{N} \\ 72 | \frac{A \cdot B + (A \cdot B \cdot \text{N'} \bmod R) \cdot \text{N}}{R} & \text{otherwise} 73 | \end{cases} 74 | $$ 75 | 76 | where: 77 | - R is 2^`MONTY_BITS` 78 | - N' is N^(-1) mod R which is named as `MONTY_MU` in plonky3 79 | 80 | ### Example: Computing (a * b * c) mod Prime 81 | 82 | Let's walk through an example using the BabyBear field: 83 | 84 | 1. Preprocess: 85 | ```rust 86 | // Plonky3/baby-bear/src/baby_bear.rs 87 | impl MontyParameters for BabyBearParameters { 88 | // Our field modulus 89 | const PRIME: u32 = 0x78000001; 90 | // Bit length of Prime 91 | // The power of Montgomery constant(The R in above formular) 92 | const MONTY_BITS: u32 = 32; 93 | // Montgomery multiplier = PRIME^(-1) mod 2^MONTY_BITS 94 | const MONTY_MU: u32 = 0x88000001; 95 | } 96 | ``` 97 | 98 | 2. Convert inputs to Montgomery domain 99 | 100 | A = (a * R) mod Prime 101 | 102 | B = (b * R) mod Prime 103 | 104 | C = (c * R) mod Prime 105 | 106 | 107 | 3. Modular Calculation in Montgomery domain 108 | 109 | intermediate_O = Mon(A, B) 110 | 111 | intermediate_1 = Mon(C, intermediate_O) 112 | 113 | 4. Convert result from Montgomery domain to Babybear field 114 | 115 | result = Mon(intermediate_1, 1) 116 | -------------------------------------------------------------------------------- /ReedSolomonCode/rscode.md: -------------------------------------------------------------------------------- 1 | # RScode 2 | 3 | Before the advent of ZK, RS codes were already widely used in the domain of **Channel Coding and Decoding**. 4 | 5 | To transfer a message, the raw message undergoes a series of coding and decoding stages. The process can be organized as follows: 6 | 7 | Encoding Side (Sender): 8 | 9 | 1. Source Coding: Compresses the raw message to reduce redundancy. 10 | 2. Cryptographic Coding: Encrypts the message to ensure its security. 11 | 3. **Channel Coding**: Adds error-correcting codes to make the message resilient to errors during transmission. 12 | 4. Line Coding: Converts the coded message into a signal suitable for transmission through the channel. 13 | 14 | Channel: 15 | 16 | The message travels through the communication channel, which may introduce noise, distortions, or errors. 17 | 18 | Decoding Side (Reciver): 19 | 20 | 1. Line Decoding: Converts the received signal back into its coded message form. 21 | 2. **Channel Decoding**: Detects and corrects errors introduced by the channel. 22 | 3. Cryptographic Decoding: Decrypts the message to restore its secure content. 23 | 4. Source Decoding: Decompresses the message to reconstruct the original raw message. 24 | 25 | This structured process ensures the message is accurately transmitted through the channel while maintaining security and reliability. 26 | 27 | ## Basic concept 28 | 29 | ### RS-code is a block code. 30 | 31 | For example, given a message of length $x$ to be encoded, the encoding process is performed step by step, processing the message in blocks of size $k$ at each step. 32 | 33 | In other words, the raw message is divided into multiple blocks, each containing $k$ elements of information. Each block is referred to as a message . 34 | 35 | Each message of size $k$ is mapped to a code word consisting of $n$ elements in $F_q$: 36 | 37 | $$ 38 | F_{q}^{k} \rightarrow F_{q}^{n} 39 | $$ 40 | 41 | $k$: The message length, 42 | 43 | $F_{q}^{k}$ The set of all possible messages of length k . 44 | 45 | $n$: The code word length 46 | 47 | Code Space: A q-array code C is a subset of $F_{q}^{n}$ . Since $F_{q}^{n}$ contains many more elements than $F_{q}^{k}$ ($F_{q}^{n} \gg F_{q}^{k}$ ), not all elements of $F_{q}^{n}$ are used as codewords. 48 | 49 | code word: The element in code space 50 | 51 | We also need to mention another two terms: 52 | 53 | 1. Code Rate: $r := k/n$ The ratio of message length to code word length. 54 | 55 | 2. Distance: $d := n - k + 1$ Minimum distance (measured as the number of symbol positions in which two codewords differ, we will explain it later). 56 | 57 | Please keep the above information in mind, as different papers and textbooks may use varying notations at times. 58 | 59 | ### RS-code is a Liner Code 60 | 61 | First, let's recap the Reed-Solomon (RS) coding's process step by step: 62 | 1. Message Representation: 63 | 64 | The message is divided into blocks of size $k$, where each block is treated as a sequence of symbols from a finite field $F_q$. Each block is called a message . 65 | 66 | 2. Polynomial Representation: 67 | 68 | Each message is converted into a polynomial $P(x)$ of degree less than $k$. The coefficients of the polynomial come from the finite field $F_q$. 69 | 70 | 3. Evaluation: 71 | 72 | The polynomial $P(x)$ is evaluated at $n$ distinct points in $F_q$, producing a sequence of $n$ symbols: $c = (P(a_1), P(a_2), \dots, P(a_n))$ . 73 | 74 | These evaluation points $\{a_1, a_2, \dots, a_n\}$ are predefined and fixed. 75 | 76 | 4. Codeword Construction: 77 | 78 | The resulting sequence of $n$ symbols is called a codeword. Each codeword is a representation of the original message , with additional redundancy added. 79 | 80 | 5. Error Correction: 81 | 82 | *You don't need to fully understand when you just start your zk journey, this property is not untilzed in zk for now(2024, Dec, 31th)* 83 | 84 | In the decoding process, the receiver uses the properties of polynomials to correct errors. If fewer than $(n-k)/2$ symbols are corrupted, the original message can be fully recovered. 85 | #### Why do we call it linear code? 86 | 87 | The RS encoding process can be expressed as a matrix multiplication. 88 | 89 | - For a message $m = (m_0, m_1, \dots, m_{k-1})$ in $F_q^k$, the codeword $c = (c_0, c_1, \dots, c_{n-1})$ in $F_q^n$ is obtained by multiplying $m$ with a generator matrix $G$. 90 | 91 | $$ 92 | c = m \cdot G 93 | $$ 94 | 95 | Where: 96 | - $m$ is a $1 \times k$ row vector. 97 | - $G$ is a $k \times n$ generator matrix whose entries are evaluations of a basis of polynomials at the $n$ predefined points in $F_q$. 98 | - $c$ is the resulting $1 \times n$ row vector. 99 | 100 | Matrix multiplication is a linear operation, (we also have [convolution code](https://en.wikipedia.org/wiki/Convolutional_code) but which is outof scope.) 101 | 102 | This matrix is a Vandermonde matrix over $F_P$ ([read more](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction#Constructions_(encoding):~:text=%5D-,This%20matrix%20is%20a%20Vandermonde%20matrix%20over,.,-Systematic%20encoding%20procedure)). 103 | 104 | If the code $F$ is a linear function. That is, $F(a \cdot m_0 + b \cdot m_1) = a \cdot F(m_0) + b \cdot F(m_1)$ for any messages $m_0, m_1 \in F_{q}^{k}$ and scalars $a, b \in F_{q}$. 105 | 106 | ## What is an MDS Code? 107 | 108 | An MDS Code (Maximum Distance Separable Code) is a type of error-correcting code that achieves the maximum possible minimum distance $d_{\text{min}}$ for a given codeword length $n$ and message length $k$. 109 | 110 | Key Properties of MDS Codes 111 | 112 | 1. Maximum Minimum Distance: $d_{\text{min}} = n - k + 1$ 113 | 114 | - $n$: The length of the codeword (number of symbols in the encoded block). 115 | - $k$: The length of the message (number of information symbols in the block). 116 | - $d_{\text{min}}$: The minimum Hamming distance between any two codewords. 117 | 118 | 2. Optimal Error-Correction: 119 | - MDS codes can detect $d_{\text{min}} - 1$ errors. 120 | - They can correct up to $\lfloor (d_{\text{min}} - 1) / 2 \rfloor$ errors. 121 | 122 | 3. Singleton Bound: 123 | 124 | MDS codes achieve the theoretical maximum distance bound: $d_{\text{min}} \leq n - k + 1$ 125 | 126 | 4. Efficient Recovery: 127 | 128 | Even if up to n - k symbols are lost, the original message can still be fully reconstructed. 129 | 130 | As the distance of RScode is also $d_{\text{min}} \leq n - k + 1$, which is a kind of MDS 131 | -------------------------------------------------------------------------------- /pcs/tensor pcs.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Interpolating polynomials\n", 8 | "\n", 9 | "At the beginning we recall R1CS instance is of form $A z \\circ Bz = Cz$ with size $N$.\n", 10 | "We use $X = (\\mathbb{F}, A, B, C, M, N, io)$ to define an R1CS instance, where $A, B, C \\in \\mathbb{F}^{M \\times M}$ are $M \\times M$ over $\\mathbb{F}$ and each matrix has at most $N = \\Omega(M)$ non-zero entries. \n", 11 | "$z = (w, 1, io)$ consists of a witness $w$ and the public input-output $io$.\n", 12 | "\n", 13 | "In Spatan, the matrices $A, B, C$ are interpreted as functions mapping domain $\\{0,1\\}^{\\log M} \\times \\{0,1\\}^{\\log M}$ to $\\mathbb{F}$ by converting binary to decimal representation. \n", 14 | "Take matrix $A = \\{ a_{i,j}\\}_{i,j \\in [N]}$ as an example. \n", 15 | "We write $(i,j)$ in their binary representation as input to the multilinear polynomial $\\tilde{A}$.\n", 16 | "Then the evaluation of $\\tilde{A}$ at point $(i,j)$ is $a_{i,j} = \\tilde{A}(i,j) \\in \\mathbb{F}$, which means the entry in $i$-th row and $j$-th column of matrix $A$ is the field element $a_{i,j}$.\n", 17 | "That is $$\\tilde{A}(r_x,r_y) = \\sum_{i,j\\in \\{0,1\\}^{\\log M \\times \\log M}:a_{i,j}\\neq 0} a_{i,j} \\cdot \\tilde{eq}(r_x,i) \\cdot \\tilde{eq}(r_y,j).$$\n", 18 | "Note that since $N = \\Omega(M)$, we replaced $\\lceil \\log N \\rceil$ with $\\log M$ in the above equation." 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "\n", 26 | "\n", 27 | "### Spatan Polynomial IOP\n", 28 | "\n", 29 | "At the start of the protocol, the prover sends a single $(\\log M-1)$-variate multilinear polynomial $\\tilde{W}$, and\n", 30 | "the verifier has a query access to three additional $2 \\log M$-variate multilinear polynomials $\\tilde{A}, \\tilde{B}$ and $\\tilde{C}$.\n", 31 | "\n", 32 | "the verifier makes a single evaluation query to each of the four polynomials $\\tilde{W}$, $\\tilde{A}, \\tilde{B}$ and $\\tilde{C}$, and otherwise performs $O(\\log M)$ operations over $\\mathbb{F}$; \n", 33 | "\n", 34 | "the prescribed prover performs $O(N)$ operations over $\\mathbb{F}$ to compute its messages over the course of the\n", 35 | "polynomial IOP (and to compute answers to the verifier's four queries to $\\tilde{W}$, $\\tilde{A}, \\tilde{B}$ and $\\tilde{C}$).\n", 36 | "\n", 37 | "The protocol goes as follows:\n", 38 | "1. The prover sends a $(\\log M -1)$-variate multilinear polynomial $\\tilde{W}$ as an oracle.\n", 39 | "2. The verifier chooses a randomness $\\tau \\gets \\mathbb{F}^s$.\n", 40 | "3. Interaction: Both parties run the sumcheck reduction for the R1CS instance:\n", 41 | "$$ \\sum_{x \\in \\{0,1\\}^s} \\tilde{eq}(\\tau,x) \\cdot \\left( \\left( \\sum_{y\\in\\{0,1\\}^s} \\tilde{A}(x,y) \\tilde{Z}(y) \\right) \\left( \\sum_{y\\in\\{0,1\\}^s} \\tilde{B}(x,y) \\tilde{Z}(y) \\right) -\\sum_{y\\in\\{0,1\\}^s}\\tilde{C}(x,y) \\tilde{Z}(y) \\right) = 0.$$\n", 42 | "4. The verifier checks:\n", 43 | " - $\\tilde{A}(r_x,r_y) = v_A$, $\\tilde{B}(r_x,r_y) = v_B$ and $\\tilde{C}(r_x,r_y) = v_C$ reduced by the sumcheck.\n", 44 | " - $\\tilde{Z}(r_y) = v_Z$ by checking $v_Z = (1- r_y[1]) \\cdot v_W + r_y[1] \\cdot (io,1)(r_y[2..])$ where $r_y[2..]$ refers to a slide of $r_y$ without the first element of $r_y$, and $v_W \\gets \\tilde{W}(r_y[2..])$ via an oracle query.\n", 45 | "\n", 46 | "\\textbf{Soundness.} the soundness error for the depicted polynomial IOP is at most $O(\\log M)/ |\\mathbb{F}|$.\n", 47 | "\n", 48 | "\\textbf{Round and communication complexity.} The sumcheck protocol is applied 4 times (although 3 of the\n", 49 | "invocations occur in parallel and in practice combined into one). In each invocation, the polynomial to\n", 50 | "which the sumcheck protocol is applied has degree at most 3 in each variable, and the number of variables\n", 51 | "is $s = \\log M$. Hence, the round complexity of the polynomial IOP is $O(\\log M)$. Since each polynomial has\n", 52 | "degree at most 3 in each variable, the total communication cost is $O(\\log M)$ field elements.\n", 53 | "\n", 54 | "\\textbf{Verifier time.} The verifier's running time is bounded in $O(\\log M)$ field operations.\n", 55 | "\n", 56 | "\\textbf{Prover time.} The prover's running time includes the time to compute $\\tilde{A}(r_x,r_y), \\tilde{B}(r_x,r_y), \\tilde{C}(r_x,r_y)$ and $\\tilde{Z}(r_y)$.\n" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "### Polynomial commitments\n", 64 | "In the polynomial commitment schemes, we aim to commit to a multilinear polynomial $g$ with $n$ coefficients. \n", 65 | "All evaluations of $g$ are represented by vector $u = (u_1, ..., u_m) \\in \\mathbb{F}^m$.\n", 66 | "The polynomial $g$ is committed with Merkle trees using its evaluation vector $u$.\n", 67 | "The queries from the verifier are answered by the prover with Merkle tree authentication path. \n", 68 | "\n", 69 | "The PCS consists of 3 phases: Commit phase, Testing phase and Evaluation phase.\n", 70 | "- Commit phase\n", 71 | " - Prover sends the encoding vector $\\hat{u} = \\{Enc(u_i)\\}_{i \\in [m]}$ of $u$.\n", 72 | " Note: The encoding method will be discussed in the \n", 73 | "- Testing phase\n", 74 | " - Verifier samples a random vector $r \\in \\mathbb{F}^m$.\n", 75 | " - Prover sends a vector $u' \\in \\mathbb{F}^m$ claimed to equal $v = \\sum_{i=1}^m r_i \\cdot u_i \\mathbb{F}^m$.\n", 76 | " - Verifier chooses $Q$ to be a random subset of $[N]$ and the prover is required to open $|Q|$ entries of $\\hat{u}$. \n", 77 | " For each $j \\in Q$:\n", 78 | " - queries all $m$ entries of $\\hat{u}$, denoted by $\\hat{u}_{1,j}, ..., \\hat{u}_{m,j}$.\n", 79 | " - confirms that $Enc(u')_j = \\sum_{i=1}^m r_i \\cdot \\hat{u}_{i,j}$.\n", 80 | "- Evaluation phase\n", 81 | " - Let $q_1, q_2 \\in \\mathbb{F}^m$ be vectors such that $g(r) = \\langle(q_1 \\otimes q_2), z \\rangle$.\n", 82 | " - Verifier sends $q_1$.\n", 83 | " - Prover send a vector $u''$ claimed to equal $v' = \\sum_{i=1}^m q_{1,i} \\cdot u_i \\mathbb{F}^m$. \n", 84 | " - Verifier choose a random set $Q'$ to confirm $Enc(u'')$.\n", 85 | " - If all consistency tests pass, then the verifier outputs $\\langle u', q_2 \\rangle$ as $g(r)$." 86 | ] 87 | } 88 | ], 89 | "metadata": { 90 | "language_info": { 91 | "name": "python" 92 | } 93 | }, 94 | "nbformat": 4, 95 | "nbformat_minor": 2 96 | } 97 | -------------------------------------------------------------------------------- /pcs/multilinear to univariate polynomial adaptor.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "In this document, we introduce adaptors for univariate polynomials to multivariate polynomials.\n", 8 | "Our goal is to express multivariate polynomials in terms of univariate polynomials, and then apply the polynomial commitment scheme for univariate polynomials to multivariate polynomial commitments.\n", 9 | "Below we introduce the transformation idea of zeromorph." 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "### Univariate Polynomials and Multilinear Polynomials\n", 17 | "\n", 18 | "1. A univariate polynomial $f$ is defined over $\\mathbb{F}[X]$ with degree $d$.\n", 19 | "2. An MLE (Multilinear Extension) polynomial $\\tilde{f}$ is a class of Multivariate polynomials defined on the Boolean HyperCube $\\{0,1\\}^m$ over $\\mathbb{F}[X_0, ..., X_{m-1}]$. \n", 20 | "We call $m$ the dimension of the polynomial $\\tilde{f}$.\n", 21 | "The degree of any variable in each term does not exceed 1, ie, $deg(\\tilde{f}) \\leq 1$. " 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "### Uni Map to Multi\n", 29 | "In order to map a multivariate polynomial $\\tilde{f}$ to a univariate polynomial $f$, we need to care about the degree of the polynomial.\n", 30 | "we want to map $\\tilde{f}$ over $\\mathbb{F}[X_0, ..., X_{m-1}]$ to $f$ with degree $d = 2^m-1$.\n", 31 | "We use $U_m$ to denote this homomorphic map.\n", 32 | "$U_m$ maps $\\tilde{f}$ to $f$ in this way: the evaluation of $\\tilde{f}$ at the point $\\tilde{i}$ is mapped to the $i$-th the coefficient of univariate polynomials on $\\mathbb{F}$, where $\\tilde{i}$ is the binary representation(lower bits first) of $i$.\n", 33 | "As described in zeromorph, $U_m$ is an additional homomorphism.\n", 34 | "\n", 35 | "Take a $2$-dimension multipolynomial $\\tilde{f}(X_0, X_1) = 1+X_1+ X_0X_1$ as an example. \n", 36 | "All the evaluations of $\\tilde{f}$ are:\n", 37 | "$$\\tilde{f}(0,0) = 1 \\\\\n", 38 | "\\tilde{f}(1,0) = 1 \\\\\n", 39 | "\\tilde{f}(0,1) = 2 \\\\\n", 40 | "\\tilde{f}(1,1) = 3.$$ \n", 41 | "Then we can obtain $U_m(\\tilde{f}) = f(X) = 1 + 1\\cdot X + 2 \\cdot X^2 + 3 \\cdot X^3$." 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "\n", 49 | "#### Special Cases\n", 50 | "For a constant multivariate polynomial\n", 51 | "$\\tilde{f}(X_0,..., X_m) = c$, it maps to $U_m(\\tilde{f}) = f(X) = c(1+X+...+X^{d})$, where $d = 2^m-1$, $c \\in \\mathbb{F}$.\n", 52 | "We can see that every coefficient of $f$ is $c$, because $\\tilde{f}$ evaluates to $c$ at every point.\n", 53 | "Denote $\\Phi_m(X) = 1+X+...+X^{d}$, we have $U_m(c) = c \\cdot \\Phi_m(X)$.\n", 54 | "\n", 55 | "The second special case is mapping the polynomial $\\tilde{p}$ in $k variables, where $0 \\leq k < m$, to a univariate polynomial of degree $d=2^m-1$. \n", 56 | "Namely, we want to see what $U_m(\\tilde{p})$ looks like.\n", 57 | "For a $k$-variate polynomial $\\tilde{p}$, where $0\\leq k = if let Some(diff_invs) = diff_invs {\n", 125 | " g.zip(diff_invs)\n", 126 | " .map(|(sg, &diff_inv)| diff_inv * sg)\n", 127 | " .collect()\n", 128 | " } else {\n", 129 | " let subgroup = g.collect::>();\n", 130 | " let diffs: Vec = subgroup\n", 131 | " .par_iter()\n", 132 | " .map(|&subgroup_i| point - subgroup_i * shift)\n", 133 | " .collect();\n", 134 | " let diff_invs = batch_multiplicative_inverse(&diffs);\n", 135 | " subgroup\n", 136 | " .par_iter()\n", 137 | " .zip(diff_invs)\\\\\n", 138 | " .map(|(&sg, diff_inv)| diff_inv * sg)\n", 139 | " .collect()\n", 140 | " };\n", 141 | "```" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "## Under the two adic field\n", 149 | "\n", 150 | "In many applications of barycentric evaluation, we can choose the evaluation points. One very convenient choice is the set $\\{1,\\omega,\\omega^2\\ldots\\omega^{N-1}\\}$ $N$ is a power of two. The main advantage of this choice is that it removes the need to pre-compute the $w_j$ values, because there is a simple closed-form value for them.\n", 151 | "\n", 152 | "For a 2-adic subgroup we have following feature:\n", 153 | "$$\n", 154 | "z_H(X)=\\prod_{i=0}^{N-1}(X-\\omega^i)=X^N-1\n", 155 | "$$\n", 156 | "\n", 157 | "\"Proof\": \n", 158 | "\n", 159 | "For $F_{13}$ we have $F_{13}=(0,1,2,3,4,5,6,7,8,9,10,11,12)$\n", 160 | "\n", 161 | "The the generator of the multiplicative group is $g=2$, as $13-1 = 2 * 2 * 3$, we have a smaller group of order 4, which generator is 5\n", 162 | "\n", 163 | "$H=(\\omega^0=1,\\omega^1=5,\\omega^2=12,\\omega^3=8)$\n", 164 | "\n", 165 | "Suppose N = 4\n", 166 | "\n", 167 | "$$\n", 168 | "\\begin{aligned}&(X-\\omega^0)(X-\\omega^1)(X-\\omega^2)(X-\\omega^3)\\\\&=\\quad(X-1)(X-\\omega)(X+1)(X-\\omega^3)\\\\&=\\quad(X^2-1)(X-\\omega)(X+\\omega)\\\\&=\\quad(X^2-1)(X^2-\\omega^2)\\\\&=\\quad(X^2-1)(X^2+1)\\\\&=\\quad(X^4-1)\\end{aligned}\n", 169 | "$$\n", 170 | "\n", 171 | "You should calculate above process by yourself (Do not forget to compute mod 13)\n", 172 | "\n", 173 | "And we can find $w_j=\\frac1{\\prod_{i=0,i\\neq j}^k(x_j-x_i)}$ has a similar structure. we can apply above optimization on $w_j$, finally we will get \n", 174 | "\n", 175 | "$$\n", 176 | "w_i=\\frac N{\\omega^i}\n", 177 | "$$\n", 178 | "\n", 179 | "You can read this blog for more information https://hackmd.io/@vbuterin/barycentric_evaluation\n" 180 | ] 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "metadata": {}, 185 | "source": [ 186 | "## REF\n", 187 | "\n", 188 | "https://hackmd.io/@vbuterin/barycentric_evaluation\n", 189 | "\n", 190 | "https://github.com/sec-bit/learning-zkp/blob/master/plonk-intro-zh/2-plonk-lagrange-basis.md\n", 191 | "\n", 192 | "https://epubs.siam.org/doi/epdf/10.1137/S0036144502417715" 193 | ] 194 | } 195 | ], 196 | "metadata": { 197 | "language_info": { 198 | "name": "python" 199 | } 200 | }, 201 | "nbformat": 4, 202 | "nbformat_minor": 2 203 | } 204 | -------------------------------------------------------------------------------- /merkle-tree/merkle-tree.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 1. Recap: Understanding Traditional Merkle Trees\n", 8 | "## What is a Merkle Tree?\n", 9 | "A Merkle Tree is a data structure used to verify the integrity of data. It's a type of binary tree where each leaf node is a hash of a data block, and each non-leaf node is a hash of its child nodes. This structure allows for efficient and secure verification of data integrity.\n", 10 | "## Why Use Merkle Trees?\n", 11 | "Merkle Trees are widely used in blockchain technology, distributed systems, and version control systems. They allow you to verify that a piece of data belongs to a larger dataset without needing to access the entire dataset.\n", 12 | "## How Does a Merkle Tree Work?\n", 13 | "- Leaves: Each leaf node contains a hash of a data block.\n", 14 | "- Nodes: Each non-leaf node contains a hash of the concatenation of its child nodes.\n", 15 | "- Root: The root node is a single hash that represents the entire dataset.\n", 16 | "\n", 17 | "Example:\n", 18 | "Imagine you have four data blocks: A, B, C, and D. The Merkle Tree would look like this:\n", 19 | "```\n", 20 | " Root\n", 21 | " / \\\n", 22 | " Hash0 Hash1\n", 23 | " / \\ / \\\n", 24 | " A B C D\n", 25 | "```\n", 26 | "- Hash0 = hash(A|B)\n", 27 | "- Hash1 = hash(C|D)\n", 28 | "- Root = hash(Hash0|Hash1)\n", 29 | "\n", 30 | "1. Build the merkle tree -> compute Hash0, Hash1, Root\n", 31 | "2. Prove leaf node B is in the tree -> compute the merkle proof [A, Hash1]\n", 32 | "3. Verify the merkle proof, ensuring the node B is in the tree. Check hash(hash(A|B) | Hash1) == Root\n" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 12, 38 | "metadata": {}, 39 | "outputs": [ 40 | { 41 | "name": "stdout", 42 | "output_type": "stream", 43 | "text": [ 44 | "Merkle Proof: [('left', '559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd'), ('right', '26b5aabe804fe5d533c663dea833e8078188376ce5ca2b5c3371d09ef6b0657b')]\n", 45 | "Proof is valid: True\n" 46 | ] 47 | } 48 | ], 49 | "source": [ 50 | "import hashlib\n", 51 | "\n", 52 | "\n", 53 | "def hash(data):\n", 54 | " return hashlib.sha256(data.encode()).hexdigest()\n", 55 | "\n", 56 | "\n", 57 | "class MerkleTree:\n", 58 | " def __init__(self, leaves):\n", 59 | " # fixed-size hash outputs as leaves\n", 60 | " self.leaves = [hash(leaf) for leaf in leaves]\n", 61 | " self.tree = [self.leaves]\n", 62 | " self.build_tree(self.leaves)\n", 63 | "\n", 64 | " def build_tree(self, current_level):\n", 65 | " if len(current_level) == 1:\n", 66 | " return\n", 67 | " next_level = []\n", 68 | " for i in range(0, len(current_level), 2):\n", 69 | " left = current_level[i]\n", 70 | " right = current_level[i + 1] if i + 1 < len(current_level) else left\n", 71 | " combined = left + right\n", 72 | " next_level.append(hash(combined))\n", 73 | " self.tree.append(next_level)\n", 74 | " self.build_tree(next_level)\n", 75 | " \n", 76 | " def get_root(self):\n", 77 | " return self.tree[-1][0]\n", 78 | "\n", 79 | " def get_proof(self, leaf_index):\n", 80 | " \"\"\"Generate a Merkle proof for a leaf node\n", 81 | " Returns a list of (position, hash) tuples, where position is 'left' or 'right'\n", 82 | " \"\"\"\n", 83 | " proof = []\n", 84 | " current_index = leaf_index\n", 85 | "\n", 86 | " for level in range(len(self.tree) - 1): # Exclude root level\n", 87 | " current_level = self.tree[level]\n", 88 | " # Find sibling index (if current is even, sibling is right; if odd, sibling is left)\n", 89 | " is_right = current_index % 2\n", 90 | " sibling_index = current_index - 1 if is_right else current_index + 1\n", 91 | "\n", 92 | " # If sibling exists, add it to proof\n", 93 | " if sibling_index < len(current_level):\n", 94 | " position = \"left\" if is_right else \"right\"\n", 95 | " proof.append((position, current_level[sibling_index]))\n", 96 | "\n", 97 | " # Move to parent index in next level\n", 98 | " current_index = current_index // 2\n", 99 | "\n", 100 | " return proof\n", 101 | "\n", 102 | " def verify_proof(self, leaf, proof):\n", 103 | " \"\"\"Verify a Merkle proof\n", 104 | " leaf: The leaf node value to verify\n", 105 | " proof: List of (position, hash) tuples\n", 106 | " \"\"\"\n", 107 | " current_hash = hash(leaf)\n", 108 | "\n", 109 | " for position, sibling_hash in proof:\n", 110 | " if position == \"left\":\n", 111 | " combined = sibling_hash + current_hash\n", 112 | " else:\n", 113 | " combined = current_hash + sibling_hash\n", 114 | " current_hash = hash(combined)\n", 115 | "\n", 116 | " return current_hash == self.get_root()\n", 117 | "\n", 118 | "\n", 119 | "# Example usage:\n", 120 | "leaves = [\"A\", \"B\", \"C\", \"D\"]\n", 121 | "merkle_tree = MerkleTree(leaves)\n", 122 | "\n", 123 | "# Get proof for \"B\" (index 1)\n", 124 | "proof = merkle_tree.get_proof(1)\n", 125 | "print(\"Merkle Proof:\", proof)\n", 126 | "# proof[0] is A\n", 127 | "assert(proof[0] == (\"left\", merkle_tree.tree[0][0]))\n", 128 | "# proof[1] is hash(C|D)\n", 129 | "assert(proof[1] == (\"right\", merkle_tree.tree[1][1]))\n", 130 | "\n", 131 | "# Verify the proof\n", 132 | "is_valid = merkle_tree.verify_proof(\"B\", proof)\n", 133 | "print(\"Proof is valid:\", is_valid)" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "# 2. Generalizing Merkle Trees\n", 141 | "## Why Generalize Merkle Trees?\n", 142 | "Traditional Merkle Trees assume that all data blocks are of the same size and structure. However, in many applications, data can be more complex and varied. Generalized Merkle Trees extend the concept to handle more diverse data structures.\n", 143 | "## What is a Generalized Merkle Tree?\n", 144 | "A Generalized Merkle Tree is an advanced version of a Merkle Tree that can accommodate different types of data, such as matrices with different dimensions.\n", 145 | "\n", 146 | "## Example\n", 147 | "\n", 148 | "### Matrices with same dimensions\n", 149 | "4 matrices with same dimensions, 8 rows, 1 column.\n", 150 | "- Node l0 is the hash of the first row of each matrix.\n", 151 | "- Node l1 is the hash of the second row of each matrix.\n", 152 | "- ...\n", 153 | "- Node l7 is the hash of the last row of each matrix.\n", 154 | "\n", 155 | "![same dimensions](https://hackmd.io/_uploads/BkGW-fFYT.png)\n", 156 | "\n", 157 | "### Matrices with different dimensions\n", 158 | "4 matrics:\n", 159 | "- 4x3, 4x2, 4x3 -> leaves\n", 160 | "- 2x2 -> upper layer\n", 161 | "\n", 162 | "![different dimensions](./mt.png)" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "# 3. Understanding MMCS (Mixed Matrix Commitment Scheme)\n", 170 | "MMCS is a generalization of a vector commitment scheme.\n", 171 | "It supports committing to matrices and then opening rows. It is also batch-oriented; one can commit to a batch of matrices at once even if their widths and heights differ.\n", 172 | "\n", 173 | "## How MMCS Works:\n", 174 | "MMCS built upon Generalized Merkle Trees. Since Generalized Merkle Trees can handle different types of data, MMCS can handle matrices with different dimensions.\n", 175 | "\n", 176 | "1. commit: build a Generalized Merkle Tree from the matrices.\n", 177 | "2. open_batch: prove a node is in the tree <=> prove a row is in the matrix.\n", 178 | "3. verify_batch: verify a node is in the tree <=> verify the row is in the matrix.\n", 179 | "\n", 180 | "## Why MMCS?\n", 181 | "In STARKs, it requires to commit to multiple polynomials with different degrees: \n", 182 | "- Execution Trace. Multiple columns, each column is a polynomial.\n", 183 | "- Quotient Composition Polynomial.\n", 184 | "- Polynomials in FRI folding. Fold once, one new polynomial.\n", 185 | "\n", 186 | "- Without MMCS, one polynomial use one merkle tree. \n", 187 | "- With MMCS, we can commit all polynomials in one Generalized Merkle Tree.\n", 188 | "\n", 189 | "Reference: https://hackmd.io/@0xKanekiKen/H1ww-qWKa" 190 | ] 191 | } 192 | ], 193 | "metadata": { 194 | "kernelspec": { 195 | "display_name": "venv", 196 | "language": "python", 197 | "name": "python3" 198 | }, 199 | "language_info": { 200 | "codemirror_mode": { 201 | "name": "ipython", 202 | "version": 3 203 | }, 204 | "file_extension": ".py", 205 | "mimetype": "text/x-python", 206 | "name": "python", 207 | "nbconvert_exporter": "python", 208 | "pygments_lexer": "ipython3", 209 | "version": "3.12.4" 210 | } 211 | }, 212 | "nbformat": 4, 213 | "nbformat_minor": 2 214 | } 215 | -------------------------------------------------------------------------------- /pcs/fri-based pcs.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "FRI-based PCS\n", 8 | "\n", 9 | "## How FRI works\n", 10 | "A FRI protocol is a protocol for proving that a function $f : H \\to \\mathbb{F}$ is closed to a polynomial of low degree $d$, where $d \\ll |H|$. \n", 11 | "The FRI PCS outputs the root of the Merkle tree as the commitment to the function $f$ while the FRI protocol consists of the evaluation proof.\n", 12 | "The protocol can be divided into two phases: commit and query.\n", 13 | "In the commit phash, the prover commits to (via Merkle trees) a series of functions generated from $f$ and random elements $v_0, v_1, ... $ from $\\mathbb{K}$ provided by the verifier at each round. \n", 14 | "Then in the query phase, the prover provides a set of evaluations of the previously committed functions at a point randomly chosen by the verifier." 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "### Commit Phase\n", 22 | "Denote by $p_0$ the function of low degree. \n", 23 | "In the commit phase, the polynomial $p_0$ is split into two polynomials $g_{0,1}, g_{0,2} : H^2 \\to \\mathbb{K}$ of degree lower than $d/2$ such that:\n", 24 | "$$p_0(X) = g_{0,1}(X^2) + X g_{0,2}(X^2).$$\n", 25 | "\n", 26 | "Then, the verifier sends a uniform randomness $v_0 \\in \\mathbb{K}$ and ask the prover to commit to the polynomial: $p_1(X) = g_{0,1}(X) + v_0 g_{0,2}(X)$.\n", 27 | "Note that $p_1$ is a polynomial of degree less than $d/2$ and the commitment of $p_1$ is not over $H$ but over $H^2 = \\{x^2 : x \\in H \\}$.\n", 28 | "\n", 29 | "The prover then continues by splitting $p_1$ into $g_{1,1}$ and $g_{1,2}$ of degree lower than $d/4$, and constructs $p_2$ with a uniformly sampled $v_1 \\in \\mathbf{K}$ sent by the verifier.\n", 30 | "\n", 31 | "This above interaction iterates $k = \\log d$ times.\n", 32 | "At last, $deg(p_k) = 0$ and the prover sends a constant $p_k$, representing a polynomial of degree lower than 1, to the verifier. " 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "### Query Phase\n", 40 | "In the query phase, the verifier sends a randomness $r \\in H$ to the prover and queries the evaluations $p_0(r), p_0(-r)$ and $p_1(r_2)$.\n", 41 | "From $p_0(r), p_0(-r)$ the verifier computes $p_1(r^2)$ and checks that the computed value matches $p_1(r^2)$ sent by the prover.\n", 42 | "In detail, $p_1(r^2)$ can be obtained as follows:\n", 43 | "- $p_0(r) = g_{0,1}(r^2) + r\\cdot g_{0,2}(r^2)$;\n", 44 | "- $p_0(-r) = g_{0,1}(r^2) - r\\cdot g_{0,2}(r^2)$;\n", 45 | "- $p_1(r^2) = g_{0,1}(r^2) + v_0\\cdot g_{0,2}(r^2)$.\n", 46 | "\n", 47 | "In the next interaction, the verifier queries $p_1(-r^2)$ and $p_2(r^4)$ and checks the consistency between $p_1, p_2$ as before.\n", 48 | "The interaction repeats till the constant $p_k$. \n", 49 | "The verifier checks that the value sent by the prover is indeed equal to the value that the verifier computed from the queries up until $p_{k-1}$.\n", 50 | "To fully ensure correctness, the prover must accompany the evaluations that she sends with a claim of their existence (via Merkle tree paths).\n", 51 | "Upon the completion of this process, the verifier has a first confirmation that the polynomials committed in the commit phase $p_0, p_1, . . . , p_k$ are consistent with each other.\n", 52 | "\n", 53 | "Finally, to achieve the required bounds for the soundness of the protocol, the query phase is repeated multiple times." 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "### A Python example\n", 61 | "At the beginning, we define two classes PolynomialOperations and MerkleTree. \n", 62 | "The former is to spliting the polynomial into two sub-polynomial and evaluate a polynomial at a given point.\n", 63 | "The latter generates a Merkle tree root as a commitment." 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "metadata": { 70 | "vscode": { 71 | "languageId": "plaintext" 72 | } 73 | }, 74 | "outputs": [], 75 | "source": [ 76 | "class PolynomialOperations:\n", 77 | " @staticmethod\n", 78 | " def split_polynomial(coeffs):\n", 79 | " g0 = coeffs[::2] # Even coefficients\n", 80 | " g1 = coeffs[1::2] # Odd coefficients\n", 81 | " return g0, g1\n", 82 | "\n", 83 | " @staticmethod\n", 84 | " def evaluate_polynomial(coeffs, x):\n", 85 | " return sum(c * x**i for i, c in enumerate(coeffs))\n", 86 | "\n", 87 | "class MerkleTree:\n", 88 | " @staticmethod\n", 89 | " def commit(data):\n", 90 | " leaves = [sha256(bytes(str(d), 'utf-8')).digest() for d in data]\n", 91 | " while len(leaves) > 1:\n", 92 | " leaves = [sha256(leaves[i] + leaves[i + 1]).digest() for i in range(0, len(leaves), 2)]\n", 93 | " return leaves[0]" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "metadata": {}, 99 | "source": [ 100 | "Now, we are prepared to define the protocol." 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": { 107 | "vscode": { 108 | "languageId": "plaintext" 109 | } 110 | }, 111 | "outputs": [], 112 | "source": [ 113 | " import numpy as np\n", 114 | "from hashlib import sha256\n", 115 | "\n", 116 | "class FRI:\n", 117 | " def __init__(self, domain, degree, randomness_source):\n", 118 | " self.domain = np.array(domain)\n", 119 | " self.degree = degree\n", 120 | " self.randomness_source = randomness_source\n", 121 | " self.commitments = []\n", 122 | " self.queries = []\n", 123 | " self.proofs = []\n", 124 | "\n", 125 | " def commit_phase(self, poly_coeffs):\n", 126 | " current_poly = poly_coeffs\n", 127 | " for _ in range(int(np.log2(self.degree))):\n", 128 | " # Split polynomial into g0 and g1\n", 129 | " g0, g1 = PolynomialOperations.split_polynomial(current_poly)\n", 130 | "\n", 131 | " # Verifier sends randomness\n", 132 | " v = self.randomness_source()\n", 133 | "\n", 134 | " # Compute next polynomial p_i\n", 135 | " current_poly = g0 + v * g1\n", 136 | "\n", 137 | " # Commit to p_i\n", 138 | " commitment = MerkleTree.commit(current_poly)\n", 139 | " self.commitments.append(commitment)\n", 140 | "\n", 141 | " # Final degree-0 polynomial\n", 142 | " self.proofs.append(current_poly[0])\n", 143 | "\n", 144 | " def query_phase(self, verifier_randomness):\n", 145 | " for r in verifier_randomness:\n", 146 | " # Query p0(r) and p0(-r)\n", 147 | " p0_r = PolynomialOperations.evaluate_polynomial(self.commitments[0], r)\n", 148 | " p0_minus_r = PolynomialOperations.evaluate_polynomial(self.commitments[0], -r)\n", 149 | "\n", 150 | " # Compute p1(r^2)\n", 151 | " r_squared = r**2\n", 152 | " g0_r2 = (p0_r + p0_minus_r) / 2\n", 153 | " g1_r2 = (p0_r - p0_minus_r) / (2 * r)\n", 154 | " p1_r2 = g0_r2 + self.randomness_source() * g1_r2\n", 155 | "\n", 156 | " # Check consistency\n", 157 | " self.queries.append((r, p0_r, p0_minus_r, p1_r2))\n", 158 | "\n", 159 | " def verify(self):\n", 160 | " for r, p0_r, p0_minus_r, p1_r2 in self.queries:\n", 161 | " # Compute g0(r^2) and g1(r^2) from p0\n", 162 | " g0_r2 = (p0_r + p0_minus_r) / 2\n", 163 | " g1_r2 = (p0_r - p0_minus_r) / (2 * r)\n", 164 | "\n", 165 | " # Compute expected p1(r^2)\n", 166 | " v = self.randomness_source()\n", 167 | " expected_p1_r2 = g0_r2 + v * g1_r2\n", 168 | "\n", 169 | " # Check consistency\n", 170 | " if not np.isclose(p1_r2, expected_p1_r2):\n", 171 | " return False\n", 172 | " return True" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "metadata": {}, 178 | "source": [ 179 | "Below, we take an example function $f = 1+2x +3x^2+4x^3$ to show how FRI works." 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": null, 185 | "metadata": { 186 | "vscode": { 187 | "languageId": "plaintext" 188 | } 189 | }, 190 | "outputs": [], 191 | "source": [ 192 | "# Example usage\n", 193 | "def random_element():\n", 194 | " return np.random.rand()\n", 195 | "\n", 196 | "domain = [1, -1, 2, -2, 4, -4, 8, -8]\n", 197 | "degree = 4\n", 198 | "poly_coeffs = [1, 2, 3, 4]\n", 199 | "fri = FRI(domain, degree, random_element)\n", 200 | "\n", 201 | "# Commit phase\n", 202 | "fri.commit_phase(poly_coeffs)\n", 203 | "\n", 204 | "# Query phase\n", 205 | "verifier_randomness = [1, 2]\n", 206 | "fri.query_phase(verifier_randomness)\n", 207 | "\n", 208 | "# Verify\n", 209 | "assert fri.verify(), \"Verification failed!\"" 210 | ] 211 | } 212 | ], 213 | "metadata": { 214 | "language_info": { 215 | "name": "python" 216 | } 217 | }, 218 | "nbformat": 4, 219 | "nbformat_minor": 2 220 | } 221 | -------------------------------------------------------------------------------- /fft/radix_2_bowers.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Radix_2_bowers FFT\n", 8 | "Bowers FFT is an optimized version of the classical DIT (Decimation-In-Time) and DIF (Decimation-In-Frequency) FFT algorithms. The primary goal of Bowers FFT is to enhance computational efficiency by reducing the number of Twiddle factor accesses and optimizing memory access patterns. In modern computing architectures, memory access efficiency often becomes a bottleneck. Bowers FFT addresses this issue by significantly lowering Twiddle factor overhead and improving hardware vectorization utilization.\n", 9 | "\n", 10 | "## 1. Dit and Dif FFT\n", 11 | "In Dit FFT,the input data is in bit-reversed order, while the output is in natural order.It start with an 2-DFT, and then 4-DFT...until N-DFT. In Dif FFT, the input data is in natural order, while the output is in bit-reversed order.It start with an N-DFT, and then N/2-DFT...until 2-DFT. In both DIT and DIF FFT, the butterfly operations are performed using an iterative method instead of a recursive approach
\n", 12 | "In this part, only the code for Dif FFT will be presented, but in the section on the Four-Step FFT, both Dit and Dif FFT (including forward and inverse transforms) will be demonstrated.
\n", 13 | "\n", 14 | "## 2. Bowers FFT\n", 15 | "Bowers FFT is very similar to DIF FFT, with the core difference lying in the way twiddle factors are accessed. Taking an 8-point FFT as an example, in DIF FFT, the twiddle factors for the different stage are follows:
\n", 16 | "first Layer: $$w_{8}^{0} ,w_{8}^{1}, w_{8}^{2}, w_{8}^{3}$$\n", 17 | "second Layer: $$w_{8}^{0} ,w_{8}^{2}, w_{8}^{0}, w_{8}^{2}$$ \n", 18 | "third Layer: $$w_{8}^{0} ,w_{8}^{0}, w_{8}^{0}, w_{8}^{0}$$ \n", 19 | "While in Bower FFT. the the twiddle factors for the different stage are follows:
\n", 20 | "first Layer: $$w_{8}^{0} ,w_{8}^{0}, w_{8}^{0}, w_{8}^{0}$$\n", 21 | "second Layer: $$w_{8}^{0} ,w_{8}^{0}, w_{8}^{2}, w_{8}^{2}$$ \n", 22 | "third Layer: $$w_{8}^{0} ,w_{8}^{1}, w_{8}^{2}, w_{8}^{3}$$ \n", 23 | "We focus on the second layer: In Bowers FFT, memory access will be more contiguous. The difference is shown by the following figure (a is dif fft, b is bowers_g_t fft):\n", 24 | "\"Example\n", 25 | "\n", 26 | "## 3. Code implementation of bowers FFT\n", 27 | "In plonky3, two computing networks, Bowers G and Bower G^T, are implemented, which are inverse fft algorithms with inverse transformations to each other. The Bowers G network is similar to DFT, with the only difference being that the inputs are bit-reverse, so that the outputs are directly in natural order. (Note that the twiddle factor also uses bit-reverse). In the bowers g^t network, it is similar to the inverse DFT. We use natural sequential inputs, and the outputs need to be bit-reversed.\n", 28 | "\n", 29 | "### 3.1 implementation of Dif and Bowers FFT\n", 30 | "You can use the following code with a simple 8-point data [1, 2, 3, 4, 5, 6, 7, 8] as input to run the DIF FFT and its inverse transform, so that it can be used for comparative observation later.\n", 31 | "Notice that bowers_g is for fft, and bower_g_t is for inverse-fft." 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 2, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "import numpy as np\n", 41 | "class Field:\n", 42 | " # basic operations of finite domains\n", 43 | " def __init__(self, modulus):\n", 44 | " self.modulus = modulus\n", 45 | "\n", 46 | " def add(self, x, y):\n", 47 | " return (x + y) % self.modulus\n", 48 | "\n", 49 | " def sub(self, x, y):\n", 50 | " return (x - y) % self.modulus\n", 51 | "\n", 52 | " def mul(self, x, y):\n", 53 | " return (x * y) % self.modulus\n", 54 | "\n", 55 | " def pow(self, x, exp):\n", 56 | " return pow(x, exp, self.modulus)\n", 57 | "\n", 58 | " def inv(self, x):\n", 59 | " return pow(x, self.modulus - 2, self.modulus)\n", 60 | "\n", 61 | " def roots_of_unity(self, n):\n", 62 | " root = self.pow(11, (self.modulus - 1) // n) # suppose 11 is the primitive root\n", 63 | " return [self.pow(root, i) for i in range(n)]\n", 64 | " \n", 65 | "\n", 66 | "class FTT:\n", 67 | " # generate forward and inverse roots, bit-reverse, dit and dif FFT, forward and inverse dit or dif.\n", 68 | " def __init__(self, modulus, n):\n", 69 | " self.gf = Field(modulus)\n", 70 | " self.n = n\n", 71 | " \n", 72 | " def get_forward_roots(self,n):\n", 73 | " return self.gf.roots_of_unity(n)\n", 74 | " \n", 75 | " def get_inverse_roots(self,n):\n", 76 | " forward_roots=self.gf.roots_of_unity(n) \n", 77 | " return [self.gf.inv(r) for r in forward_roots]\n", 78 | "\n", 79 | " def bit_reversed_indices(self, n):\n", 80 | " logn = n.bit_length() - 1\n", 81 | " return [int(f\"{i:0{logn}b}\"[::-1], 2) for i in range(n)]\n", 82 | "\n", 83 | " def bit_reverse(self, a):\n", 84 | " n = len(a)\n", 85 | " indices = self.bit_reversed_indices(n)\n", 86 | " return [a[i] for i in indices]\n", 87 | "\n", 88 | " def dif(self, a, roots):\n", 89 | " n = len(a)\n", 90 | " logn = n.bit_length() - 1\n", 91 | " for s in range(logn, 0, -1):\n", 92 | " m = 1 << s\n", 93 | " wm = roots[n//m]\n", 94 | " for k in range(0, n, m):\n", 95 | " w = 1\n", 96 | " for j in range(m // 2):\n", 97 | " u = a[k + j]\n", 98 | " v = a[k + j + m // 2]\n", 99 | " a[k + j] = self.gf.add(u, v)\n", 100 | " a[k + j + m // 2] = self.gf.mul(w, self.gf.sub(u, v))\n", 101 | " w = self.gf.mul(w, wm)\n", 102 | " return self.bit_reverse(a)\n", 103 | "\n", 104 | " def forward_dif(self, a):\n", 105 | " roots=self.get_forward_roots(len(a))\n", 106 | " return self.dif(a,roots)\n", 107 | "\n", 108 | " def inverse_dif(self, a):\n", 109 | " inverse_roots=self.get_inverse_roots(len(a))\n", 110 | " a = self.dif(a, inverse_roots)\n", 111 | " n_inv = self.gf.inv(len(a))\n", 112 | " return [self.gf.mul(x, n_inv) for x in a]\n", 113 | " \n", 114 | " def bower_g(self,a):\n", 115 | " n = len(a)\n", 116 | " a = self.bit_reverse(a)\n", 117 | " roots=self.get_forward_roots(n)\n", 118 | " roots=self.bit_reverse(roots[:len(roots) // 2])\n", 119 | " logn = n.bit_length() - 1\n", 120 | " for s in range(1, logn + 1):\n", 121 | " m = 1 << s\n", 122 | " for k in range(0, n, m):\n", 123 | " w = roots[k//m]\n", 124 | " for j in range(m // 2):\n", 125 | " u = a[k + j]\n", 126 | " v = a[k + j + m // 2]\n", 127 | " a[k + j] = self.gf.add(u, v)\n", 128 | " a[k + j + m // 2] = self.gf.mul(w, self.gf.sub(u, v))\n", 129 | " return a\n", 130 | " \n", 131 | " def bower_g_t(self, a):\n", 132 | " n = len(a)\n", 133 | " roots=self.get_inverse_roots(n)\n", 134 | " roots=self.bit_reverse(roots[:len(roots) // 2])\n", 135 | " logn = n.bit_length() - 1\n", 136 | " for s in range(logn, 0, -1):\n", 137 | " m = 1 << s\n", 138 | " for k in range(0, n, m):\n", 139 | " w = roots[k//m]\n", 140 | " for j in range(m // 2):\n", 141 | " u = a[k + j]\n", 142 | " v = self.gf.mul(w, a[k + j + m // 2])\n", 143 | " a[k + j] = self.gf.add(u, v)\n", 144 | " a[k + j + m // 2] = self.gf.sub(u, v)\n", 145 | " n_inv = self.gf.inv(len(a))\n", 146 | " a=self.bit_reverse(a)\n", 147 | " return [self.gf.mul(x, n_inv) for x in a]\n" 148 | ] 149 | }, 150 | { 151 | "cell_type": "markdown", 152 | "metadata": {}, 153 | "source": [ 154 | "## 3.2 Test for Dif and Bowers FFT\n" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 3, 160 | "metadata": {}, 161 | "outputs": [ 162 | { 163 | "name": "stdout", 164 | "output_type": "stream", 165 | "text": [ 166 | "forward_roots: [1, 2, 4, 8, 16, 15, 13, 9]\n", 167 | "inverse_roots: [1, 9, 13, 15, 16, 8, 4, 2]\n", 168 | "result of forward_result_dif is: [2, 8, 14, 6, 13, 3, 12, 1]\n", 169 | "result of inverse_result_dif is: [13, 15, 10, 11, 8, 5, 6, 1]\n", 170 | "inverse back result is: [1, 2, 3, 4, 5, 6, 7, 8]\n", 171 | "Dif FFT tests passed!\n", 172 | "bowers_g_result is: [2, 8, 14, 6, 13, 3, 12, 1]\n", 173 | "bowers_g_t_result is: [13, 15, 10, 11, 8, 5, 6, 1]\n", 174 | "Bowers FFT tests passed!\n" 175 | ] 176 | } 177 | ], 178 | "source": [ 179 | "\n", 180 | "def test_fft():\n", 181 | " # 1.set the input and other params:\n", 182 | " modulus = 17 \n", 183 | " input_array = [1,2,3,4,5,6,7,8] \n", 184 | " n = len(input_array) \n", 185 | " ntt = FTT(modulus, n)\n", 186 | " forward_roots=ntt.get_forward_roots(n)\n", 187 | " print(\"forward_roots:\", forward_roots)\n", 188 | " inverse_roots=ntt.get_inverse_roots(n)\n", 189 | " print(\"inverse_roots:\", inverse_roots)\n", 190 | "\n", 191 | " # 2. test for Dif fft\n", 192 | " forward_result_dif = ntt.forward_dif(input_array[:]) \n", 193 | " print(\"result of forward_result_dif is:\", forward_result_dif)\n", 194 | " # test for inverse fft\n", 195 | " inverse_result_dif = ntt.inverse_dif(input_array[:]) \n", 196 | " print(\"result of inverse_result_dif is:\", inverse_result_dif)\n", 197 | " # test if it can be restored to the original input\n", 198 | " result_back=ntt.inverse_dif(forward_result_dif)\n", 199 | " print(\"inverse back result is:\", result_back) \n", 200 | " assert result_back == input_array,\"Dif FTT test Failed!\"\n", 201 | " print(\"Dif FFT tests passed!\")\n", 202 | "\n", 203 | " # 2. test for Bowers fft\n", 204 | " forward_result_dif = ntt.forward_dif(input_array[:]) \n", 205 | " bowers_g_result= ntt.bower_g(input_array[:]) \n", 206 | " print(\"bowers_g_result is:\", bowers_g_result)\n", 207 | " assert forward_result_dif==bowers_g_result,\"forward_result_dif is not equal to bowers_g_result!\"\n", 208 | "\n", 209 | " bowers_g_t_result=ntt.bower_g_t(input_array[:])\n", 210 | " print(\"bowers_g_t_result is:\", bowers_g_t_result)\n", 211 | " assert inverse_result_dif==bowers_g_t_result,\"inverse_result_dit is not equal to bowers_g_t_result!\"\n", 212 | " print(\"Bowers FFT tests passed!\")\n", 213 | "\n", 214 | "if __name__ == \"__main__\":\n", 215 | " test_fft()" 216 | ] 217 | } 218 | ], 219 | "metadata": { 220 | "kernelspec": { 221 | "display_name": "Python 3", 222 | "language": "python", 223 | "name": "python3" 224 | }, 225 | "language_info": { 226 | "codemirror_mode": { 227 | "name": "ipython", 228 | "version": 3 229 | }, 230 | "file_extension": ".py", 231 | "mimetype": "text/x-python", 232 | "name": "python", 233 | "nbconvert_exporter": "python", 234 | "pygments_lexer": "ipython3", 235 | "version": "3.11.5" 236 | } 237 | }, 238 | "nbformat": 4, 239 | "nbformat_minor": 2 240 | } 241 | -------------------------------------------------------------------------------- /Mersenne31/cfft.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "id": "ebda5254-2956-42f9-ab76-4627e17dff73", 5 | "cell_type": "markdown", 6 | "source": [ 7 | "## Mersenne Circle Group FFT\n", 8 | "\n", 9 | "It demonstrates how to compute the Fast Fourier Transform (FFT) on a Mersenne circle group using Rader’s algorithm. In this context, we focus on sequences whose length is a Mersenne prime (of the form $2^p - 1$). For demonstration purposes, we use $N = 7$ (since $7 = 2^3 - 1$) as our prime length." 10 | ], 11 | "metadata": {}, 12 | "attachments": {} 13 | }, 14 | { 15 | "id": "00e290f0-d5ec-4049-b9b8-6b4fe9274245", 16 | "cell_type": "markdown", 17 | "source": [ 18 | "### Introduction\n", 19 | "\n", 20 | "In many applications, such as polynomial commitment schemes and cryptographic proof systems, it is necessary to convert between the evaluation representation and the coefficient representation of a polynomial. When the evaluation domain is a circle (often chosen as roots of unity), the Fast Fourier Transform (FFT) offers an efficient solution. In this notebook, we define a circle domain, perform interpolation (recovering polynomial coefficients from evaluations), and show how to evaluate the polynomial at arbitrary points. We also include an example of extrapolation (low-degree extension)." 21 | ], 22 | "metadata": {}, 23 | "attachments": {} 24 | }, 25 | { 26 | "id": "face2ec0-11f7-4350-8f07-57e447791264", 27 | "cell_type": "markdown", 28 | "source": [ 29 | "### Defining the Circle Domain\n", 30 | "\n", 31 | "We define a `CircleDomain` class that represents a set of points on the complex unit circle. For simplicity, the domain size is chosen as a power of two." 32 | ], 33 | "metadata": {}, 34 | "attachments": {} 35 | }, 36 | { 37 | "id": "3a6caac2-e566-4742-a4dd-45ecb98d6179", 38 | "cell_type": "code", 39 | "execution_count": 2, 40 | "outputs": [], 41 | "source": [ 42 | "import numpy as np\n", 43 | "\n", 44 | "class CircleDomain:\n", 45 | " def __init__(self, log_n, shift=0):\n", 46 | " \"\"\"\n", 47 | " Initialize the circle domain.\n", 48 | " \n", 49 | " Args:\n", 50 | " log_n: logarithm (base 2) of the domain size.\n", 51 | " shift: a phase shift applied to all points.\n", 52 | " \"\"\"\n", 53 | " self.log_n = log_n\n", 54 | " self.n = 1 << log_n # Domain size: 2^log_n\n", 55 | " self.shift = shift\n", 56 | " # Generate points on the unit circle with an optional shift.\n", 57 | " self.points = np.array([np.exp(2j * np.pi * (k + shift) / self.n) for k in range(self.n)])\n", 58 | " \n", 59 | " def __iter__(self):\n", 60 | " return iter(self.points)\n", 61 | " \n", 62 | " def size(self):\n", 63 | " return self.n" 64 | ], 65 | "metadata": {} 66 | }, 67 | { 68 | "id": "7e520a48-202b-4eda-a0e8-99935dccc6fe", 69 | "cell_type": "markdown", 70 | "source": [ 71 | "### Circle Evaluations and Interpolation\n", 72 | "\n", 73 | "The `CircleEvaluations` class encapsulates evaluation values (typically obtained by an FFT) over a circle domain. It provides methods to interpolate (recover polynomial coefficients), evaluate the polynomial at an arbitrary point, and extrapolate to a larger domain." 74 | ], 75 | "metadata": {}, 76 | "attachments": {} 77 | }, 78 | { 79 | "id": "2372e12c-f6d3-4fe3-8601-abb85d444edc", 80 | "cell_type": "code", 81 | "execution_count": 3, 82 | "outputs": [], 83 | "source": [ 84 | "class CircleEvaluations:\n", 85 | " def __init__(self, domain, values):\n", 86 | " \"\"\"\n", 87 | " Initialize with a circle domain and corresponding evaluation values.\n", 88 | " \n", 89 | " Args:\n", 90 | " domain: an instance of CircleDomain.\n", 91 | " values: a list or numpy array of evaluation values.\n", 92 | " \"\"\"\n", 93 | " self.domain = domain\n", 94 | " self.values = np.array(values)\n", 95 | " assert self.values.shape[0] == self.domain.size(), \"Mismatch between domain size and number of values.\"\n", 96 | " \n", 97 | " def interpolate(self):\n", 98 | " \"\"\"\n", 99 | " Interpolate the polynomial coefficients from the evaluation values using the inverse FFT.\n", 100 | " \n", 101 | " Returns:\n", 102 | " The recovered polynomial coefficients as a numpy array.\n", 103 | " \"\"\"\n", 104 | " coeffs = np.fft.ifft(self.values)\n", 105 | " return coeffs\n", 106 | " \n", 107 | " def evaluate_at_point(self, point):\n", 108 | " \"\"\"\n", 109 | " Evaluate the interpolated polynomial at an arbitrary point.\n", 110 | " \n", 111 | " This is achieved by computing the dot product of the polynomial coefficients\n", 112 | " with the monomial basis evaluated at the point.\n", 113 | " \n", 114 | " Args:\n", 115 | " point: the point at which to evaluate the polynomial.\n", 116 | " \n", 117 | " Returns:\n", 118 | " The polynomial evaluation at the given point.\n", 119 | " \"\"\"\n", 120 | " coeffs = self.interpolate()\n", 121 | " n = len(coeffs)\n", 122 | " powers = np.array([point**i for i in range(n)])\n", 123 | " return np.dot(coeffs, powers)\n", 124 | " \n", 125 | " def extrapolate(self, target_log_n):\n", 126 | " \"\"\"\n", 127 | " Extrapolate (compute a low-degree extension) by zero-padding the coefficients to a larger domain.\n", 128 | " \n", 129 | " Args:\n", 130 | " target_log_n: logarithm (base 2) of the target domain size (must be larger than the current domain).\n", 131 | " \n", 132 | " Returns:\n", 133 | " A new CircleEvaluations instance with the extrapolated evaluations.\n", 134 | " \"\"\"\n", 135 | " coeffs = self.interpolate()\n", 136 | " current_n = len(coeffs)\n", 137 | " target_n = 1 << target_log_n\n", 138 | " if target_n < current_n:\n", 139 | " raise ValueError(\"Target domain must be larger than current domain.\")\n", 140 | " # Zero-pad the coefficients.\n", 141 | " padded_coeffs = np.concatenate([coeffs, np.zeros(target_n - current_n, dtype=complex)])\n", 142 | " # Compute the FFT on the padded coefficients to obtain the new evaluation values.\n", 143 | " new_values = np.fft.fft(padded_coeffs)\n", 144 | " new_domain = CircleDomain(target_log_n, shift=self.domain.shift)\n", 145 | " return CircleEvaluations(new_domain, new_values)" 146 | ], 147 | "metadata": {} 148 | }, 149 | { 150 | "id": "92cfa744-99d7-4ad8-8756-40e523b314e9", 151 | "cell_type": "markdown", 152 | "source": [ 153 | "We generate a random polynomial, compute its evaluations on a circle domain via FFT, interpolate to recover the coefficients, and verify the evaluation at an arbitrary point." 154 | ], 155 | "metadata": {}, 156 | "attachments": {} 157 | }, 158 | { 159 | "id": "37ab8d72-8113-4319-85eb-ee33316ceae9", 160 | "cell_type": "code", 161 | "execution_count": 4, 162 | "outputs": [ 163 | { 164 | "output_type": "stream", 165 | "name": "stdout", 166 | "text": [ 167 | "Original Coefficients:\n[0.37454012+0.60111501j 0.95071431+0.70807258j 0.73199394+0.02058449j\n 0.59865848+0.96990985j 0.15601864+0.83244264j 0.15599452+0.21233911j\n 0.05808361+0.18182497j 0.86617615+0.18340451j]\n\nRecovered Coefficients (via interpolation):\n[0.37454012+0.60111501j 0.95071431+0.70807258j 0.73199394+0.02058449j\n 0.59865848+0.96990985j 0.15601864+0.83244264j 0.15599452+0.21233911j\n 0.05808361+0.18182497j 0.86617615+0.18340451j]\n\nEvaluation at point (1.2+0.5j):\nUsing evaluate_at_point: (-9.093525188073993+6.003573307765665j)\nDirect evaluation: (-9.093525188073993+6.003573307765664j)\n" 168 | ] 169 | } 170 | ], 171 | "source": [ 172 | "# Set up a circle domain with log_n = 3 (n = 8)\n", 173 | "log_n = 3\n", 174 | "domain = CircleDomain(log_n)\n", 175 | "\n", 176 | "# Generate a random polynomial of degree n-1.\n", 177 | "np.random.seed(42)\n", 178 | "coeffs = np.random.random(1 << log_n) + 1j * np.random.random(1 << log_n)\n", 179 | "\n", 180 | "# Compute evaluations using FFT.\n", 181 | "values = np.fft.fft(coeffs)\n", 182 | "\n", 183 | "# Create a CircleEvaluations instance.\n", 184 | "circle_evals = CircleEvaluations(domain, values)\n", 185 | "\n", 186 | "# Interpolate to recover the coefficients.\n", 187 | "recovered_coeffs = circle_evals.interpolate()\n", 188 | "print(\"Original Coefficients:\")\n", 189 | "print(coeffs)\n", 190 | "print(\"\\nRecovered Coefficients (via interpolation):\")\n", 191 | "print(recovered_coeffs)\n", 192 | "\n", 193 | "# Evaluate the polynomial at an arbitrary point (e.g., 1.2 + 0.5j).\n", 194 | "point = 1.2 + 0.5j\n", 195 | "eval_at_point = circle_evals.evaluate_at_point(point)\n", 196 | "# Direct evaluation using the recovered coefficients.\n", 197 | "direct_eval = sum(c * (point**i) for i, c in enumerate(recovered_coeffs))\n", 198 | "print(\"\\nEvaluation at point {}:\".format(point))\n", 199 | "print(\"Using evaluate_at_point:\", eval_at_point)\n", 200 | "print(\"Direct evaluation:\", direct_eval)" 201 | ], 202 | "metadata": {} 203 | }, 204 | { 205 | "id": "46d3c838-f7a3-4d9e-8353-a3412777c0f4", 206 | "cell_type": "markdown", 207 | "source": [ 208 | "We demonstrate how to extrapolate to a larger domain. This is analogous to computing a low-degree extension (LDE) of the polynomial." 209 | ], 210 | "metadata": {}, 211 | "attachments": {} 212 | }, 213 | { 214 | "id": "4718fe98-2791-44c4-9a8d-1137a3e3b7f6", 215 | "cell_type": "code", 216 | "execution_count": 5, 217 | "outputs": [ 218 | { 219 | "output_type": "stream", 220 | "name": "stdout", 221 | "text": [ 222 | "\nExtrapolated Coefficients (zero-padded):\n[ 3.74540119e-01+6.01115012e-01j 9.50714306e-01+7.08072578e-01j\n 7.31993942e-01+2.05844943e-02j 5.98658484e-01+9.69909852e-01j\n 1.56018640e-01+8.32442641e-01j 1.55994520e-01+2.12339111e-01j\n 5.80836122e-02+1.81824967e-01j 8.66176146e-01+1.83404510e-01j\n 2.77555756e-17-5.55111512e-17j 0.00000000e+00+0.00000000e+00j\n -5.55111512e-17-1.38777878e-17j -5.55111512e-17+5.55111512e-17j\n 0.00000000e+00+0.00000000e+00j -1.38777878e-16-6.93889390e-17j\n -4.85722573e-17+6.93889390e-17j 0.00000000e+00+1.38777878e-17j]\n" 223 | ] 224 | } 225 | ], 226 | "source": [ 227 | "# Extrapolate to a larger domain (e.g., log_n = 4, so n = 16).\n", 228 | "target_log_n = 4\n", 229 | "extrapolated_evals = circle_evals.extrapolate(target_log_n)\n", 230 | "\n", 231 | "# Interpolate the extrapolated evaluations to obtain new coefficients.\n", 232 | "extrapolated_coeffs = extrapolated_evals.interpolate()\n", 233 | "print(\"\\nExtrapolated Coefficients (zero-padded):\")\n", 234 | "print(extrapolated_coeffs)" 235 | ], 236 | "metadata": {} 237 | }, 238 | { 239 | "id": "0f72f35d-206e-46df-a2a6-c107426816ea", 240 | "cell_type": "markdown", 241 | "source": [ 242 | "### Conclusion\n", 243 | "\n", 244 | "In this notebook, we demonstrated a simplified version of FFT-based interpolation and extrapolation on a circle domain. Inspired by a Rust implementation that uses advanced techniques (such as parallel processing and specialized butterfly operations), our Python version shows how to:\n", 245 | "- Define a circle domain (representing evaluation points on the unit circle),\n", 246 | "- Recover polynomial coefficients from evaluation values using the inverse FFT,\n", 247 | "- Evaluate the polynomial at arbitrary points using a monomial basis,\n", 248 | "- Extend the domain via zero-padding (extrapolation).\n", 249 | "\n", 250 | "This method forms a foundation for many advanced algorithms in polynomial commitment schemes and cryptographic protocols." 251 | ], 252 | "metadata": {}, 253 | "attachments": {} 254 | }, 255 | { 256 | "id": "0c21f273-06ad-4f1e-9435-3d9e41244e2a", 257 | "cell_type": "code", 258 | "execution_count": null, 259 | "outputs": [], 260 | "source": [ 261 | "" 262 | ], 263 | "metadata": {} 264 | } 265 | ], 266 | "metadata": { 267 | "language_info": { 268 | "name": "python" 269 | } 270 | }, 271 | "nbformat": 4, 272 | "nbformat_minor": 5 273 | } -------------------------------------------------------------------------------- /Mersenne31/circlestark.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "id": "0", 5 | "cell_type": "markdown", 6 | "source": [ 7 | "## Circle STARK: FRI Prover and Verifier\n", 8 | "\n", 9 | "This notebook demonstrates a simplified FRI (Fast Reed-Solomon Interactive Oracle Proofs of Proximity) proof system—one of the core components in Circle STARK protocols. \n", 10 | "- Commit Phase: Iteratively “fold” a vector of polynomial evaluations using random challenge values.\n", 11 | "- Query Phase: Generate query proofs by extracting opening pairs from each folding round.\n", 12 | "- Verification: Reconstruct the folded value from the query proofs and check that it matches the final constant.\n", 13 | "\n", 14 | "The simulation uses simplified arithmetic and randomness to illustrate the main ideas behind the FRI prover and verifier." 15 | ], 16 | "metadata": {}, 17 | "attachments": {} 18 | }, 19 | { 20 | "id": "4d2c35bb-c6db-4634-a12b-0d83f1e85da7", 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "outputs": [], 24 | "source": [ 25 | "import numpy as np\n", 26 | "import random\n", 27 | "\n", 28 | "def fold_evals(evals, beta):\n", 29 | " \"\"\"\n", 30 | " Fold the evaluation vector by combining adjacent pairs with the challenge beta.\n", 31 | " \n", 32 | " Args:\n", 33 | " evals (np.array): The current evaluation vector (must have even length).\n", 34 | " beta (float): A randomly sampled challenge.\n", 35 | " \n", 36 | " Returns:\n", 37 | " np.array: A new evaluation vector of half the length.\n", 38 | " \"\"\"\n", 39 | " new_evals = []\n", 40 | " for i in range(0, len(evals), 2):\n", 41 | " new_val = evals[i] + beta * evals[i+1]\n", 42 | " new_evals.append(new_val)\n", 43 | " return np.array(new_evals)" 44 | ], 45 | "metadata": {} 46 | }, 47 | { 48 | "id": "db4f87f9-4e1a-4545-bae6-9dc919dbf2c1", 49 | "cell_type": "markdown", 50 | "source": [ 51 | "### Commit Phase (FRI Prover)\n", 52 | "\n", 53 | "In the commit phase, the prover repeatedly folds the evaluation vector until its length reaches a predefined “blowup” factor (here, we use 1 for simplicity). Each folding round uses a fresh challenge value." 54 | ], 55 | "metadata": {}, 56 | "attachments": {} 57 | }, 58 | { 59 | "id": "fbd8b313-35ff-4aa4-844a-60c3225c5743", 60 | "cell_type": "code", 61 | "execution_count": 2, 62 | "outputs": [], 63 | "source": [ 64 | "def commit_phase(evals, blowup=1):\n", 65 | " \"\"\"\n", 66 | " Simulate the commit phase of FRI by iteratively folding the evaluation vector.\n", 67 | " \n", 68 | " Args:\n", 69 | " evals (np.array): The initial evaluation vector.\n", 70 | " blowup (int): The target length (e.g. 1).\n", 71 | " \n", 72 | " Returns:\n", 73 | " (list, final_poly): A tuple where the first element is a list of round data \n", 74 | " (each round containing the evaluation vector and beta)\n", 75 | " and final_poly is the folded constant.\n", 76 | " \"\"\"\n", 77 | " rounds = []\n", 78 | " current = evals\n", 79 | " while len(current) > blowup:\n", 80 | " beta = random.uniform(0.1, 2.0) # simulate a random challenge\n", 81 | " round_data = {'evals': current, 'beta': beta}\n", 82 | " rounds.append(round_data)\n", 83 | " current = fold_evals(current, beta)\n", 84 | " final_poly = current[0]\n", 85 | " return rounds, final_poly" 86 | ], 87 | "metadata": {} 88 | }, 89 | { 90 | "id": "7d9da810-0232-4e62-8d19-6335bae73788", 91 | "cell_type": "markdown", 92 | "source": [ 93 | "### Query Phase\n", 94 | "\n", 95 | "For each query, the prover extracts “openings” from each commit round. In this simplified version, we assume that queries target even indices so that the corresponding pair in each round is well defined." 96 | ], 97 | "metadata": {}, 98 | "attachments": {} 99 | }, 100 | { 101 | "id": "2aab669b-3ef9-422e-98d9-52c35d66ce87", 102 | "cell_type": "code", 103 | "execution_count": 3, 104 | "outputs": [], 105 | "source": [ 106 | "def answer_query(commit_rounds, query_index):\n", 107 | " \"\"\"\n", 108 | " For a given (even) query index, extract the corresponding pair of values \n", 109 | " (v0, v1) from each commit round.\n", 110 | " \n", 111 | " Args:\n", 112 | " commit_rounds (list): List of commit round data from the commit phase.\n", 113 | " query_index (int): The chosen query index (assumed even).\n", 114 | " \n", 115 | " Returns:\n", 116 | " list: A list of tuples (v0, v1) for each round.\n", 117 | " \"\"\"\n", 118 | " proof = []\n", 119 | " current_index = query_index\n", 120 | " for round_data in commit_rounds:\n", 121 | " evals = round_data['evals']\n", 122 | " # Ensure current_index is even and in range\n", 123 | " q = current_index - (current_index % 2)\n", 124 | " if q+1 >= len(evals):\n", 125 | " q = len(evals) - 2 # adjust if out-of-bound\n", 126 | " v0 = evals[q]\n", 127 | " v1 = evals[q+1]\n", 128 | " proof.append((v0, v1))\n", 129 | " # For the next round, simulate the index update (integer division by 2)\n", 130 | " current_index //= 2\n", 131 | " return proof" 132 | ], 133 | "metadata": {} 134 | }, 135 | { 136 | "id": "55767ddd-af01-4098-8c9f-3e17fba6ab65", 137 | "cell_type": "markdown", 138 | "source": [ 139 | "### Prover Function\n", 140 | "\n", 141 | "The prove function combines the commit phase and query generation. It returns a proof object containing:\n", 142 | "- The commit rounds (with challenges and intermediate evaluation vectors)\n", 143 | "- The final folded value (`final_poly`)\n", 144 | "- A list of query proofs (each including the query index and opening pairs)\n", 145 | "- A dummy proof-of-work witness" 146 | ], 147 | "metadata": {}, 148 | "attachments": {} 149 | }, 150 | { 151 | "id": "19029495-73e3-49a6-939f-3b4640ff6132", 152 | "cell_type": "code", 153 | "execution_count": 4, 154 | "outputs": [], 155 | "source": [ 156 | "def prove(evals, num_queries=2, blowup=1):\n", 157 | " \"\"\"\n", 158 | " Simulate the FRI prover by executing the commit phase and generating query proofs.\n", 159 | " \n", 160 | " Args:\n", 161 | " evals (np.array): The initial evaluation vector.\n", 162 | " num_queries (int): Number of queries to generate.\n", 163 | " blowup (int): Target length for the commit phase (e.g., 1).\n", 164 | " \n", 165 | " Returns:\n", 166 | " dict: A proof object containing commit rounds, final_poly, query proofs, and a PoW witness.\n", 167 | " \"\"\"\n", 168 | " commit_rounds, final_poly = commit_phase(evals, blowup=blowup)\n", 169 | " query_proofs = []\n", 170 | " max_index = len(commit_rounds[0]['evals'])\n", 171 | " # For simplicity, choose random even indices within the range.\n", 172 | " for _ in range(num_queries):\n", 173 | " query_index = random.randrange(0, max_index, 2)\n", 174 | " proof_steps = answer_query(commit_rounds, query_index)\n", 175 | " query_proofs.append({'query_index': query_index, 'proof_steps': proof_steps})\n", 176 | " proof = {\n", 177 | " 'commit_rounds': commit_rounds,\n", 178 | " 'final_poly': final_poly,\n", 179 | " 'query_proofs': query_proofs,\n", 180 | " 'pow_witness': random.getrandbits(64) # Dummy proof-of-work witness\n", 181 | " }\n", 182 | " return proof" 183 | ], 184 | "metadata": {} 185 | }, 186 | { 187 | "id": "8b76441a-3d94-45e1-8e64-81e449260e0e", 188 | "cell_type": "markdown", 189 | "source": [ 190 | "### Verification Functions\n", 191 | "\n", 192 | "The verifier uses the query proofs and commit rounds to re-fold the corresponding values and checks that the final result matches the claimed `final_poly`." 193 | ], 194 | "metadata": {}, 195 | "attachments": {} 196 | }, 197 | { 198 | "id": "daa24482-42e9-4bff-b52b-d1e18e41598d", 199 | "cell_type": "code", 200 | "execution_count": 5, 201 | "outputs": [], 202 | "source": [ 203 | "def verify_query(commit_rounds, query_proof, query_index, final_poly):\n", 204 | " \"\"\"\n", 205 | " Verify a single query proof by re-folding the opened pairs.\n", 206 | " \n", 207 | " Args:\n", 208 | " commit_rounds (list): The commit rounds data.\n", 209 | " query_proof (list): The list of opening pairs for this query.\n", 210 | " query_index (int): The original query index.\n", 211 | " final_poly (complex): The claimed final folded value.\n", 212 | " \n", 213 | " Returns:\n", 214 | " bool: True if the recomputed value matches final_poly, False otherwise.\n", 215 | " \"\"\"\n", 216 | " computed = None\n", 217 | " current_index = query_index\n", 218 | " for round_data, (v0, v1) in zip(commit_rounds, query_proof):\n", 219 | " beta = round_data['beta']\n", 220 | " folded = v0 + beta * v1\n", 221 | " computed = folded # Update computed value for the round\n", 222 | " current_index //= 2 # Simulate index update\n", 223 | " return np.isclose(computed, final_poly)\n", 224 | "\n", 225 | "def verify(proof, num_queries=2, blowup=1):\n", 226 | " \"\"\"\n", 227 | " Simulate the FRI verifier by checking all query proofs.\n", 228 | " \n", 229 | " Args:\n", 230 | " proof (dict): The proof object produced by the prover.\n", 231 | " num_queries (int): Number of queries expected.\n", 232 | " blowup (int): Target length for the commit phase.\n", 233 | " \n", 234 | " Returns:\n", 235 | " bool: True if all query proofs verify, False otherwise.\n", 236 | " \"\"\"\n", 237 | " commit_rounds = proof['commit_rounds']\n", 238 | " final_poly = proof['final_poly']\n", 239 | " for qp in proof['query_proofs']:\n", 240 | " query_index = qp['query_index']\n", 241 | " proof_steps = qp['proof_steps']\n", 242 | " if not verify_query(commit_rounds, proof_steps, query_index, final_poly):\n", 243 | " return False\n", 244 | " # Dummy PoW check (always passes in this simulation)\n", 245 | " return True" 246 | ], 247 | "metadata": {} 248 | }, 249 | { 250 | "id": "0fd7c13b-98a3-47c8-965a-e0634a0927e2", 251 | "cell_type": "markdown", 252 | "source": [ 253 | "### Testing the Proof System\n", 254 | "\n", 255 | "The following cell generates a random evaluation vector, runs the prover to produce a proof, and then verifies that proof." 256 | ], 257 | "metadata": {}, 258 | "attachments": {} 259 | }, 260 | { 261 | "id": "564bbfca-ac05-42fa-86c0-7e3c8c650475", 262 | "cell_type": "code", 263 | "execution_count": 6, 264 | "outputs": [ 265 | { 266 | "output_type": "stream", 267 | "name": "stdout", 268 | "text": [ 269 | "Initial Evaluation Vector:\n[0.37454012+0.30424224j 0.95071431+0.52475643j 0.73199394+0.43194502j\n 0.59865848+0.29122914j 0.15601864+0.61185289j 0.15599452+0.13949386j\n 0.05808361+0.29214465j 0.86617615+0.36636184j 0.60111501+0.45606998j\n 0.70807258+0.78517596j 0.02058449+0.19967378j 0.96990985+0.51423444j\n 0.83244264+0.59241457j 0.21233911+0.04645041j 0.18182497+0.60754485j\n 0.18340451+0.17052412j]\n\nGenerated Proof:\n{'commit_rounds': [{'evals': array([0.37454012+0.30424224j, 0.95071431+0.52475643j,\n 0.73199394+0.43194502j, 0.59865848+0.29122914j,\n 0.15601864+0.61185289j, 0.15599452+0.13949386j,\n 0.05808361+0.29214465j, 0.86617615+0.36636184j,\n 0.60111501+0.45606998j, 0.70807258+0.78517596j,\n 0.02058449+0.19967378j, 0.96990985+0.51423444j,\n 0.83244264+0.59241457j, 0.21233911+0.04645041j,\n 0.18182497+0.60754485j, 0.18340451+0.17052412j]), 'beta': 1.698999847555588}, {'evals': array([1.98980358+1.19580334j, 1.74911462+0.92674328j,\n 0.42105331+0.84885294j, 1.52971675+0.91459336j,\n 1.80413021+1.79008382j, 1.66846119+1.07335801j,\n 1.19320676+0.67133381j, 0.4934292 +0.89726531j]), 'beta': 0.17180607573430273}, {'evals': array([2.2903121 +1.35502347j, 0.68386794+1.00598564j,\n 2.09078198+1.97449325j, 1.27798089+0.82548945j]), 'beta': 0.6088997450624349}, {'evals': array([2.70671911+1.96756787j, 2.86894422+2.47713356j]), 'beta': 0.36129120962240335}], 'final_poly': (3.743243440360101+2.862534448229133j), 'query_proofs': [{'query_index': 8, 'proof_steps': [((0.6011150117432088+0.45606998421703593j), (0.7080725777960455+0.7851759613930136j)), ((1.8041302134769825+1.7900838229280784j), (1.6684611852616937+1.0733580146309194j)), ((2.0907819822317975+1.974493251279779j), (1.2779808922334537+0.8254894451481576j)), ((2.706719112160226+1.9675678665017404j), (2.8689442217074106+2.477133563982223j))]}, {'query_index': 6, 'proof_steps': [((0.05808361216819946+0.29214464853521815j), (0.8661761457749352+0.3663618432936917j)), ((0.4210533067131519+0.848852942705139j), (1.529716751796101+0.9145933644413846j)), ((2.2903120985537586+1.3550234670478742j), (0.6838679388242643+1.0059856395424462j)), ((2.706719112160226+1.9675678665017404j), (2.8689442217074106+2.477133563982223j))]}, {'query_index': 4, 'proof_steps': [((0.15601864044243652+0.6118528947223795j), (0.15599452033620265+0.13949386065204183j)), ((0.4210533067131519+0.848852942705139j), (1.529716751796101+0.9145933644413846j)), ((2.2903120985537586+1.3550234670478742j), (0.6838679388242643+1.0059856395424462j)), ((2.706719112160226+1.9675678665017404j), (2.8689442217074106+2.477133563982223j))]}], 'pow_witness': 14100371587337022533}\n\nProof Verification Result: True\n" 270 | ] 271 | } 272 | ], 273 | "source": [ 274 | "# Set a random seed for reproducibility\n", 275 | "np.random.seed(42)\n", 276 | "\n", 277 | "# Generate a random evaluation vector of length 16 (must be a power of 2)\n", 278 | "evals = np.random.random(16) + 1j * np.random.random(16)\n", 279 | "print(\"Initial Evaluation Vector:\")\n", 280 | "print(evals)\n", 281 | "\n", 282 | "# Run the FRI prover to generate a proof (using 3 queries for demonstration)\n", 283 | "proof = prove(evals, num_queries=3, blowup=1)\n", 284 | "print(\"\\nGenerated Proof:\")\n", 285 | "print(proof)\n", 286 | "\n", 287 | "# Verify the generated proof\n", 288 | "is_valid = verify(proof, num_queries=3, blowup=1)\n", 289 | "print(\"\\nProof Verification Result:\", is_valid)" 290 | ], 291 | "metadata": {} 292 | }, 293 | { 294 | "id": "d4e95267-7120-4da4-824d-13e0dff4f824", 295 | "cell_type": "markdown", 296 | "source": [ 297 | "### Conclusion\n", 298 | "\n", 299 | "In this notebook, we simulated a simplified version of the FRI proof system as used in Circle STARK protocols. We demonstrated:\n", 300 | "- Commit Phase: Folding a polynomial evaluation vector using random challenge values.\n", 301 | "- Query Phase: Extracting opening proofs corresponding to specific query indices.\n", 302 | "- Verification: Recomputing folded values to ensure consistency with the final claimed constant.\n", 303 | "\n", 304 | "This simulation—while simplified—captures the core idea of how FRI proofs ensure that a function is close to a low-degree polynomial, a key step in STARK-based proofs." 305 | ], 306 | "metadata": {}, 307 | "attachments": {} 308 | }, 309 | { 310 | "id": "ad50212d-cb0e-4023-90b2-1102dd1e8059", 311 | "cell_type": "code", 312 | "execution_count": null, 313 | "outputs": [], 314 | "source": [ 315 | "" 316 | ], 317 | "metadata": {} 318 | } 319 | ], 320 | "metadata": {}, 321 | "nbformat": 4, 322 | "nbformat_minor": 5 323 | } -------------------------------------------------------------------------------- /fft/four_step.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Four-step FFT\n", 8 | "Four-step FFT(also known as Bailey's FFT) is a high-performance algorithm for computing the fast Fourier transform (FFT). This variation of the Cooley–Tukey FFT algorithm was originally designed for systems with hierarchical memory common in modern computers. The algorithm treats the samples as a two dimensional matrix (thus yet another name, a matrix FFT algorithm) and executes short FFT operations on the columns and rows of the matrix\n", 9 | "\n", 10 | "## 1. How Four-step FFT work\n", 11 | "Here is a brief overview of how the \"4-step\" version of the Bailey FFT algorithm works:
\n", 12 | "First: The data (in natural order) is first arranged into a matrix.
\n", 13 | "Second: Each column of a matrix is then independently processed using a standard FFT algorithm
\n", 14 | "Third: Each element of a matrix is multiplied by a correction coefficient(called twiddle factors)
\n", 15 | "Fourth: Each row of a matrix is then independently processed using a standard FFT algorithm
\n", 16 | "The following pictures show the process of this algorithm:
\n", 17 | "\"Example\n", 18 | "\n", 19 | "## 2. How the Four-Step FFT Enhances Performance and Efficiency \n", 20 | "Take a data of size $2^{10}$ for example, using the Cooley-Tukey FFT algorithm requires $2^{10}*10$ operations. If use Four-step algorithm, we can transfer it to a matrix($2^{5}$ column,$2^{5}$ row), each row and each column run fft algorithm, then should take $2^{5}*2^{5}*5*2=2^{10}*10$ operations. However, we must add to this a further more operations to perform the scaling operation. It seems that the Four-Step FFT requires a bit more computational steps than the conventional FFT. So, what are the advantages of the Four-Step FFT?\n", 21 | "\n", 22 | "Despite having the same computational complexity, Four-Step FFT offers several practical advantages due to its memory access patterns and hardware compatibility:\n", 23 | "* The Four-Step FFT transforms a large-scale FFT into two smaller FFTs (row FFT and column FFT), each operating on only a portion of the matrix at a time. This reduces random memory access compared to conventional FFT, where butterfly operations involve data spread across the entire input\n", 24 | "* Row FFTs and column FFTs are independent and can be computed in parallel. Modern hardware like FPGAs and GPUs can leverage this parallelism to compute multiple FFTs simultaneously\n", 25 | "* Instead of storing the entire FFT input/output, Four-Step FFT processes smaller chunks (e.g., rows or columns of the matrix) that fit into the limited on-chip RAM
\n", 26 | "\n", 27 | "Based on these advantages, the Four-Step FFT offers significant performance improvements on hardware, particularly FPGA or GPU, due to its compatibility with the hardware's architecture and memory hierarchy\n", 28 | "\n", 29 | "## 3. Code implementation of Four-step FFT\n", 30 | "we will use python code to show the process of this algorithm.\n", 31 | "\n", 32 | "### 3.1 Finte Field\n", 33 | "We perform FFT calculations in a finite field (rather than the complex number field), using a primitive root in the finite field (analogous to the roots of unity in the complex field). Therefore, this FFT implementation in the finite field is also known as the Number Theoretic Transform (NTT)" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 1, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "import numpy as np\n", 43 | "\n", 44 | "class Field:\n", 45 | " # basic operations of finite domains\n", 46 | " def __init__(self, modulus):\n", 47 | " self.modulus = modulus\n", 48 | "\n", 49 | " def add(self, x, y):\n", 50 | " return (x + y) % self.modulus\n", 51 | "\n", 52 | " def sub(self, x, y):\n", 53 | " return (x - y) % self.modulus\n", 54 | "\n", 55 | " def mul(self, x, y):\n", 56 | " return (x * y) % self.modulus\n", 57 | "\n", 58 | " def pow(self, x, exp):\n", 59 | " return pow(x, exp, self.modulus)\n", 60 | "\n", 61 | " def inv(self, x):\n", 62 | " return pow(x, self.modulus - 2, self.modulus)\n", 63 | "\n", 64 | " def roots_of_unity(self, n):\n", 65 | " root = self.pow(11, (self.modulus - 1) // n) # suppose 11 is the primitive root\n", 66 | " return [self.pow(root, i) for i in range(n)]" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "## 3.2 Forward and inverse NTT\n", 74 | "The following describes the content of the NTT algorithm, including the forward and inverse computations of DIT and DIF, as well as some methods required for matrix operations.\n", 75 | "These operation modules will be used in four-step FFT." 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 2, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "class NTT:\n", 85 | " # generate forward and inverse roots, bit-reverse, dit and dif FFT, forward and inverse dit or dif.\n", 86 | " def __init__(self, modulus, n):\n", 87 | " self.gf = Field(modulus)\n", 88 | " self.n = n\n", 89 | " \n", 90 | " def get_forward_roots(self,n):\n", 91 | " return self.gf.roots_of_unity(n)\n", 92 | " \n", 93 | " def get_inverse_roots(self,n):\n", 94 | " forward_roots=self.gf.roots_of_unity(n) \n", 95 | " return [self.gf.inv(r) for r in forward_roots]\n", 96 | "\n", 97 | " def bit_reversed_indices(self, n):\n", 98 | " logn = n.bit_length() - 1\n", 99 | " return [int(f\"{i:0{logn}b}\"[::-1], 2) for i in range(n)]\n", 100 | "\n", 101 | " def bit_reverse(self, a):\n", 102 | " n = len(a)\n", 103 | " indices = self.bit_reversed_indices(n)\n", 104 | " return [a[i] for i in indices]\n", 105 | "\n", 106 | " def dit(self, a, roots):\n", 107 | " n = len(a)\n", 108 | " a = self.bit_reverse(a)\n", 109 | " logn = n.bit_length() - 1\n", 110 | " for s in range(1, logn + 1):\n", 111 | " m = 1 << s\n", 112 | " wm = roots[n//m]\n", 113 | " for k in range(0, n, m):\n", 114 | " w = 1\n", 115 | " for j in range(m // 2):\n", 116 | " u = a[k + j]\n", 117 | " v = self.gf.mul(w, a[k + j + m // 2])\n", 118 | " a[k + j] = self.gf.add(u, v)\n", 119 | " a[k + j + m // 2] = self.gf.sub(u, v)\n", 120 | " w = self.gf.mul(w, wm)\n", 121 | " return a\n", 122 | "\n", 123 | " def dif(self, a, roots):\n", 124 | " n = len(a)\n", 125 | " logn = n.bit_length() - 1\n", 126 | " for s in range(logn, 0, -1):\n", 127 | " m = 1 << s\n", 128 | " wm = roots[n//m]\n", 129 | " for k in range(0, n, m):\n", 130 | " w = 1\n", 131 | " for j in range(m // 2):\n", 132 | " u = a[k + j]\n", 133 | " v = a[k + j + m // 2]\n", 134 | " a[k + j] = self.gf.add(u, v)\n", 135 | " a[k + j + m // 2] = self.gf.mul(w, self.gf.sub(u, v))\n", 136 | " w = self.gf.mul(w, wm)\n", 137 | " return self.bit_reverse(a)\n", 138 | "\n", 139 | " def forward_dit(self, a):\n", 140 | " roots=self.get_forward_roots(len(a))\n", 141 | " return self.dit(a,roots)\n", 142 | "\n", 143 | " def inverse_dit(self, a):\n", 144 | " inverse_roots=self.get_inverse_roots(len(a))\n", 145 | " a = self.dit(a, inverse_roots)\n", 146 | " n_inv = self.gf.inv(len(a))\n", 147 | " return [self.gf.mul(x, n_inv) for x in a]\n", 148 | "\n", 149 | " def forward_dif(self, a):\n", 150 | " roots=self.get_forward_roots(len(a))\n", 151 | " return self.dif(a, roots)\n", 152 | "\n", 153 | " def inverse_dif(self, a):\n", 154 | " inverse_roots=self.get_inverse_roots(len(a))\n", 155 | " a = self.dif(a, inverse_roots)\n", 156 | " n_inv = self.gf.inv(len(a))\n", 157 | " return [self.gf.mul(x, n_inv) for x in a]\n", 158 | " \n", 159 | " def matrix(self, a, log_rows, log_cols): \n", 160 | " # transfer array into matrix\n", 161 | " rows = 1 << log_rows\n", 162 | " cols = 1 << log_cols\n", 163 | " return np.array(a).reshape((rows, cols))\n", 164 | "\n", 165 | " def transpose_and_flatten(self, matrix):\n", 166 | " # Transpose the matrix, and flatten it\n", 167 | " return [element for row in matrix.T for element in row]\n", 168 | "\n", 169 | " def apply_twiddles(self, wm, matrix):\n", 170 | " # each matrix[i,j] mul wm^(i*j), wm is the root of n-domain\n", 171 | " n, m = matrix.shape\n", 172 | " for i in range(n):\n", 173 | " for j in range(m):\n", 174 | " factor = self.gf.pow(wm, i * j)\n", 175 | " matrix[i, j] = self.gf.mul(matrix[i, j], factor)\n", 176 | "\n", 177 | " def apply_column_fft(self, matrix):\n", 178 | " # do fft for each column in matrix\n", 179 | " n_rows, n_cols = matrix.shape \n", 180 | " for j in range(n_cols): \n", 181 | " column = matrix[:, j].tolist() \n", 182 | " fft_result = self.forward_dit(column) \n", 183 | " matrix[:, j] = fft_result \n", 184 | "\n", 185 | " def apply_row_fft(self,matrix):\n", 186 | " # do fft for each row in matrix\n", 187 | " for i in range(matrix.shape[0]):\n", 188 | " matrix[i] = self.forward_dit(matrix[i].tolist())" 189 | ] 190 | }, 191 | { 192 | "cell_type": "markdown", 193 | "metadata": {}, 194 | "source": [ 195 | "Here is the test code for ntt algorithm:" 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": 3, 201 | "metadata": {}, 202 | "outputs": [ 203 | { 204 | "name": "stdout", 205 | "output_type": "stream", 206 | "text": [ 207 | "forward_roots: [1, 2, 4, 8, 16, 15, 13, 9]\n", 208 | "inverse_roots: [1, 9, 13, 15, 16, 8, 4, 2]\n", 209 | "forward_result is: [2, 8, 14, 6, 13, 3, 12, 1]\n", 210 | "inverse_result is: [13, 15, 10, 11, 8, 5, 6, 1]\n", 211 | "inverse back result is: [1, 2, 3, 4, 5, 6, 7, 8]\n", 212 | "NTT tests passed!\n" 213 | ] 214 | } 215 | ], 216 | "source": [ 217 | "def test_ntt():\n", 218 | " # test for ffts: forward_dit, inverse_dit, forward_dif, inverse_dif.\n", 219 | " modulus = 17 \n", 220 | " input_array = [1,2,3,4,5,6,7,8] \n", 221 | " n = len(input_array) \n", 222 | " ntt = NTT(modulus, n)\n", 223 | " forward_roots=ntt.get_forward_roots(n)\n", 224 | " print(\"forward_roots:\", forward_roots)\n", 225 | " inverse_roots=ntt.get_inverse_roots(n)\n", 226 | " print(\"inverse_roots:\", inverse_roots)\n", 227 | "\n", 228 | " # test for forward fft\n", 229 | " forward_result_dit = ntt.forward_dit(input_array[:]) \n", 230 | " forward_result_dif = ntt.forward_dif(input_array[:]) \n", 231 | " assert forward_result_dit==forward_result_dif,\"forward_result_dit is not equal to forward_result_dif!\"\n", 232 | " print(\"forward_result is:\", forward_result_dit)\n", 233 | "\n", 234 | " # test for inverse fft\n", 235 | " inverse_result_dit = ntt.inverse_dit(input_array[:]) \n", 236 | " inverse_result_dif = ntt.inverse_dif(input_array[:]) \n", 237 | " assert inverse_result_dit==inverse_result_dif,\"inverse_result_dit is not equal to inverse_result_dif!\"\n", 238 | " print(\"inverse_result is:\", inverse_result_dit) \n", 239 | "\n", 240 | " # test if it can be restored to the original input\n", 241 | " result_back=ntt.inverse_dit(forward_result_dit)\n", 242 | " print(\"inverse back result is:\", result_back) \n", 243 | "\n", 244 | " assert result_back == input_array,\"NTT test Failed!\"\n", 245 | " print(\"NTT tests passed!\")\n", 246 | "\n", 247 | "if __name__ == \"__main__\":\n", 248 | " test_ntt()" 249 | ] 250 | }, 251 | { 252 | "cell_type": "markdown", 253 | "metadata": {}, 254 | "source": [ 255 | "## 3.3 Four-step FFT\n" 256 | ] 257 | }, 258 | { 259 | "cell_type": "code", 260 | "execution_count": 4, 261 | "metadata": {}, 262 | "outputs": [], 263 | "source": [ 264 | "def four_step(array, log_rows,modulus):\n", 265 | " n = len(array)\n", 266 | " logn = n.bit_length() - 1\n", 267 | " log_cols = logn - log_rows\n", 268 | " assert log_rows > 0\n", 269 | " assert log_cols > 0\n", 270 | " assert modulus > n\n", 271 | "\n", 272 | " gf = Field(modulus)\n", 273 | " ntt = NTT(modulus, n)\n", 274 | "\n", 275 | " # first step: transfer the array into matrix\n", 276 | " matrix = ntt.matrix(array, log_cols, log_rows)\n", 277 | " print(\"origin matrix is:\",matrix)\n", 278 | "\n", 279 | " # second step: do FFT for each column\n", 280 | " ntt.apply_column_fft(matrix)\n", 281 | " print(\"after column fft, matrix is:\",matrix)\n", 282 | "\n", 283 | " # third step: apply twiddles wm^(i*j)\n", 284 | " wm = ntt.get_forward_roots(n)[1]\n", 285 | " ntt.apply_twiddles(wm, matrix)\n", 286 | "\n", 287 | " # fourth step: do FFT for each row\n", 288 | " ntt.apply_row_fft(matrix)\n", 289 | " print(\"after row fft, matrix is:\",matrix) \n", 290 | "\n", 291 | " # Transpose the matrix, and flatten it into array\n", 292 | " out_array = ntt.transpose_and_flatten(matrix)\n", 293 | " print(\"after transpose and flatten, array is:\",out_array) \n", 294 | " return out_array" 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "metadata": {}, 300 | "source": [ 301 | "Here is the test code for four-step FFT algorithm:" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": 5, 307 | "metadata": {}, 308 | "outputs": [ 309 | { 310 | "name": "stdout", 311 | "output_type": "stream", 312 | "text": [ 313 | "origin matrix is: [[ 1 2 3 4]\n", 314 | " [ 5 6 7 8]\n", 315 | " [ 9 10 11 12]\n", 316 | " [13 14 15 16]]\n", 317 | "after column fft, matrix is: [[11 15 2 6]\n", 318 | " [11 11 11 11]\n", 319 | " [ 9 9 9 9]\n", 320 | " [ 7 7 7 7]]\n", 321 | "after row fft, matrix is: [[ 0 11 9 7]\n", 322 | " [ 5 15 10 14]\n", 323 | " [16 12 6 2]\n", 324 | " [ 4 8 3 13]]\n", 325 | "after transpose and flatten, array is: [0, 5, 16, 4, 11, 15, 12, 8, 9, 10, 6, 3, 7, 14, 2, 13]\n", 326 | "Four-Step FFT result: [0, 5, 16, 4, 11, 15, 12, 8, 9, 10, 6, 3, 7, 14, 2, 13]\n", 327 | "Direct FFT result is: [0, 5, 16, 4, 11, 15, 12, 8, 9, 10, 6, 3, 7, 14, 2, 13]\n", 328 | "Four-Step FFT tests passed!\n" 329 | ] 330 | } 331 | ], 332 | "source": [ 333 | "def test_four_step_fft():\n", 334 | " # a small example\n", 335 | " modulus = 17 # test example take 17 as modulus\n", 336 | " n = 16 \n", 337 | " log_rows = 2 # matrix shape is 4*4\n", 338 | " gf = Field(modulus)\n", 339 | " ntt = NTT(modulus, n)\n", 340 | " input_array = list(range(1, n + 1)) # [1, 2, ..., 16]\n", 341 | "\n", 342 | " # test four step fft\n", 343 | " four_step_result = four_step(input_array, log_rows,modulus)\n", 344 | " print(\"Four-Step FFT result:\", four_step_result)\n", 345 | "\n", 346 | " # Test the result is consistent with directly performing fft\n", 347 | " direct_result = ntt.forward_dit(input_array)\n", 348 | " print(\"Direct FFT result is:\", direct_result)\n", 349 | " assert four_step_result == direct_result, \"Four-Step FFT failed !\"\n", 350 | " print(\"Four-Step FFT tests passed!\")\n", 351 | "\n", 352 | "if __name__ == \"__main__\":\n", 353 | " test_four_step_fft()" 354 | ] 355 | } 356 | ], 357 | "metadata": { 358 | "kernelspec": { 359 | "display_name": "Python 3", 360 | "language": "python", 361 | "name": "python3" 362 | }, 363 | "language_info": { 364 | "codemirror_mode": { 365 | "name": "ipython", 366 | "version": 3 367 | }, 368 | "file_extension": ".py", 369 | "mimetype": "text/x-python", 370 | "name": "python", 371 | "nbconvert_exporter": "python", 372 | "pygments_lexer": "ipython3", 373 | "version": "3.11.5" 374 | } 375 | }, 376 | "nbformat": 4, 377 | "nbformat_minor": 2 378 | } 379 | -------------------------------------------------------------------------------- /Mersenne31/Mersenne31.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "72c03f7d-8cea-4aca-a82f-1d231deb8464", 6 | "metadata": {}, 7 | "source": [ 8 | "## Mersenne Field \n", 9 | "```python\n", 10 | "def sqrt(self):\n", 11 | " assert self.modulus % 4 == 3\n", 12 | " return self ** ((self.modulus + 1) // 4)\n", 13 | "```\n", 14 | "\n", 15 | "By [Euler's criterion](https://en.wikipedia.org/wiki/Euler%27s_criterion), if there is an $x$ s.t. $x^2 \\equiv a \\pmod p$, then $a^{\\frac{p-1}{2}}\\equiv 1 \\pmod p$.\n", 16 | "\n", 17 | "So $x^2 \\equiv a^{\\frac{p-1}{2}} a \\equiv a^{\\frac{p+1}{2}} \\pmod p$, also by $p \\equiv 3 \\pmod 4$, $\\frac{p+1}{4}$ is an integer. \n", 18 | "\n", 19 | "Therefore $x \\equiv a^{\\frac{p+1}{4}} \\pmod p$\n", 20 | "\n", 21 | "The `mersenne_31.rs` file in Plonky3 provided Rust code defines the `Mersenne31` prime field using the Mersenne prime $2^{31} - 1$. This field is used in cryptographic and mathematical computations due to its efficient arithmetic properties. The code includes implementations for various traits for field operations such as `Add`, `Sub`, `Mul`, `Div` to enable field arithmetic operations of `Mersenne31` values. Additionally, it provides methods for creating new `Mersenne31` instances, checking equality, hashing, and performing field-specific operations like exponentiation and inversion. Here is a simplified implementation using Python with detailed annotation:" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 3, 27 | "id": "35f40ed2-fa02-447b-be62-1a0411f12c51", 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "# Base class for elements in a finite field\n", 32 | "class FieldElement():\n", 33 | " def __init__(self, value):\n", 34 | " # If the input is an instance of FieldElement, extract its value\n", 35 | " if isinstance(value, self.__class__):\n", 36 | " value = value.value\n", 37 | " # Store the value modulo the field's modulus\n", 38 | " self.value = value % self.modulus\n", 39 | "\n", 40 | " # Addition operation\n", 41 | " def __add__(self, other):\n", 42 | " # Determine the value to add based on the type of 'other'\n", 43 | " othervalue = other if isinstance(other, int) else other.value\n", 44 | " # Return a new instance with the sum modulo the modulus\n", 45 | " return self.__class__((self.value + othervalue) % self.modulus)\n", 46 | "\n", 47 | " # Subtraction operation\n", 48 | " def __sub__(self, other):\n", 49 | " # Determine the value to subtract based on the type of 'other'\n", 50 | " othervalue = other if isinstance(other, int) else other.value\n", 51 | " # Return a new instance with the difference modulo the modulus\n", 52 | " return self.__class__((self.value - othervalue) % self.modulus)\n", 53 | "\n", 54 | " # Negation operation (additive inverse)\n", 55 | " def __neg__(self):\n", 56 | " # Return a new instance representing the negation\n", 57 | " return self.__class__(self.modulus - (self.value or self.modulus))\n", 58 | "\n", 59 | " # Multiplication operation\n", 60 | " def __mul__(self, other):\n", 61 | " # Determine the value to multiply based on the type of 'other'\n", 62 | " othervalue = other if isinstance(other, int) else other.value\n", 63 | " # Return a new instance with the product modulo the modulus\n", 64 | " return self.__class__((self.value * othervalue) % self.modulus)\n", 65 | "\n", 66 | " # Right-side addition (for operations like int + FieldElement)\n", 67 | " __radd__ = __add__\n", 68 | " # Right-side multiplication (for operations like int * FieldElement)\n", 69 | " __rmul__ = __mul__\n", 70 | "\n", 71 | " # Exponentiation operation\n", 72 | " def __pow__(self, other):\n", 73 | " # Return a new instance with the value raised to the power 'other' modulo the modulus\n", 74 | " return self.__class__(pow(self.value, other, self.modulus))\n", 75 | "\n", 76 | " # Multiplicative inverse\n", 77 | " def inv(self):\n", 78 | " # Compute the modular inverse using Python's built-in pow function with a negative exponent\n", 79 | " return self.__class__(\n", 80 | " pow(self.value, -1, self.modulus) if self.value else 0\n", 81 | " )\n", 82 | "\n", 83 | " # Square root operation (valid when modulus % 4 == 3)\n", 84 | " def sqrt(self):\n", 85 | " # Ensure the modulus is of the form 4k + 3\n", 86 | " assert self.modulus % 4 == 3\n", 87 | " # Return the square root using the exponentiation method specific to such moduli\n", 88 | " return self ** ((self.modulus + 1) // 4)\n", 89 | "\n", 90 | " # Division operation\n", 91 | " def __truediv__(self, other):\n", 92 | " # Convert 'other' to a FieldElement if it's an integer\n", 93 | " if isinstance(other, int):\n", 94 | " other = self.__class__(other)\n", 95 | " # Multiply by the multiplicative inverse of 'other'\n", 96 | " return self * other.inv()\n", 97 | "\n", 98 | " # Equality check\n", 99 | " def __eq__(self, other):\n", 100 | " # Determine the value to compare based on the type of 'other'\n", 101 | " othervalue = other if isinstance(other, int) else other.value\n", 102 | " # Check if the values are equal\n", 103 | " return self.value == othervalue\n", 104 | "\n", 105 | " # String representation for printing\n", 106 | " def __repr__(self):\n", 107 | " return '<' + str(self.value) + '>'\n", 108 | "\n", 109 | " # Convert the value to bytes (little-endian, 4 bytes)\n", 110 | " def to_bytes(self):\n", 111 | " return self.value.to_bytes(4, 'little')\n", 112 | "\n", 113 | " # Create an instance from bytes (little-endian)\n", 114 | " @classmethod\n", 115 | " def from_bytes(cls, bytez):\n", 116 | " return cls(int.from_bytes(bytez, 'little'))\n", 117 | "\n", 118 | "# Subclass representing a specific finite field with modulus 7\n", 119 | "class BabyMersenneElement(FieldElement):\n", 120 | " # Define the modulus as 2^3 - 1 = 7\n", 121 | " modulus = 2**3 - 1\n", 122 | "\n", 123 | "# Alias for easier usage\n", 124 | "BB = BabyMersenneElement\n" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": 2, 130 | "id": "3a5a4bb5-868b-428c-bcaf-601292d7f6b5", 131 | "metadata": {}, 132 | "outputs": [ 133 | { 134 | "data": { 135 | "text/plain": [ 136 | "<1>" 137 | ] 138 | }, 139 | "execution_count": 2, 140 | "metadata": {}, 141 | "output_type": "execute_result" 142 | } 143 | ], 144 | "source": [ 145 | "a = BB(3)\n", 146 | "b = BB(5)\n", 147 | "\n", 148 | "# Addition\n", 149 | "c = a + b # Result: <1> because (3 + 5) % 7 == 1\n", 150 | "c" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 3, 156 | "id": "219240cb-9c57-4fe0-8875-c329674dcbd6", 157 | "metadata": {}, 158 | "outputs": [ 159 | { 160 | "data": { 161 | "text/plain": [ 162 | "<1>" 163 | ] 164 | }, 165 | "execution_count": 3, 166 | "metadata": {}, 167 | "output_type": "execute_result" 168 | } 169 | ], 170 | "source": [ 171 | "# Multiplication\n", 172 | "d = a * b # Result: <1> because (3 * 5) % 7 == 1\n", 173 | "d" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": 4, 179 | "id": "8493aa3c-9936-483c-a142-766d805a9477", 180 | "metadata": {}, 181 | "outputs": [ 182 | { 183 | "data": { 184 | "text/plain": [ 185 | "<5>" 186 | ] 187 | }, 188 | "execution_count": 4, 189 | "metadata": {}, 190 | "output_type": "execute_result" 191 | } 192 | ], 193 | "source": [ 194 | "# Inverse\n", 195 | "e = a.inv() # Result: <5> because 3 * 5 % 7 == 1\n", 196 | "e" 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": 5, 202 | "id": "6e717585-c363-408b-8e75-80a5bd08adaa", 203 | "metadata": {}, 204 | "outputs": [ 205 | { 206 | "data": { 207 | "text/plain": [ 208 | "<2>" 209 | ] 210 | }, 211 | "execution_count": 5, 212 | "metadata": {}, 213 | "output_type": "execute_result" 214 | } 215 | ], 216 | "source": [ 217 | "# Division\n", 218 | "f = a / b # Result: <5> because 3 * 5^(-1) % 7 == 5\n", 219 | "f" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "id": "95e9a487-8b2f-4c48-a1b1-0ea4db5a26cf", 225 | "metadata": {}, 226 | "source": [ 227 | "## Extension Field" 228 | ] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "execution_count": 4, 233 | "id": "eb3ad341-e20f-4c3b-8dad-8626f94ca2db", 234 | "metadata": {}, 235 | "outputs": [], 236 | "source": [ 237 | "class ExtendedFieldElement():\n", 238 | " def __init__(self, value):\n", 239 | " self.value = self._to_list(value)\n", 240 | " self.modulus = self.value[0].modulus\n", 241 | "\n", 242 | " def _to_list(self, value):\n", 243 | " if isinstance(value, self.__class__):\n", 244 | " return value.value\n", 245 | " elif isinstance(value, self.subclass):\n", 246 | " return [value] + [self.subclass(0)]*3\n", 247 | " elif isinstance(value, list):\n", 248 | " return [self.subclass(v) for v in value]\n", 249 | " elif isinstance(value, int):\n", 250 | " return [self.subclass(value)] + [self.subclass(0)]*3\n", 251 | " else:\n", 252 | " raise Exception(\"Incompatible value: {}\".format(value))\n", 253 | "\n", 254 | " def __add__(self, other):\n", 255 | " othervalue = self._to_list(other)\n", 256 | " return self.__class__([x+y for x,y in zip(self.value, othervalue)])\n", 257 | "\n", 258 | " def __sub__(self, other):\n", 259 | " othervalue = self._to_list(other)\n", 260 | " return self.__class__([x-y for x,y in zip(self.value, othervalue)])\n", 261 | "\n", 262 | " def __mul__(self, other):\n", 263 | " if isinstance(other, (int, self.subclass)):\n", 264 | " return self.__class__([x*other for x in self.value])\n", 265 | " m1, m2, m3, m4 = self.value\n", 266 | " o1, o2, o3, o4 = self._to_list(other)\n", 267 | " o_LL = [m1*o1 - m2*o2, m1*o2 + m2*o1]\n", 268 | " o_LR = [m1*o3 - m2*o4, m1*o4 + m2*o3]\n", 269 | " o_RL = [m3*o1 - m4*o2, m3*o2 + m4*o1]\n", 270 | " o_RR = [m3*o3 - m4*o4, m3*o4 + m4*o3]\n", 271 | " o = [\n", 272 | " o_LL[0] - (o_RR[0] - o_RR[1]*self.extension_i),\n", 273 | " o_LL[1] - (o_RR[1] + o_RR[0]*self.extension_i),\n", 274 | " o_LR[0] + o_RL[0],\n", 275 | " o_LR[1] + o_RL[1]\n", 276 | " ]\n", 277 | " return self.__class__(o)\n", 278 | "\n", 279 | " __radd__ = __add__\n", 280 | " __rmul__ = __mul__\n", 281 | "\n", 282 | " def __pow__(self, other):\n", 283 | " if other == 0:\n", 284 | " return self.__class__([1,0,0,0])\n", 285 | " elif other == 1:\n", 286 | " return self\n", 287 | " elif other == 2:\n", 288 | " return self * self\n", 289 | " else:\n", 290 | " return self.__pow__(other % 2) * self.__pow__(other // 2) ** 2\n", 291 | "\n", 292 | " def inv(self):\n", 293 | " # return self ** (self.modulus ** 4 - 2)\n", 294 | " x0, x1, x2, x3 = self.value\n", 295 | " r20 = x2*x2 - x3*x3\n", 296 | " r21 = 2 * x2 * x3\n", 297 | " denom0 = x0**2 - x1**2 + r20 - r21 * 2\n", 298 | " denom1 = 2*x0*x1 + r21 + r20 * 2\n", 299 | " inv_denom_norm = (denom0 ** 2 + denom1 ** 2).inv()\n", 300 | " inv_denom0 = denom0 * inv_denom_norm\n", 301 | " inv_denom1 = -denom1 * inv_denom_norm\n", 302 | " o = self.__class__([\n", 303 | " x0 * inv_denom0 - x1 * inv_denom1,\n", 304 | " x0 * inv_denom1 + x1 * inv_denom0,\n", 305 | " -x2 * inv_denom0 + x3 * inv_denom1,\n", 306 | " -x2 * inv_denom1 - x3 * inv_denom0,\n", 307 | " ])\n", 308 | " return o\n", 309 | " \n", 310 | " def __truediv__(self, other):\n", 311 | " other = self.__class__(self._to_list(other))\n", 312 | " if other.value[1:] == [0,0,0]:\n", 313 | " factor = other.value[0].inv()\n", 314 | " return self.__class__([v * factor for v in self.value])\n", 315 | " else:\n", 316 | " return self * other.inv()\n", 317 | "\n", 318 | " def __eq__(self, other):\n", 319 | " return self.value == self._to_list(other)\n", 320 | "\n", 321 | " def __repr__(self):\n", 322 | " return '<'+str([v.value for v in self.value])+'>'\n", 323 | "\n", 324 | " def to_bytes(self):\n", 325 | " return b''.join([v.to_bytes() for v in self.value])\n", 326 | "\n", 327 | " @classmethod\n", 328 | " def from_bytes(cls, bytez):\n", 329 | " return cls([\n", 330 | " int.from_bytes(bytez[i:i+4], 'little') for i in range(0, 16, 4)\n", 331 | " ])\n", 332 | "\n", 333 | "class ExtendedBabyMersenneElement(ExtendedFieldElement):\n", 334 | " subclass = BabyMersenneElement\n", 335 | " extension_i = 4\n", 336 | "EBB = ExtendedBabyMersenneElement" 337 | ] 338 | }, 339 | { 340 | "cell_type": "code", 341 | "execution_count": 6, 342 | "id": "13aa4ee3-dc5f-48e0-bbf2-2812371e0cc9", 343 | "metadata": {}, 344 | "outputs": [ 345 | { 346 | "data": { 347 | "text/plain": [ 348 | "<[1, 0, 0, 0]>" 349 | ] 350 | }, 351 | "execution_count": 6, 352 | "metadata": {}, 353 | "output_type": "execute_result" 354 | } 355 | ], 356 | "source": [ 357 | "EBB([1,2,3,4]) ** (31**4-1)" 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": 7, 363 | "id": "94ac048f-e9f9-431d-b62e-8d01f58627b1", 364 | "metadata": {}, 365 | "outputs": [ 366 | { 367 | "data": { 368 | "text/plain": [ 369 | "<[2, 1, 3, 4]>" 370 | ] 371 | }, 372 | "execution_count": 7, 373 | "metadata": {}, 374 | "output_type": "execute_result" 375 | } 376 | ], 377 | "source": [ 378 | "EBB([1,2,3,4])*EBB([5,6,7,8])" 379 | ] 380 | }, 381 | { 382 | "cell_type": "markdown", 383 | "id": "6bf84224-5b20-4179-b0a3-cc010fd80396", 384 | "metadata": {}, 385 | "source": [ 386 | "## Circle Group " 387 | ] 388 | }, 389 | { 390 | "cell_type": "markdown", 391 | "id": "49760a62-26f4-4676-b3f3-9b6723cc06c7", 392 | "metadata": {}, 393 | "source": [ 394 | "Adding two points on the unit circle.\n", 395 | "$$\n", 396 | "\\begin{aligned}\n", 397 | "& x_{\\text {new }}=x 1 \\cdot x 2-y 1 \\cdot y 2=\\cos \\theta_1 \\cdot \\cos \\theta_2-\\sin \\theta_1 \\cdot \\sin \\theta_2=\\cos \\left(\\theta_1+\\theta_2\\right) \\\\\n", 398 | "& y_{\\text {new }}=x 1 \\cdot y 2+x 2 \\cdot y 1=\\cos \\theta_1 \\cdot \\sin \\theta_2+\\cos \\theta_2 \\cdot \\sin \\theta_1=\\sin \\left(\\theta_1+\\theta_2\\right)\n", 399 | "\\end{aligned}\n", 400 | "$$" 401 | ] 402 | }, 403 | { 404 | "cell_type": "code", 405 | "execution_count": 6, 406 | "id": "e13fcbd2-f9fb-48f9-b739-8122bac78fb1", 407 | "metadata": {}, 408 | "outputs": [], 409 | "source": [ 410 | "def point_add(pt1, pt2):\n", 411 | " (x1, y1), (x2, y2) = pt1, pt2\n", 412 | " return (\n", 413 | " x1 * x2 - y1 * y2,\n", 414 | " x1 * y2 + x2 * y1\n", 415 | " )" 416 | ] 417 | }, 418 | { 419 | "cell_type": "markdown", 420 | "id": "084ea114-e8c8-41ed-8473-9a122c506864", 421 | "metadata": {}, 422 | "source": [ 423 | "Doubling a point on the unit circle. This operation corresponds to doubling the angle $\\theta$ of a point $(\\cos \\theta, \\sin \\theta)$ on the unit circle. The formulas used in the function align with the double-angle formulas for cosine and sine:\n", 424 | "- $\\cos (2 \\theta)=2 \\cos ^2(\\theta)-1$\n", 425 | "- $\\sin (2 \\theta)=2 \\sin (\\theta) \\cos (\\theta)$" 426 | ] 427 | }, 428 | { 429 | "cell_type": "code", 430 | "execution_count": 8, 431 | "id": "35fc5dcc-6b44-40f9-970f-c8bbd5443e68", 432 | "metadata": {}, 433 | "outputs": [], 434 | "source": [ 435 | "def point_double(pt):\n", 436 | " x1, y1 = pt\n", 437 | " return (2 * x1 * x1 - 1, 2 * x1 * y1)" 438 | ] 439 | }, 440 | { 441 | "cell_type": "markdown", 442 | "id": "26eecea4-8506-4628-9a0a-2149a4983aac", 443 | "metadata": {}, 444 | "source": [ 445 | "![](./circlestark.png)" 446 | ] 447 | }, 448 | { 449 | "cell_type": "markdown", 450 | "id": "3422d338-f422-4c36-a107-74cf411893be", 451 | "metadata": {}, 452 | "source": [ 453 | "## References\n", 454 | "- https://github.com/Plonky3/Plonky3/tree/main/mersenne-31\n", 455 | "- https://eprint.iacr.org/2024/278.pdf\n", 456 | "- [Circle STARKs: Part I, Mersenne](https://www.zksecurity.xyz/blog/posts/circle-starks-1/)\n", 457 | "- [Ariel Gabizon - FFT's on the projective line and circle-STARKs](https://www.youtube.com/watch?v=d1f9sBajj10)\n", 458 | "- [Vitalik: Exploring circle STARKs](https://vitalik.eth.limo/general/2024/07/23/circlestarks.html)\n", 459 | "- [Finite field - Wikipedia](https://en.wikipedia.org/wiki/Finite_field)\n", 460 | "- [Circle group](https://en.wikipedia.org/wiki/Circle_group)" 461 | ] 462 | } 463 | ], 464 | "metadata": { 465 | "kernelspec": { 466 | "display_name": "miniconda3", 467 | "language": "python", 468 | "name": "conda-miniconda3" 469 | }, 470 | "language_info": { 471 | "codemirror_mode": { 472 | "name": "ipython", 473 | "version": 3 474 | }, 475 | "file_extension": ".py", 476 | "mimetype": "text/x-python", 477 | "name": "python", 478 | "nbconvert_exporter": "python", 479 | "pygments_lexer": "ipython3", 480 | "version": "3.11.5" 481 | } 482 | }, 483 | "nbformat": 4, 484 | "nbformat_minor": 5 485 | } 486 | -------------------------------------------------------------------------------- /BrakeDown/SimpleBrakedown.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "3972471f-ccb3-4ef1-bce6-86abc1f8711f", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import random\n", 11 | "import numpy as np\n", 12 | "\n", 13 | "prime = 47 # The order of our Finit Field \n", 14 | "\n", 15 | "# Our Polynomial is 3 * x^0 + 7 * x^1 + 11 * x^2 + 5 * x^3 ....\n", 16 | "coefficients = [3, 7, 11, 5, 2, 8, 4, 9, 1]\n", 17 | "message_length = 3 # len(coefficients)**0.5\n", 18 | "code_word_length = message_length * 2\n", 19 | "test_message = coefficients[:message_length]\n", 20 | "\n", 21 | "# The number of columns need to be checked\n", 22 | "sec_num = 2\n", 23 | "\n", 24 | "# The point we want to evaluate\n", 25 | "open_point = 5" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "id": "7f1c4436-87de-4db0-bf08-d7b13ce4f232", 31 | "metadata": {}, 32 | "source": [ 33 | "# Utils" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "id": "caf401f0-b211-470b-824c-5f9c97baec87", 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "def generate_random_polynomial(p, deg):\n", 44 | " n = deg + 1\n", 45 | " if int(n**0.5)**2 != n:\n", 46 | " raise ValueError(f\"{n} is not a perfect square\")\n", 47 | " return [random.randint(0, p - 1) for _ in range(n)]\n", 48 | "\n", 49 | "def add_polynomials(poly1, poly2, p):\n", 50 | " max_len = max(len(poly1), len(poly2))\n", 51 | " poly1 += [0] * (max_len - len(poly1))\n", 52 | " poly2 += [0] * (max_len - len(poly2))\n", 53 | " return [(poly1[i] + poly2[i]) % p for i in range(max_len)]\n", 54 | "\n", 55 | "def multiply_polynomials(poly1, poly2, p):\n", 56 | " result = [0] * (len(poly1) + len(poly2) - 1)\n", 57 | " for i in range(len(poly1)):\n", 58 | " for j in range(len(poly2)):\n", 59 | " result[i + j] = (result[i + j] + poly1[i] * poly2[j]) % p\n", 60 | " return result\n", 61 | "\n", 62 | "def find_generator(p):\n", 63 | " for g in range(2, p):\n", 64 | " seen = {pow(g, i, p) for i in range(p - 1)}\n", 65 | " if len(seen) == p - 1:\n", 66 | " return g\n", 67 | " raise ValueError(\"No generator found\")\n", 68 | "\n", 69 | "def polynomial_to_string(coefficients):\n", 70 | " terms = [\n", 71 | " f\"{coef}\" if i == 0 else f\"{coef}x^{i}\"\n", 72 | " for i, coef in enumerate(coefficients) if coef != 0\n", 73 | " ]\n", 74 | " return \" + \".join(terms)\n", 75 | "\n", 76 | "def evaluate_polynomial(coefficients, x, p):\n", 77 | " result = 0\n", 78 | " for i, coef in enumerate(coefficients):\n", 79 | " result = (result + coef * pow(x, i, p)) % p\n", 80 | " return result" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "id": "b543684e-7ecd-4b83-b30e-56d72b6d6786", 86 | "metadata": {}, 87 | "source": [ 88 | "## RSCODE base on lagrange interpolate" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 3, 94 | "id": "4ce566f0-0dab-4aff-8c3d-df15f780f450", 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [ 98 | "def lagrange_basis(points, j, p):\n", 99 | " \"\"\"\n", 100 | " Computes the Lagrange basis function L_j(x).\n", 101 | " :param points: List of interpolation points (x, y)\n", 102 | " :param j: Index of the current basis function\n", 103 | " :param p: Prime number defining the finite field\n", 104 | " :return: Coefficients of L_j(x) (from lowest to highest degree)\n", 105 | " \"\"\"\n", 106 | " x_j = points[j][0]\n", 107 | " numerator = [1] # Initialize numerator\n", 108 | " denominator = 1 # Initialize denominator\n", 109 | "\n", 110 | " for i, (x_i, _) in enumerate(points):\n", 111 | " if i != j:\n", 112 | " numerator = multiply_polynomials(numerator, [-x_i % p, 1], p)\n", 113 | " denominator = (denominator * (x_j - x_i)) % p\n", 114 | "\n", 115 | " denominator_inv = pow(denominator, -1, p) # Modular inverse of denominator\n", 116 | " return [(coeff * denominator_inv) % p for coeff in numerator]\n", 117 | "\n", 118 | "def interpolate_polynomial(points, p):\n", 119 | " result = [0] * len(points)\n", 120 | " for j, (_, y_j) in enumerate(points):\n", 121 | " basis = lagrange_basis(points, j, p)\n", 122 | " scaled_basis = [(coeff * y_j) % p for coeff in basis]\n", 123 | " result = add_polynomials(result, scaled_basis, p)\n", 124 | " return result\n", 125 | "\n", 126 | "def rs_encode(message, n, p):\n", 127 | " \"\"\"\n", 128 | " Reed-Solomon encoding.\n", 129 | " :param n: Codeword length\n", 130 | " :return: Codeword\n", 131 | " \"\"\"\n", 132 | " k = len(message)\n", 133 | " if n < k:\n", 134 | " raise ValueError(\"Codeword length n must be greater than or equal to message length k\")\n", 135 | " \n", 136 | " g = find_generator(p)\n", 137 | " points = [(pow(g, i, p), message[i]) for i in range(k)]\n", 138 | " coefficients = interpolate_polynomial(points, p)\n", 139 | "\n", 140 | " for x, y in points:\n", 141 | " assert evaluate_polynomial(coefficients, x, p) == y, f\"Interpolation error: x={x}, y={y}\"\n", 142 | "\n", 143 | " return [evaluate_polynomial(coefficients, pow(g, i, p), p) for i in range(n)]\n", 144 | "\n", 145 | "def rs_decode(codeword, m):\n", 146 | " \"\"\"\n", 147 | " In our case the rscode issystematic,\n", 148 | " meaning for any message m, the first m symbols of recode(m) are the entries of m itself.\n", 149 | " So we return the first m elements in codeword\n", 150 | " \"\"\"\n", 151 | " return codeword[:m]" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 4, 157 | "id": "8756658a-29b2-4b53-8823-d189317fb521", 158 | "metadata": {}, 159 | "outputs": [ 160 | { 161 | "name": "stdout", 162 | "output_type": "stream", 163 | "text": [ 164 | "Raw messages: [3, 7, 11]\n", 165 | "Reed-Solomon Codeword: [3, 7, 11, 7, 45, 40]\n" 166 | ] 167 | } 168 | ], 169 | "source": [ 170 | "# Simple test\n", 171 | "codeword = rs_encode(test_message, code_word_length, prime)\n", 172 | "\n", 173 | "print(f\"Raw messages: {test_message}\")\n", 174 | "print(f\"Reed-Solomon Codeword: {codeword}\")\n", 175 | "\n", 176 | "assert test_message == rs_decode(codeword, message_length)" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "id": "c2650569-2315-4dd3-b85e-21a15df506d0", 182 | "metadata": {}, 183 | "source": [ 184 | "# Brakedown" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "id": "50bdad6d-7d99-4a91-bc1c-65d90461c0eb", 190 | "metadata": {}, 191 | "source": [ 192 | "The key point of Brakedown is identifying Tensor Product Structure in Polynomial Evaluation Queries" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "id": "47da307d", 198 | "metadata": {}, 199 | "source": [ 200 | "## Tensor Product Structure\n", 201 | "\n", 202 | "Let q be a degree (n-1) univariate polynomial over field $\\mathbb{F}p$ that the prover wishes to commit to, and let u denote the vector of coefficients of q . Then, we can express evaluations of q as inner products of u with appropriate “evaluation vectors”. Specifically, if $q(X) = \\sum{i=0}^{n-1} u_i X^i$ , then for $z \\in \\mathbb{F}p$ , $q(z) = \\langle u$, $y \\rangle$ where $y = (1, z, z^2, \\ldots, z^{n-1})$ consists of powers of z , and $\\langle u, y \\rangle = \\sum{i=0}^{n-1} u_i v_i$ denotes the inner product of u and y .\n", 203 | "\n", 204 | "Moreover, the vector y has a tensor-product structure in the following sense. Let us assume that $n = m^2$ is a perfect square, and define $a, b \\in \\mathbb{F}^m$ as $a := (1, z, z^2, \\ldots, z^{m-1})$ and $b := (1, z^m, z^{2m}, \\ldots, z^{m(m-1)})$ . \n", 205 | "\n", 206 | "If we view y as an $m \\times m$ matrix with entries indexed as $(y_{i,j}, i = 1, \\ldots, m, j = 1, \\ldots, m)$ , then y is simply the outer product $b \\cdot a^T$ of a and b . That is, $y_{i,j} = z^{i \\cdot m + j} = b_i \\cdot a_j$ for all $0 \\leq i, j \\leq m-1$ . Equivalently, we can write $q(z)$ as the vector-matrix-vector product $b^T \\cdot u \\cdot a$ . \n", 207 | "\n", 208 | "The following `tensor_form` function is used to generate the tensorform of vector y" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "id": "4e05d47f-fdbd-44b2-9aa2-3fd1945299b5", 214 | "metadata": {}, 215 | "source": [ 216 | "## Public function" 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": 5, 222 | "id": "dfb5ed67-88d7-4a7d-a2fc-a95022e94a34", 223 | "metadata": {}, 224 | "outputs": [], 225 | "source": [ 226 | "def tensor_form(z, deg, p):\n", 227 | " \"\"\"\n", 228 | " Generates the tensor form of z based on the polynomial degree.\n", 229 | " :return: Vectors a and b representing the tensor form\n", 230 | " \"\"\"\n", 231 | " m = int((deg + 1)**0.5)\n", 232 | " a = [pow(z, i, p) for i in range(m)] # a = (1, z, z^2, ..., z^(m-1))\n", 233 | " b = [pow(z, m * i, p) for i in range(m)] # b = (1, z^m, z^(2m), ..., z^(m(m-1)))\n", 234 | " return a, b" 235 | ] 236 | }, 237 | { 238 | "cell_type": "markdown", 239 | "id": "41dd598d-1de9-4171-9528-f3661499bb63", 240 | "metadata": {}, 241 | "source": [ 242 | "## Prover compoments" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": 13, 248 | "id": "e5c2ce06-cc4c-4acb-95a7-6b6524cb0006", 249 | "metadata": {}, 250 | "outputs": [], 251 | "source": [ 252 | "def coefficient_to_rscode_matrix(coefficients, m, n, p):\n", 253 | " \"\"\"\n", 254 | " 1. Converts coefficients to matrix form.\n", 255 | " 2. Encodes the matrix using Reed-Solomon codes.\n", 256 | " 3. Prover claims the result (honest).\n", 257 | " :param coefficients: Polynomial coefficients (low to high degree)\n", 258 | " :param m: Sqrt of (deg(poly) + 1)\n", 259 | " :param n: encoded codeword length\n", 260 | " :param p: Prime number defining the finite field Fp\n", 261 | " :return: Matrix M\n", 262 | " \"\"\"\n", 263 | " degree = len(coefficients)\n", 264 | " if degree % m != 0:\n", 265 | " raise ValueError(f\"Length of coefficients {degree} must be divisible by number of rows {m}\")\n", 266 | "\n", 267 | " row_size = degree // m # Size of each sub-polynomial\n", 268 | "\n", 269 | " # Split coefficients into m sub-polynomials\n", 270 | " sub_polynomials = [\n", 271 | " coefficients[i * row_size:(i + 1) * row_size]\n", 272 | " for i in range(m)\n", 273 | " ]\n", 274 | "\n", 275 | " # Encode each sub-polynomial and construct matrix M\n", 276 | " matrix = []\n", 277 | " for sub_poly in sub_polynomials:\n", 278 | " codeword = rs_encode(sub_poly, n, p)\n", 279 | " matrix.append(codeword)\n", 280 | "\n", 281 | " return np.array(matrix)\n", 282 | "\n", 283 | "def compute_w(b, M, p):\n", 284 | " \"\"\"\n", 285 | " Computes w = b^T * M, where b is a row vector and M is a matrix.\n", 286 | " :param b: Row vector (can be part of the tensor form or a randomly generated vector by verifier)\n", 287 | " :param M: Input matrix\n", 288 | " :param p: Prime number defining the finite field\n", 289 | " :return: Resultant vector w after modular arithmetic\n", 290 | " \"\"\"\n", 291 | " return [(sum(b[i] * M[i][j] for i in range(len(b))) % p) for j in range(M.shape[1])]\n", 292 | "\n", 293 | "def decode_w(w):\n", 294 | " \"\"\"\n", 295 | " Decodes the vector w.\n", 296 | " :param w: Vector w\n", 297 | " :return: Decoded message\n", 298 | " \"\"\"\n", 299 | " return rs_decode(w, message_length)" 300 | ] 301 | }, 302 | { 303 | "cell_type": "markdown", 304 | "id": "1c226588-0ebe-4856-ab0f-29f165b34f06", 305 | "metadata": {}, 306 | "source": [ 307 | "## Verifer compoments" 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": 14, 313 | "id": "830f1e4b-71ce-4e98-9ffa-2c249b3ed306", 314 | "metadata": {}, 315 | "outputs": [], 316 | "source": [ 317 | "def generate_random_vector(m, p):\n", 318 | " \"\"\"\n", 319 | " Generates a random vector r over the finite field Fp.\n", 320 | " :param m: Sqrt of (deg(poly) + 1)\n", 321 | " :return: Random vector r\n", 322 | " \"\"\"\n", 323 | " return [random.randint(0, p - 1) for _ in range(m)]\n", 324 | "\n", 325 | "def consistency_check(M, r, w, p, t):\n", 326 | " \"\"\"\n", 327 | " Verifier performs a consistency check on the columns of the matrix M.\n", 328 | " :param M: Matrix M submitted by the Prover\n", 329 | " :param r: Random vector r\n", 330 | " :param w: Vector w submitted by the Prover\n", 331 | " :param p: Prime number defining the finite field\n", 332 | " :param t: Number of random columns to check\n", 333 | " :return: True if consistent, otherwise False\n", 334 | " \"\"\"\n", 335 | " n = M.shape[1] # number of columns of matrix\n", 336 | "\n", 337 | " # Step 1: random pick columns\n", 338 | " sampled_indices = random.sample(range(n), t)\n", 339 | " print(f\"Randomly selected column indices: {sampled_indices}\")\n", 340 | "\n", 341 | " # Step 2: consistency to each selected column\n", 342 | " for i in sampled_indices:\n", 343 | " # calculate r^T * M_i by verifier own\n", 344 | " computed_wi = sum(r[j] * M[j][i] for j in range(len(r))) % p\n", 345 | "\n", 346 | " # check computed_wi is equal to Prover's w[i]\n", 347 | " if computed_wi != w[i]:\n", 348 | " print(f\"Column {i} inconsistency: w[{i}] = {w[i]}, computed = {computed_wi}\")\n", 349 | " return False\n", 350 | "\n", 351 | " return True" 352 | ] 353 | }, 354 | { 355 | "cell_type": "code", 356 | "execution_count": 15, 357 | "id": "b55fc68f-e050-4b47-8eda-ae2ee329361e", 358 | "metadata": {}, 359 | "outputs": [], 360 | "source": [ 361 | "def simulate_interaction(coefficients, z, m, n, p, t):\n", 362 | " \"\"\"\n", 363 | " Simulates the complete interaction between Prover and Verifier\n", 364 | " :param m: Sqrt of (deg(poly) + 1)\n", 365 | " :param n: encoded codeword length, also, number of columns in matrix M\n", 366 | " :param t: Number of random columns to check\n", 367 | " :return: the evaluation of p(z)\n", 368 | " \"\"\"\n", 369 | " ## Commitment Phase\n", 370 | " # Step 1: Prover declares matrix M and evaluates the polynomial at point z\n", 371 | " M = coefficient_to_rscode_matrix(coefficients, m, n, p)\n", 372 | " # Badcase\n", 373 | " # Then verifier will recive MBad to do the consistency_check\n", 374 | " # MBad= coefficient_to_rscode_matrix([4, 7, 11, 5, 2, 8, 4, 9, 1], m, n, p)\n", 375 | " print(f\"Prover declares matrix M:\\n {M}\")\n", 376 | "\n", 377 | " # Step 2: Verifier generates a random vector r\n", 378 | " r = generate_random_vector(m, p)\n", 379 | " print(f\"Verifier generates random vector r: {r}\")\n", 380 | "\n", 381 | " # Step 3: Prover computes w = r^T * M, decodes it, and returns message v\n", 382 | " v = decode_w(compute_w(r, M, p))\n", 383 | " print(f\"Prover send message : {v} to verifier\")\n", 384 | "\n", 385 | " # Step 5: Verifier performs consistency check\n", 386 | " w = rs_encode(v, n, p)\n", 387 | " is_consistent = consistency_check(M, r, w, p, t)\n", 388 | " print(f\"Consistency check result: {is_consistent}\")\n", 389 | "\n", 390 | " ## Evaluation Phase\n", 391 | " # Step 1: Calculate tensor form of point z which is public to all\n", 392 | " a, b = tensor_form(z, len(coefficients), p)\n", 393 | " print(a, b)\n", 394 | "\n", 395 | " # Step 2: Prover calculate the left part of tensor product b*U*a which is b*U\n", 396 | " v = decode_w(compute_w(b, M, p)) # Computes v' = b^T * M\n", 397 | " print(f\"Prover send message : {v} to verifier\")\n", 398 | "\n", 399 | " # Step 3: Verifier ckecks v_i == b * M_i by random select\n", 400 | " w = rs_encode(v, n, p)\n", 401 | " is_consistent = consistency_check(M, b, w, p, t)\n", 402 | " print(\"Consistency check result:\", is_consistent)\n", 403 | "\n", 404 | " # Step 4: Verifier calculate the rest part of of tensor product b*U*a which is (b*U)*a\n", 405 | " evaluation = sum(v[i] * a[i] for i in range(len(a))) % p\n", 406 | "\n", 407 | " return evaluation" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": 16, 413 | "id": "c1a83673-0b10-4d5e-959d-59bef7886a67", 414 | "metadata": {}, 415 | "outputs": [ 416 | { 417 | "name": "stdout", 418 | "output_type": "stream", 419 | "text": [ 420 | "Prover declares matrix M:\n", 421 | " [[ 3 7 11 7 45 40]\n", 422 | " [ 5 2 8 46 13 7]\n", 423 | " [ 4 9 1 29 36 36]]\n", 424 | "Verifier generates random vector r: [39, 9, 26]\n", 425 | "Prover send message : [31, 8, 10] to verifier\n", 426 | "Randomly selected column indices: [4, 5]\n", 427 | "Consistency check result: True\n", 428 | "[1, 5, 25] [1, 31, 21]\n", 429 | "Prover send message : [7, 23, 45] to verifier\n", 430 | "Randomly selected column indices: [3, 2]\n", 431 | "Consistency check result: True\n", 432 | "Final result: 25\n" 433 | ] 434 | } 435 | ], 436 | "source": [ 437 | "# prime = 101\n", 438 | "# coefficients = generate_random_polynomial(prime, 8)\n", 439 | "result = simulate_interaction(coefficients, open_point, message_length, code_word_length , prime, sec_num)\n", 440 | "print(\"Final result:\", result)\n", 441 | "assert result == evaluate_polynomial(coefficients, open_point, prime)" 442 | ] 443 | }, 444 | { 445 | "cell_type": "markdown", 446 | "id": "ada5d5cd-657c-4153-a55a-cac16801bbf8", 447 | "metadata": {}, 448 | "source": [ 449 | "## REF\n", 450 | "\n", 451 | "PAZK Chapter 10(https://people.cs.georgetown.edu/jthaler/ProofsArgsAndZK.pdf)" 452 | ] 453 | }, 454 | { 455 | "cell_type": "markdown", 456 | "id": "2b4b46a1", 457 | "metadata": {}, 458 | "source": [] 459 | } 460 | ], 461 | "metadata": { 462 | "kernelspec": { 463 | "display_name": "Python 3 (ipykernel)", 464 | "language": "python", 465 | "name": "python3" 466 | }, 467 | "language_info": { 468 | "codemirror_mode": { 469 | "name": "ipython", 470 | "version": 3 471 | }, 472 | "file_extension": ".py", 473 | "mimetype": "text/x-python", 474 | "name": "python", 475 | "nbconvert_exporter": "python", 476 | "pygments_lexer": "ipython3", 477 | "version": "3.11.1" 478 | } 479 | }, 480 | "nbformat": 4, 481 | "nbformat_minor": 5 482 | } 483 | -------------------------------------------------------------------------------- /fft/radix_2_dit.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Radix-2 DIT FFT was invented by Cooley and Tukey in 1965. It's widely used in digital signal processing.\n", 8 | "\n", 9 | "It is a divide-and-conquer algorithm that recursively splits the problem into smaller subproblems. The complexity is O(N log N).\n", 10 | "\n", 11 | "Radix-2 means that the number of points is a power of 2. DIT is Decimation In Time, means that the input is decimated in time domain. we will show why we need FFT and how can we do Radix-2 DIT FFT.\n", 12 | "\n", 13 | "### 1. Background of the problem\n", 14 | "#### 1.1 Example\n", 15 | "\n", 16 | "One representation of the polynomial is using coefficient form. Such as:\n", 17 | "\n", 18 | "$$f(x) = a_0 + a_1 x + a_2 x^2 + a_3 x^3$$\n", 19 | "\n", 20 | "$$g(x) = b_0 + b_1 x + b_2 x^2 + b_3 x^3$$\n", 21 | "\n", 22 | "Normally, if you want to compute the convolution of $f(x)$ and $g(x)$, you would need to compute the following:\n", 23 | "\n", 24 | "$$h(x) = f(x) * g(x) = (a_0 + a_1 x + a_2 x^2 + a_3 x^3) * (b_0 + b_1 x + b_2 x^2 + b_3 x^3) = c_0 + c_1 x + c_2 x^2 + c_3 x^3 + c_4 x^4 + c_5 x^5 + c_6 x^6$$\n", 25 | "\n", 26 | "This is a polynomial multiplication, and the complexity is O(N^2).Can we lower the complexity?\n", 27 | "\n", 28 | "Before we answer this question, let's take a look at another representation of the polynomial.\n", 29 | "#### 1.2 Coefficient form and evaluation form\n", 30 | "A polynomial in the variable x over an algebraic field F represents a function f(x) as a formal sum:\n", 31 | "$$f(x)=\\sum_{i=1}^n a_i*x^i$$\n", 32 | "such as the formula above:$f(x) = a_0 + a_1 x + a_2 x^2 + a_3 x^3$, We call the values $a_0,a_1,a_2,a_3$ the coefficients of the polynomial.\n", 33 | "\n", 34 | "Another form of a polynomial is called evaluation form, that means a point-value representation of a polynomial f(x) of degree-bound n is a set of n point-value pairs:\n", 35 | "$${(x_0,y_0),(x_1,y_1)...(x_{n-1},y_{n-1})}$$\n", 36 | "\n", 37 | "such as the polynomial $f(x)$ and $g(x)$ above:\n", 38 | "\n", 39 | "$$f(x) = (x_0, f(x_0)), (x_1, f(x_1)), (x_2, f(x_2)), (x_3, f(x_3))$$\n", 40 | "\n", 41 | "$$g(x) = (x_0, g(x_0)), (x_1, g(x_1)), (x_2, g(x_2)), (x_3, g(x_3))$$\n", 42 | "Then, we want the value of points on $h(x)$: $h(x_0)=f(x_0)*g(x_0)$\n", 43 | "\n", 44 | "### 1.3 Do multiplication by evaluation\n", 45 | "\n", 46 | "So we can see, if we want compute multiplication of two polynomial,the complexity of this is only O(N) by evaluation form. But we need expand the points first. 4 points are enough to represent a polynomial of degree 3. From previous section, we know that $h(x)$ is a polynomial. of degree 6. So we need at least 7 points to represent $h(x)$.\n", 47 | "\n", 48 | "So the steps of polynomial multiplication in point-value form is as follows:\n", 49 | "\n", 50 | "1. Extend the fields of $f(x)$ and $g(x)$ to at least 7 points.\n", 51 | "2. Compute the point-value form of $f(x)$ and $g(x)$.\n", 52 | "3. Multiply the point-value form of $f(x)$ and $g(x)$.\n", 53 | "\n", 54 | "Let's take a look at the steps in detail.\n", 55 | "1. Extend the fields of $f(x)$ and $g(x)$ to at least 7 points. We use 8 points for convenience. Later we will explain why we need to extend the fields and why we use 8 points.\n", 56 | "So new fields are $x_0, x_1, x_2, x_3, x_4, x_5, x_6, x_7$.\n", 57 | "\n", 58 | "2. Compute the point-value form of $f(x)$ and $g(x)$.\n", 59 | "$f(x) = (x_0, f(x_0)), (x_1, f(x_1)), (x_2, f(x_2)), (x_3, f(x_3)), (x_4, f(x_4)), (x_5, f(x_5)), (x_6, f(x_6)), (x_7, f(x_7))$\n", 60 | "$g(x) = (x_0, g(x_0)), (x_1, g(x_1)), (x_2, g(x_2)), (x_3, g(x_3)), (x_4, g(x_4)), (x_5, g(x_5)), (x_6, g(x_6)), (x_7, g(x_7))$\n", 61 | "\n", 62 | "3. Multiply the point-value form of $f(x)$ and $g(x)$.\n", 63 | "$h(x) = (x_0, f(x_0) * g(x_0)), (x_1, f(x_1) * g(x_1)), (x_2, f(x_2) * g(x_2)), (x_3, f(x_3) * g(x_3)), (x_4, f(x_4) * g(x_4)), (x_5, f(x_5) * g(x_5)), (x_6, f(x_6) * g(x_6)), (x_7, f(x_7) * g(x_7))$\n", 64 | "\n", 65 | "If we can always compute polynomial multiplication in point-value form in O(N) time, we can use this to speed up the polynomial multiplication.\n", 66 | "\n", 67 | "However, there are two problems:\n", 68 | "1. How to convert a polynomial from coefficient form to point-value form?\n", 69 | "2. How to convert a polynomial from point-value form to coefficient form?\n", 70 | "The first problem is called polynomial interpolation. The second problem is called polynomial evaluation.\n", 71 | "\n", 72 | "### 2. DFT \n", 73 | "#### 2.1 Compute directly\n", 74 | "We can certainly do the polynomial form transform computation directly, actually it's like a matrix operation.\n", 75 | "Let's use vectors to express this: \n", 76 | "\n", 77 | "$\\vec{f}$=$\n", 78 | "\\begin{pmatrix}\n", 79 | "f(x_0)\\\\\n", 80 | "f(x_1)\\\\\n", 81 | "\\cdots\\\\\n", 82 | "f(x_{n-1}) \n", 83 | "\\end{pmatrix}\n", 84 | "$=\n", 85 | "$\n", 86 | "\\left(\\begin {array}{c}\n", 87 | "1 &x_0 &{x_0}^2 &{\\cdots} &{x_0^{n-1}}\\\\\n", 88 | "1 &x_1 &{x_1}^2 &{\\cdots} &{x_1^{n-1}} \\\\\n", 89 | "&{\\cdots} &{\\cdots} &{\\cdots} &{\\cdots} \\\\\n", 90 | "1 &x_{n-1} &x_{n-1}^2 &{\\cdots} &{x_{n-1}^{n-1}} \\\\\n", 91 | "\\end{array}\\right)\n", 92 | "$*$\n", 93 | "\\begin{pmatrix}\n", 94 | "a_1\\\\\n", 95 | "a_2\\\\\n", 96 | "\\cdots\\\\\n", 97 | "a_{n-1} \n", 98 | "\\end{pmatrix}\n", 99 | "$\n", 100 | "\n", 101 | "We donate the Vandermonde matrix of x as M, that is: $$\\vec{f}=M*\\vec{a}$$\n", 102 | "\n", 103 | "then inversely, we compute the coffecients $\\vec{a}$ like this: $$\\vec{a}=M^{-1}*\\vec{f}$$\n", 104 | "#### 2.2 DFT\n", 105 | "DFT is a mathematical tool for converting discrete signals from the time domain to the frequency domain. It is a discrete version of the Fourier transform and is particularly suitable for processing discrete and finite length signals.\n", 106 | "Recall the problem above,this time we evaluate f(x) at complex nth roots of unity, instead of random points $x_0,x_1...x_{n-1}$.\n", 107 | "\n", 108 | "A complex nth root of unity is a complex number $\\omega$ such that $\\omega^n=1$.We use n complex nth roots of unity,then $\\omega_{n}=e^{2\\pi ik/n}$.
\n", 109 | "The n complex nth roots of unity is $\\omega_{n}^0,\\omega_{n}^1,\\omega_{n}^2...\\omega_{n}^{n-1}$, we can see the n complex roots of unity are equally spaced around the circle of unit radius centered at the origin of the complex plane.
\n", 110 | "\n", 111 | "\"Example\n", 112 | "\n", 113 | "Further,if n is power of 2,we can get $\\omega_{n}^{i}=-\\omega_{n}^{n/2+i}$, the points on the circle are paired,such as:
\n", 114 | "$\\omega_{n}^{1}=-\\omega_{n}^{5},\\omega_{n}^{2}=-\\omega_{n}^{6},\\omega_{n}^{3}=-\\omega_{n}^{5},\\omega_{n}^{7}=-\\omega_{n}^{5},\\omega_{n}^{4}=-\\omega_{n}^{0}=-1$\n", 115 | "\n", 116 | "With this condition attached, on this basis, we can start to do FFT!\n", 117 | "\n", 118 | "One more thing, due to the imprecision issues associated with using complex numbers, we utilize finite fields as a replacement for expressing values." 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "### 3. FFT \n", 126 | "#### 3.1 Overview of the Framework\n", 127 | "By using a method known as the fast Fourier transform (FFT), which takes advantage of the special properties of the complex roots of unity, we can compute DFT in time $O(nlogn)$, as opposed to the $O(n^2)$ time of the straightforward method.\n", 128 | "The FFT method employs a divide-and-conquer strategy, using the even-indexed and odd-indexed coefficients of $f(x)$ separately to define the two new polynomials $f_{even}(x)$ and $f_{odd}(x)$ of degree-bound n/2. Note that here n is power of 2:\n", 129 | "\n", 130 | "$$f(x)=f_{even}(x^2)+x*f_{odd}(x^2),\\quad f(-x)=f_{even}(x^2)-x*f_{odd}(x^2)\\quad \\forall x \\in \\{1,w,w^2...w^n\\}$$ \n", 131 | "\n", 132 | "Here we can see, the split function $f_{even}(x^2)$ and $f_{odd}(x^2)$ are functions of $x^2,\\quad \\forall x^2 \\in \\{1,w^2,w^4...w^n\\}$,\n", 133 | "if we can get the evaluation of $f_{even}(x^2)$ and $f_{odd}(x^2)$, then we can get the evaluation of $f(x)$. so we can do this recursively to get next layer split fuction.\n", 134 | "\n", 135 | "Next, we will show the FFT algorithm step by step through an example.\n", 136 | "\n", 137 | "Here is the simple case:given the coeffecients of $f(x)$ and $g(x)$,compute $h(x)=f(x)*g(x)$\n", 138 | "$$f(x)=1+2x+3x^2+4x^3$$\n", 139 | "$$g(x)=4+5x+6x^2+7x^3$$\n", 140 | "\n", 141 | "#### 3.2 FinteField & Domain\n", 142 | "##### FinteField\n", 143 | "Finite fields are algebraic structures that consist of a finite number of elements, and they play a crucial role in various areas of mathematics, computer science, and cryptography.\n", 144 | "Here is the python implementation of finte field that provides functions for basic arithmetic operations (addition, subtraction, multiplication, division, exponentiation)." 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": 1, 150 | "metadata": {}, 151 | "outputs": [ 152 | { 153 | "name": "stdout", 154 | "output_type": "stream", 155 | "text": [ 156 | "15\n", 157 | "12\n", 158 | "16\n", 159 | "9\n", 160 | "6\n", 161 | "12\n" 162 | ] 163 | } 164 | ], 165 | "source": [ 166 | "class Field:\n", 167 | " modulus = None # Class-level variable to store the modulus of the field\n", 168 | "\n", 169 | " @classmethod\n", 170 | " def set_modulus(cls, p):\n", 171 | " if p <= 0:\n", 172 | " raise ValueError(\"Modulus must be a positive integer.\")\n", 173 | " cls.modulus = p #Set the modulus for the finite field\n", 174 | "\n", 175 | " def __init__(self, value):\n", 176 | " if Field.modulus is None:\n", 177 | " raise ValueError(\"Modulus is not set. Call Field.set_modulus(p) first.\")\n", 178 | " self.value = value % Field.modulus # Ensure the value is within the field\n", 179 | "\n", 180 | " def __repr__(self):\n", 181 | " return str(self.value)\n", 182 | "\n", 183 | " def __eq__(self, other):\n", 184 | " if isinstance(other, Field):\n", 185 | " return self.value == other.value\n", 186 | " return False\n", 187 | "\n", 188 | " def __add__(self, other):\n", 189 | " if isinstance(other, Field):\n", 190 | " return Field(self.value + other.value)\n", 191 | " raise ValueError(\"Cannot add elements from different fields.\")\n", 192 | "\n", 193 | " def __sub__(self, other):\n", 194 | " if isinstance(other, Field):\n", 195 | " return Field(self.value - other.value)\n", 196 | " raise ValueError(\"Cannot subtract elements from different fields.\")\n", 197 | "\n", 198 | " def __mul__(self, other):\n", 199 | " if isinstance(other, Field):\n", 200 | " return Field(self.value * other.value)\n", 201 | " elif isinstance(other, int):\n", 202 | " return Field(self.value * other)\n", 203 | " raise ValueError(\"Cannot multiply Field with non-integer or non-Field.\")\n", 204 | " \n", 205 | " def __rmul__(self, other):\n", 206 | " return self.__mul__(other)\n", 207 | "\n", 208 | " def __truediv__(self, other):\n", 209 | " if isinstance(other, Field):\n", 210 | " return self * other.inverse()\n", 211 | " elif isinstance(other, int):\n", 212 | " return self / Field(other)\n", 213 | " raise ValueError(\"Cannot divide Field by non-integer or non-Field.\")\n", 214 | "\n", 215 | " def __pow__(self, exponent):\n", 216 | " if exponent < 0:\n", 217 | " # Negative exponent: compute the inverse first\n", 218 | " return self.inverse() ** -exponent\n", 219 | " result = pow(self.value, exponent, Field.modulus)\n", 220 | " return Field(result)\n", 221 | "\n", 222 | " def inverse(self):\n", 223 | " t, new_t = 0, 1\n", 224 | " r, new_r = Field.modulus, self.value\n", 225 | " while new_r != 0:\n", 226 | " quotient = r // new_r\n", 227 | " t, new_t = new_t, t - quotient * new_t\n", 228 | " r, new_r = new_r, r - quotient * new_r\n", 229 | " if r > 1:\n", 230 | " raise ValueError(f\"{self.value} has no multiplicative inverse in F_{Field.modulus}.\")\n", 231 | " return Field(t % Field.modulus)\n", 232 | "\n", 233 | " def __neg__(self):\n", 234 | " return Field(-self.value)\n", 235 | "\n", 236 | " # Python's right-hand operations\n", 237 | " __radd__ = __add__\n", 238 | " __rsub__ = lambda self, other: Field(other) - self\n", 239 | " __rmul__ = __mul__\n", 240 | "\n", 241 | "\n", 242 | "def test_field():\n", 243 | " Field.set_modulus(17) # Create elements in the finite field F_17\n", 244 | " a = Field(5)\n", 245 | " b = Field(10)\n", 246 | " # Perform operations\n", 247 | " print(a + b) # Addition: 15\n", 248 | " print(a - b) # Subtraction: 12\n", 249 | " print(a * b) # Multiplication: 16\n", 250 | " print(a / b) # Division: 9\n", 251 | " print(a ** 3) # Exponentiation: 15\n", 252 | " print(b.inverse()) # Multiplicative inverse: 12\n", 253 | "\n", 254 | "test_field()" 255 | ] 256 | }, 257 | { 258 | "cell_type": "markdown", 259 | "metadata": {}, 260 | "source": [ 261 | "##### Domain\n", 262 | "First we need choose a domain for the evaluate points, here we will see how the Domain get halved by each layer of FFT.\n", 263 | "Since the following deals with finite field operations, we need to install the galois library first.\n", 264 | "we choose p=17, and choose $\\omega=2$ as the 8th root of unity.
\n", 265 | "First Layer, Domain ${D_1}=<\\omega>=\\{\\omega_{0},\\omega_{1}...\\omega_{7}\\}={1,2,4,8,16,15,13,9},|D_1|=8$
\n", 266 | "Second Layer, Domain ${D_2}=<\\omega^2>=\\{\\omega_{0},\\omega_{2},\\omega_{4},\\omega_{6}\\}={1,4,16,13},|D_2|=4$
\n", 267 | "Third Layer, Domain ${D_3}=<\\omega^4>=\\{\\omega_{0},\\omega_{4}\\}={1,16},|D_3|=2$
\n", 268 | "Last Layer, Domain ${D_4}=<\\omega^8>=\\{1\\},|D_3|=1$
" 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": 2, 274 | "metadata": {}, 275 | "outputs": [ 276 | { 277 | "name": "stdout", 278 | "output_type": "stream", 279 | "text": [ 280 | "[1, 2, 4, 8, 16, 15, 13, 9]\n", 281 | "[1, 4, 16, 13]\n", 282 | "[1, 16]\n", 283 | "[1]\n" 284 | ] 285 | } 286 | ], 287 | "source": [ 288 | "Field.set_modulus(17)\n", 289 | "w = Field(2)\n", 290 | "order=8\n", 291 | "\n", 292 | "while order!=0:\n", 293 | " Domain=[w**i for i in range(order)]\n", 294 | " print(Domain)\n", 295 | " w=w**2\n", 296 | " order=order//2" 297 | ] 298 | }, 299 | { 300 | "cell_type": "markdown", 301 | "metadata": {}, 302 | "source": [ 303 | "#### 3.3 Split and Recursion\n", 304 | "We take the function f(x) above, split it into two parts:\n", 305 | "$$f(x)=1+2x+3x^2+4x^3=f_{even}(x^2)+x*f_{odd}(x^2)=(1+3x^2)+x(2+4x^2)\\quad \\forall x \\in \\{1,2,4,8,16,15,13,9\\}$$\n", 306 | "We should first compute the split function:\n", 307 | "$$f_{even}(x^2)=1+3x^2,f_{odd}(x^2)=2+4x^2 \\quad \\forall x^2 \\in \\{1,4,16,13\\}$$\n", 308 | "For this two functions are simple,so here we can directly get the result,otherwise shold do split recursively until the length of Domain is one, then recursively cobmine them then get evaluations of $f(x)$.
\n", 309 | "\n", 310 | "Here is the steps:
\n", 311 | "1.we get evaluation of $f_{even}(x^2)$ and $f_{odd}(x^2)$ on ${1,2,4,8}$
\n", 312 | "2.then we can get evaluation of $f(x)$ on ${1,2,4,8}$ by combination: $f_{even}(x^2)+x*f_{odd}(x^2)$
\n", 313 | "3.then we can get evaluation of $f(x)$ on ${-1,-2,-4,-8}$ (actually,that is ${16,15,13,9}$) by combination: $f_{even}(x^2)-x*f_{odd}(x^2)$
\n", 314 | "\n", 315 | "We can see the code of fft and ifft function.\n", 316 | "we use fft function to transform coefficients form to evaluation form, and use ifft reverse back." 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": 3, 322 | "metadata": {}, 323 | "outputs": [], 324 | "source": [ 325 | "def get_domain(p,generator):\n", 326 | " Field.set_modulus(p)\n", 327 | " w = Field(generator)\n", 328 | " domain=[]\n", 329 | " for i in range(p):\n", 330 | " k=w**i\n", 331 | " if i!=0 and k==Field(1):\n", 332 | " return domain\n", 333 | " else:\n", 334 | " domain.append(k)\n", 335 | " return domain\n", 336 | "\n", 337 | "def halve_domain(domain, preserve_length=False):\n", 338 | " new_length = len(domain) if preserve_length else len(domain)//2\n", 339 | " return [x**2 for x in domain[:new_length]]\n", 340 | "\n", 341 | "# fft: from coefficients to evaluations\n", 342 | "def fft(vals, domain):\n", 343 | " if (len(domain) & (len(domain) - 1)) != 0:\n", 344 | " raise ValueError(\"Domain length must be a power of 2.\") \n", 345 | "\n", 346 | " if len(vals) < len(domain):\n", 347 | " if len(vals) == 0: \n", 348 | " zero = Field(0)\n", 349 | " else: \n", 350 | " zero = vals[0] - vals[0]\n", 351 | " vals = vals + [zero] * (len(domain) - len(vals))\n", 352 | "\n", 353 | " if len(vals) == 1:\n", 354 | " return vals\n", 355 | " half_domain = halve_domain(domain)\n", 356 | " f0 = fft(vals[::2], half_domain)\n", 357 | " f1 = fft(vals[1::2], half_domain)\n", 358 | " left = [L+x*R for L,R,x in zip(f0, f1, domain)]\n", 359 | " right = [L-x*R for L,R,x in zip(f0, f1, domain)]\n", 360 | " return left+right\n", 361 | "\n", 362 | "# ifft: from evaluations to coefficients\n", 363 | "def inv_fft(vals, domain):\n", 364 | " if (len(domain) & (len(domain) - 1)) != 0:\n", 365 | " raise ValueError(\"Domain length must be a power of 2.\")\n", 366 | " \n", 367 | " if len(vals) < len(domain):\n", 368 | " if len(vals) == 0: \n", 369 | " zero = Field(0)\n", 370 | " else: \n", 371 | " zero = vals[0] - vals[0]\n", 372 | " vals = vals + [zero] * (len(domain) - len(vals))\n", 373 | "\n", 374 | " if len(vals) == 1:\n", 375 | " return vals\n", 376 | " half_domain = halve_domain(domain)\n", 377 | " left = vals[:len(domain)//2]\n", 378 | " right = vals[len(domain)//2:]\n", 379 | " f0 = [(L+R)/2 for L,R in zip(left, right)]\n", 380 | " f1 = [(L-R)/(2*x) for L,R,x in zip(left, right, domain)]\n", 381 | " o = [0] * len(domain)\n", 382 | " o[::2] = inv_fft(f0, half_domain)\n", 383 | " o[1::2] = inv_fft(f1, half_domain)\n", 384 | " return o" 385 | ] 386 | }, 387 | { 388 | "cell_type": "markdown", 389 | "metadata": {}, 390 | "source": [ 391 | "now we can use the function fft and inv-fft to compute evaluations and coefficients of polynomial.
\n", 392 | "For $f(x)=1+2x+3x^2+4x^3$,$g(x)=5+6x+7x^2+8x^3$, we can get $h(x)=f(x)*g(x)=5+16x+9x^3+10x^4+x^5+15x^6$
\n", 393 | "If you compute directly, you can get the same result." 394 | ] 395 | }, 396 | { 397 | "cell_type": "code", 398 | "execution_count": 4, 399 | "metadata": {}, 400 | "outputs": [ 401 | { 402 | "name": "stdout", 403 | "output_type": "stream", 404 | "text": [ 405 | "domian is: [1, 2, 4, 8, 16, 15, 13, 9]\n", 406 | "coefficients of f(x) is: [1, 2, 3, 4]\n", 407 | "evaluations of f(x) is: [10, 15, 7, 13, 15, 11, 6, 16]\n", 408 | "check coefficients of f(x): [1, 2, 3, 4, 0, 0, 0, 0]\n", 409 | "evaluations of h(x) is: [9, 7, 7, 7, 15, 8, 6, 15]\n", 410 | "evaluations of h(x) is: [5, 3, 15, 6, 4, 3, 2, 2]\n", 411 | "coefficients of f(x) is: [5, 16, 0, 9, 10, 1, 15, 0]\n" 412 | ] 413 | } 414 | ], 415 | "source": [ 416 | "# set the FieldElement and generator. we choose 17 as modulus, choose 2 as the generator.\n", 417 | "p=17\n", 418 | "generator=2\n", 419 | "\n", 420 | "# a small exmaple: coefficients is [1,2,3,4]\n", 421 | "domain=get_domain(p,generator)\n", 422 | "original_coef_fx=[1,2,3,4]\n", 423 | "coef_fx=[Field(x) for x in original_coef_fx]\n", 424 | "print(\"domian is:\",domain)\n", 425 | "print(\"coefficients of f(x) is:\",coef_fx)\n", 426 | "\n", 427 | "# do fft to get the evaluations,and do ifft to get coefficients inversely\n", 428 | "eval_fx=fft(coef_fx,domain)\n", 429 | "print(\"evaluations of f(x) is:\",eval_fx)\n", 430 | "check_coef_fx=inv_fft(eval_fx,domain)\n", 431 | "print(\"check coefficients of f(x):\",check_coef_fx) # check for the coefficients backward calculated by inv-fft\n", 432 | "\n", 433 | "# compute evaluation g(x)\n", 434 | "original_coef_gx=[5,6,7,8]\n", 435 | "coef_gx=[Field(x) for x in original_coef_gx]\n", 436 | "eval_gx=fft(coef_gx,domain)\n", 437 | "print(\"evaluations of h(x) is:\",eval_gx)\n", 438 | "# compute h(x)=f(x)*g(x)\n", 439 | "eval_hx = [f * g for f, g in zip(eval_fx, eval_gx)]\n", 440 | "print(\"evaluations of h(x) is:\",eval_hx)\n", 441 | "# get coefficients of h(x) by inv-fft\n", 442 | "coef_hx=inv_fft(eval_hx,domain)\n", 443 | "print(\"coefficients of f(x) is:\",coef_hx)" 444 | ] 445 | }, 446 | { 447 | "cell_type": "markdown", 448 | "metadata": {}, 449 | "source": [ 450 | "The following diagram shows the process of fft and inverse fft algorithm. It looks like a butterfly,and we call the factors $w^k$ twiddle factors.
\n", 451 | "fft:coefficients to evaluations\n", 452 | "\n", 453 | "\"Example\n", 454 | "\n", 455 | "Inv-fft:evaluations to coefficients\n", 456 | "\n", 457 | "\"Example" 458 | ] 459 | } 460 | ], 461 | "metadata": { 462 | "kernelspec": { 463 | "display_name": "Python 3", 464 | "language": "python", 465 | "name": "python3" 466 | }, 467 | "language_info": { 468 | "codemirror_mode": { 469 | "name": "ipython", 470 | "version": 3 471 | }, 472 | "file_extension": ".py", 473 | "mimetype": "text/x-python", 474 | "name": "python", 475 | "nbconvert_exporter": "python", 476 | "pygments_lexer": "ipython3", 477 | "version": "3.9.6" 478 | } 479 | }, 480 | "nbformat": 4, 481 | "nbformat_minor": 2 482 | } 483 | --------------------------------------------------------------------------------