From fa3551264a9081797c696acf359fbff719512e51 Mon Sep 17 00:00:00 2001 From: renaud dubois Date: Tue, 18 Feb 2025 08:20:44 +0100 Subject: [PATCH 01/21] Push EIP NTT proposal with assets --- EIPS/eip-ntt.md | 264 ++++++++++++++++ assets/eip-NTT/README.md | 97 ++++++ assets/eip-NTT/pythonref/README.md | 28 ++ assets/eip-NTT/pythonref/makefile | 28 ++ assets/eip-NTT/pythonref/polyntt/__init__.py | 0 .../polyntt/bench_iterative_recursive.py | 68 +++++ assets/eip-NTT/pythonref/polyntt/ntt.py | 42 +++ .../polyntt/ntt_constants_iterative.py | 91 ++++++ .../polyntt/ntt_constants_recursive.py | 46 +++ .../pythonref/polyntt/ntt_iterative.py | 70 +++++ .../pythonref/polyntt/ntt_recursive.py | 122 ++++++++ assets/eip-NTT/pythonref/polyntt/params.py | 8 + assets/eip-NTT/pythonref/polyntt/poly.py | 144 +++++++++ .../polyntt/scripts/generate_ntt_constants.py | 126 ++++++++ .../polyntt/scripts/generate_test_vectors.py | 125 ++++++++ .../scripts/generate_test_vectors_solidity.py | 105 +++++++ .../pythonref/polyntt/tests/__init__.py | 0 .../polyntt/tests/test_ntt_iterative.py | 41 +++ .../polyntt/tests/test_ntt_recursive.py | 39 +++ .../pythonref/polyntt/tests/test_poly.py | 119 ++++++++ .../pythonref/polyntt/tests/test_utils.py | 20 ++ .../pythonref/polyntt/tests/test_vectors.py | 49 +++ assets/eip-NTT/pythonref/polyntt/utils.py | 90 ++++++ assets/eip-NTT/pythonref/setup.py | 8 + assets/eip-NTT/solidity/src/ZKNOX_NTT.sol | 288 ++++++++++++++++++ assets/eip-NTT/solidity/test/ZKNOX_NTT.t.sol | 160 ++++++++++ .../test/ZKNOX_NTT_DilithiumField.t.sol | 150 +++++++++ 27 files changed, 2328 insertions(+) create mode 100644 EIPS/eip-ntt.md create mode 100644 assets/eip-NTT/README.md create mode 100644 assets/eip-NTT/pythonref/README.md create mode 100644 assets/eip-NTT/pythonref/makefile create mode 100644 assets/eip-NTT/pythonref/polyntt/__init__.py create mode 100644 assets/eip-NTT/pythonref/polyntt/bench_iterative_recursive.py create mode 100755 assets/eip-NTT/pythonref/polyntt/ntt.py create mode 100644 assets/eip-NTT/pythonref/polyntt/ntt_constants_iterative.py create mode 100644 assets/eip-NTT/pythonref/polyntt/ntt_constants_recursive.py create mode 100755 assets/eip-NTT/pythonref/polyntt/ntt_iterative.py create mode 100755 assets/eip-NTT/pythonref/polyntt/ntt_recursive.py create mode 100644 assets/eip-NTT/pythonref/polyntt/params.py create mode 100755 assets/eip-NTT/pythonref/polyntt/poly.py create mode 100644 assets/eip-NTT/pythonref/polyntt/scripts/generate_ntt_constants.py create mode 100644 assets/eip-NTT/pythonref/polyntt/scripts/generate_test_vectors.py create mode 100644 assets/eip-NTT/pythonref/polyntt/scripts/generate_test_vectors_solidity.py create mode 100644 assets/eip-NTT/pythonref/polyntt/tests/__init__.py create mode 100644 assets/eip-NTT/pythonref/polyntt/tests/test_ntt_iterative.py create mode 100644 assets/eip-NTT/pythonref/polyntt/tests/test_ntt_recursive.py create mode 100644 assets/eip-NTT/pythonref/polyntt/tests/test_poly.py create mode 100755 assets/eip-NTT/pythonref/polyntt/tests/test_utils.py create mode 100644 assets/eip-NTT/pythonref/polyntt/tests/test_vectors.py create mode 100644 assets/eip-NTT/pythonref/polyntt/utils.py create mode 100644 assets/eip-NTT/pythonref/setup.py create mode 100644 assets/eip-NTT/solidity/src/ZKNOX_NTT.sol create mode 100644 assets/eip-NTT/solidity/test/ZKNOX_NTT.t.sol create mode 100644 assets/eip-NTT/solidity/test/ZKNOX_NTT_DilithiumField.t.sol diff --git a/EIPS/eip-ntt.md b/EIPS/eip-ntt.md new file mode 100644 index 00000000000000..97d7bffc47dcf7 --- /dev/null +++ b/EIPS/eip-ntt.md @@ -0,0 +1,264 @@ +--- +title: +description: +author: +discussions-to: +status: Draft +type: +category: # Only required for Standards Track. Otherwise, remove this field. +created: <2025-02-12> +requires: # Only required when you reference an EIP in the `Specification` section. Otherwise, remove this field. +--- + + + +## Abstract + +This proposal creates a precompiled contract that performs NTT and Inverse NTT transformations. This provides a way to have efficient and fast polynomial multiplication for Post Quantum and Starks applications. + +## Motivation + +With the release of Willow cheap, the concern for quantum threat against Ethereum accelerated. Today ECDSA is the EOA signature algorithms, which is prone to quantum computing. Efficient replacement algorithms use polynomial multiplication as the core operation. Once NTT and Inverse NTT are available, the remaining of the verification algorithm is trivial. Chosing to integrate NTT and InvNTT instead of a specific algorithm provides agility, as DILITHIUM or FALCON or any equivalent can be implemented with a modest cost from those operators. NTT is also of interest to speed-up STARK verifiers. This single operator would thus benefit to both the Ethereum scaling and Post Quantum threat mitigation. + + + +## Specification + +### Constants + +| Name | Value | Comment | +|---------------------|-------|--------------------| +| NTT_FW | 0x0f | precompile address | +| NTT_INV | 0x10 | precompile address | +| NTT_VECMULMOD | 0x11 | precompile address | +| NTT_VECADDMOD | 0x12 | precompile address | + +We introduce *four* separate precompiles to perform the following operations: + +- NTT_FW - to perform the forward NTT transformation (Negative wrap convolution) with a gas cost of `600` gas +- NTT_INV - to perform the inverse NTT transformation (Negative wrap convolution) with a gas cost of `600` gas +- NTT_VECMULMOD - to perform vectorized modular multiplication with a gas cost formula defined in the corresponding section +- NTT_VECADDMOD - to perform vectorized modular addition with a gas cost formula defined in the corresponding section + + +### Field parameters + +The NTT_FW and NTT_INV are fully defined by the following set of parameters. +Let R be the ring of cyclotomic integers, i.e $R=Z[X]/X^n+1 \mod q $, where +- $n$ is the degree +- $q$ is the field characteristic such that $q=1 \mod 2n$ +- $\omega$ is a n-th root of unity in $\mathbb Z_q$ i.e $\omega^n=1 \mod q$ +- $\psi$ is a n-th root of unity in $\mathbb Z_q$ i.e $\psi^n=1 \mod q$ + +Any element $a \in R$ is a polynomial of degree at most $n − 1$ with integer coefficients, written +as $a=\sum_{i=0}^{n-1} aiX^i$ + + +### NTT_FW +The NTT transformation is described by the following algorithm. + +**Input:** A vector $a = (a[0], a[1], \dots, a[n-1]) \in \mathbb{Z}_q^n$ in standard ordering, where $q$ is a prime such that $q \equiv 1 \mod 2n$ and $n$ is a power of two, and a precomputed table $\Psi_{rev} \in \mathbb{Z}_q^n$ storing powers of $\psi$ in bit-reversed order. + +**Output:** $a \leftarrow \text{NTT}(a)$ in bit-reversed ordering. + +```plaintext +t ← n +for m = 1 to n-1 by 2m do + t ← t / 2 + for i = 0 to m-1 do + j1 ← 2 ⋅ i ⋅ t + j2 ← j1 + t - 1 + S ← Ψrev[m + i] + for j = j1 to j2 do + U ← a[j] + V ← a[j + t] ⋅ S + a[j] ← (U + V) mod q + a[j + t] ← (U - V) mod q + end for + end for +end for +return a +``` + +### NTT_INV +The Inverse NTT is described by the following algorithm. + + + +Input: A vector $a = (a[0], a[1], \dots, a[n-1]) \in \mathbb{Z}q^n$ in bit-reversed ordering, where $q$ is a prime such that $q \equiv 1 \mod 2n$ and $n$ is a power of two, and a precomputed table $\Psi^{-1}{rev} \in \mathbb{Z}_q^n$ storing powers of $\psi^{-1}$ in bit-reversed order. + +Output: $a \leftarrow \text{INTT}(a)$ in standard ordering. + +```plaintext +t ← 1 +for m = n to 1 by m/2 do + j1 ← 0 + h ← m / 2 + for i = 0 to h-1 do + j2 ← j1 + t - 1 + S ← Ψ⁻¹rev[h + i] + for j = j1 to j2 do + U ← a[j] + V ← a[j + t] + a[j] ← (U + V) mod q + a[j + t] ← (U - V) ⋅ S mod q + end for + j1 ← j1 + 2t + end for + t ← 2t +end for +for j = 0 to n-1 do + a[j] ← a[j] ⋅ n⁻¹ mod q +end for +return a +``` + + +### NTT_VECMULMOD + +The NTT_VECMULMOD is similar to SIMD in the functionning, but operates with larger sizes it takes as input: +- q is the +- n the bit size of q, coded over the smallest integer in {16, 32, 64, 128, 256} +- x the length of the vector of operands. x is a power of 2 in {4, 8, 16, 32, 64, 128,256, 512} +- a the first operand of x elements of size n +- b the second operand of x elements of size n + +and computes the element-wise products mulmod(ai,bi,q) . +The gas cost of the operation is $\log_2(x).n \over 8$ + + +### NTT_VECADDMOD + +The Inverse NTT is described by the following algorithm. + +The NTT_VECMULMOD is similar to SIMD in the functionning, but operates with larger sizes it takes as input: +- q is the +- n the bit size of q, coded over the smaller integer in {16, 32, 64, 128, 256} +- x the size of the vector of operands. x is a power of 2 in {16, 32, 64, 128,512} +- a the first operand of x elements of size n +- b the second operand of x elements of size n + +and computes the element-wise addition addmod(ai,bi,q) . +The gas cost of the operation is $\log_2(x).n \over 32$ + +## Rationale + +If $f$ and $g$ are two polynomials of $R$, then $fg$=NTT_INV(NTT_VECMULMOD(NTT_FW(a), NTT_FW(b))) is equal to the product of f and g in R. +The algorithm has a complexity of $n \log n$ rather than $n^2$ of the classical schoolbook multiplication. + + + +### Fields of interest + +- FALCON: $q=3.2^{12}+1$ +- DILITHIUM: $q=1023.2^{13}+1$ +- KYBER: $q=13.2^8+1$ +- Babybear: $q=15.2^{27}+1$ (Risc0) +- Goldilocks: $q=2^{64}-2^{32}+1$ (Polygon's Plonky2) +- M31: $q=2^{31}-1$ (Circle STARKS, STwo, Plonky3) +- StarkCurve: $q=2^{251}+17.2^{192}+1$ + + +### Benchmarks + +#### Pure solidity + +To illustrate the interest of the precompile, the assets provide the measured gas const for a single NTT and extrapolates the minimal gas cost taking into account the required number of NTT_FW and NTT_INV. The provided assets use pure Yul optimizations, with memory access hacks. It is unlikely that more than one order of magnitude could be spared on such a minimal code. + +|Use case| Parameters | single NTT gas cost | Required NTT(FW/INV) | Estimated NTT/Full cost | +|--|------------------------|---------------------|---------------------|---| +|Falcon| $q=512, n=512$ | 1.8 M | 1 NTTFW+1 NTTINV |3.6 M| +|Dilithium| $q=1023.2^{13}+1, n=256$| 460K | 4 NTTFW +1 NTTINV|2.3M| + +Falcon cost has been measured over a full implementation and is compliant to the estimation. Dilithium cost is evaluated assuming + +This demonstrates that using pure solidity enables cheap L2s to experiment with FALCON from now, but is to expensive for L1. +This numbers are reduced to 1500 gas when this EIP is adopted. +Adopting the Hash function as a separate EIP would enable a gas verification cost of 2000 gas. +This is in line with the ratio looking at SUPERCOP implementations. + + + + + +## Backwards Compatibility +There are no backward compatibility questions. + + +## Test Cases +There is no edge cases in the considered operations. + + + +## Reference Implementation + + +There are two fully spec compatible implementations on the day of writing: +- a python reference code provided in the [assets]() of this EIP +- a solidity reference code provided in the [assets]() of this EIP +Both codes have been validated over a large base of reference vectors, and implementing both FALCON and DILITHIUM algorithms as demonstration of the usefulness of the precompile. + + + +## Security Considerations + + + +Needs discussion. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/assets/eip-NTT/README.md b/assets/eip-NTT/README.md new file mode 100644 index 00000000000000..8567a414a20f04 --- /dev/null +++ b/assets/eip-NTT/README.md @@ -0,0 +1,97 @@ +# NTT-EIP as a building block for FALCON, DILITHIUM and Stark verifiers + +This repository contains the EIP for NTT transform, along with a python reference code, and a solidity implementation. + +## Context + +### The threat +With the release of Willow cheap, the concern for quantum threat against Ethereum seems to accelerate. Post by [Asanso](https://ethresear.ch/t/so-you-wanna-post-quantum-ethereum-transaction-signature/21291) and [PMiller](https://ethresear.ch/t/tidbits-of-post-quantum-eth/21296) summarize those stakes and possible solutions. Those solutions include use of lattice based signatures such as Dillithium or FALCON (the latter being more optimized for onchain constraints), STARKs and FHE. There is a consensus in the cryptographic research community around lattices as the future of asymetric protocols, and STARKs won the race for ZKEVMs implementation (as used by Scroll, Starknet and ZKsync). + +Those protocols have in common to require fast polynomial multiplication over prime fields, and use NTT (a special [FFT](https://vitalik.eth.limo/general/2019/05/12/fft.html) adapted to prime fields). While in the past Montgomery multipliers over elliptic curve fields were the critical target of optimizations (both hardware and software), NTT optimization is the key to a performant PQ implementation. + +### Discussion + +In the past Ethereum chose specificity by picking secp256k1 as its sole candidate for signature. Later, after dedicated hardware and proving systems working on other hardwares were realeased, a zoo of EIP flourished to propose alternative curves. There where attempt to have higher level EIPs to enable all of those at once, such as EWASM, SIMD, EVMMAX, or RIP7696 (by decreasing order of genericity and complexity). + +Picking NTT as EIP instead of a given scheme would provide massive gas cost reduction for all schemes relying on it. +- **pros** : massive reduction to all cited protocols, more agility for evolutions. +- **cons**: requires to be wrapped into implementations, not optimal for a given target compared to dedicated EIP, not stateless. + +## Overview + +The NTT operates on sequences of numbers (often coefficients of polynomials) in a modular arithmetic system. It maps these sequences into a different domain where convolution operations (e.g., polynomial multiplication) become simpler and faster, akin to how FFT simplifies signal convolution. Compared to FFT which uses root of unity in complex plane, NTT uses roots of unity in a finite field or ring. + +The NTT is based on the Discrete Fourier Transform (DFT), defined as: +$$ +X[k] = \sum_{j=0}^{N-1} x[j] \cdot \omega^{j \cdot k} \mod q$$ + +Where: +- $x[j]$: Input sequence of length N. +- $X[k]$: Transformed sequence, +- $q$: A prime modulus, +- $\omega$: A primitive N-th root of unity modulo $q$, with +$\omega^N \equiv 1 \mod q \quad \text{and} \quad \omega^k \not\equiv 1 \mod q \; \forall \; 0 < k < N$ + +NTT computation uses the a similar approach as Cooley-Tukey algorithm to provide a O(N log N) complexity. The NTT algorithm transforms a sequence $(x[j])$ to $(X[k])$ using modular arithmetic. It is invertible, allowing reconstruction of the original sequence via the Inverse NTT (INTT). The inverse process is similar but requires dividing by \(N\) (mod \(q\)) and using $(\omega^{-1}$) (the modular inverse of $\omega$). The following algorithm is extracted from +[[LN16]](https://eprint.iacr.org/2016/504.pdf), and describe how to compute the NTT when $R_q= \mathbb{Z}_q[X]/X^n+1$ (Negative Wrap Convolution). + +![alt text](image.png) + +The Inverse NTT is computed through the following algorithm: + +![alt text](image-1.png) + +## Benchmarks + +### Python + +| Field | $n$ | Recursive NTT (Tetration) | Iterative NTT (ZKNox) | Iterative InvNTT (ZKNox)| +|-|-|-|-|-| +|Falcon | 512 | 761 μs | 528 μs | 561 μs | +|Falcon | 1024 | 1642 μs | 1076 μs | 1199 μs | +|Dilithium| 128 | 165 μs | 114 μs | 113 μs | +|Dilithium| 256 | 371 μs | 258 μs | 260 μs | +|BabyBear | 256 | 531 μs | 389 μs | 404 μs | + +The recursive inverse NTT is very costly because of the required inversions. For Falcon, the field is small enough so that field inversions can be precomputed, but the cost is still higher than the iterative inverse NTT. +The field arithmetic has not been optimized. In the case of BabyBear, this becomes significant and so the comparison is not really significant. + +### Solidity + + +| Function | Description | gas cost | Tests Status | +|------------------------|---------------------|---------------------|---------------------| +| NTT recursive | original gas cost from [falcon-solidity](https://github.com/Tetration-Lab/falcon-solidity/blob/main/src/Falcon.sol) | 6.9M | OK| +| InvNTT recursive | original gas cost from [falcon-solidity](https://github.com/Tetration-Lab/falcon-solidity/blob/main/src/Falcon.sol) | 7.8M | OK| +| Full Falcon verification | original gas cost from [falcon-solidity](https://github.com/Tetration-Lab/falcon-solidity/blob/main/src/Falcon.sol) | 24 M| OK| +| NTT iterative | ZKNOX | 4M | OK| +| InvNTT iterative | ZKNOX | 4.2M | OK| +| Full Falcon verification | ZKNOX | 8.5 M| OK| + + +### Yul + + +Further optimizations are reached by using Yul for critical sections and using the CODECOPY and EXTCODECOPY trick detailed in of [[RD23]](https://eprint.iacr.org/2023/939.pdf) (section 3.3, "Hacking EVM memory access cost"). + + +| Function | Description | gas cost | Tests Status | +|------------------------|---------------------|---------------------|---------------------| +| ntt.NTTFW | ZKNOX_NTTFW, iterative yuled | 1.9M | OK| +| falcon.verify_opt | Full falcon verification with precomputated pubkey | 3.6M | OK| + +### Go Ethereum (WIP) + +ZKNOX is planning a client implementation for node of the considered EIP. + +## Conclusion + +We provided an optimized version of FALCON, using an optimized version of NTT. This code can be used to speed up Stark verification as well as other lattices primitives (Dilithium, Kyber, etc.). While it seems achievable to use FALCON as a progressive precompile, the cost remains very high. Using a client implementation with NTT-EIP (in a Geth fork for example), ETHEREUM could become from a PQ-Friendly and ZK-Friendly chain. This work is supported by the Ethereum Foundation. + + +## References + +- [[LN16]](https://eprint.iacr.org/2016/504.pdf) Speeding up the Number Theoretic Transform for Faster Ideal Lattice-Based Cryptography. Patrick Longa, Michael Naehrig. +- [[EIP616]](https://eips.ethereum.org/EIPS/eip-616) EIP-616: SIMD Operations for the EVM. Greg Colvin. +- [[RD23]](https://eprint.iacr.org/2023/939.pdf) Speeding up elliptic computations for Ethereum Account Abstraction. Renaud Dubois. +- [[DILITHIUM]](https://eprint.iacr.org/2017/633.pdf) CRYSTALS-Dilithium: A Lattice-Based Digital Signature Scheme. Léo Ducas, Eike Kiltz, Tancrède Lepoint, Vadim Lyubashevsky, Peter Schwabe, Gregor Seiler and Damien Stehlé. \ No newline at end of file diff --git a/assets/eip-NTT/pythonref/README.md b/assets/eip-NTT/pythonref/README.md new file mode 100644 index 00000000000000..39eac28122c062 --- /dev/null +++ b/assets/eip-NTT/pythonref/README.md @@ -0,0 +1,28 @@ +# NTT +Generic implementation of the Number Theoretic Transform in the context of cryptography applications. + +We provide tests for various NTT-friendly rings, including Falcon's ring with `q = 12*1024+1` and the defining polynomial `x¹⁰²⁴+1`. + +The implementation requires the file `ntt_constants.py`, generated using `python generate_constants.py`. + +## Install +``` +make install +``` + +## Tests +For running all tests: +``` +make test +``` +For running a specific test, use: +``` +make test TEST=test_ntt_recursive.TestNTTRecursive.test_ntt_intt +``` + +## Benchmarks +For running the benchmarks: +``` +make bench +``` +Note that the field arithmetic is not optimized. For example, Montgomery multiplication is not implemented here. \ No newline at end of file diff --git a/assets/eip-NTT/pythonref/makefile b/assets/eip-NTT/pythonref/makefile new file mode 100644 index 00000000000000..c4f0f9f24483ef --- /dev/null +++ b/assets/eip-NTT/pythonref/makefile @@ -0,0 +1,28 @@ +PY = python3 +VENV = myenv +PIP = $(VENV)/bin/pip +PYTHON = $(VENV)/bin/python +AUX = *.pyc *.cprof */*.pyc + +install: + $(PY) -m venv $(VENV) + $(PIP) install pycryptodome + +generate_ntt_constants: + $(PYTHON) -m polyntt.scripts.generate_ntt_constants + +generate_test_vectors: + $(PYTHON) -m polyntt.scripts.generate_test_vectors + $(PYTHON) -m polyntt.scripts.generate_test_vectors_solidity + +test: generate_test_vectors + $(PYTHON) -m unittest $(if $(TEST),polyntt.tests.$(TEST),discover -s polyntt.tests) -v + +bench: + $(PYTHON) -m polyntt.bench_iterative_recursive + +clean: + rm -f $(AUX) + rm -rf __pycache__ */__pycache__ + rm -rf scripts/*.sage.py + @echo "Clean done" diff --git a/assets/eip-NTT/pythonref/polyntt/__init__.py b/assets/eip-NTT/pythonref/polyntt/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/assets/eip-NTT/pythonref/polyntt/bench_iterative_recursive.py b/assets/eip-NTT/pythonref/polyntt/bench_iterative_recursive.py new file mode 100644 index 00000000000000..f44bd9ecb5b14e --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/bench_iterative_recursive.py @@ -0,0 +1,68 @@ +from time import time +from polyntt.scripts.generate_test_vectors import deterministic_poly +from polyntt.poly import Poly +from polyntt.params import PARAMS + + +class BenchIterativeRecursive: + def bench_ntt(iterations): + print("Bench NTT") + print("({} iterations)".format(iterations)) + + print("\tq\tn\tRecursive\tIterative") + for (q, two_adicity) in PARAMS: + + # for two sizes of polynomials + for n in [1 << (two_adicity-2), 1 << (two_adicity-1)]: + print("{:10.0f}\t{}".format(q, n), end='\t') + p1 = Poly(deterministic_poly(q, n), q, 'NTTRecursive') + T = p1.NTT + tmp = p1.ntt() + t1 = time() + for i in range(iterations): + tmp = T.ntt(tmp) + t2 = time() + print("{:.0f} μs".format( + (t2-t1) * 10**6/iterations), end='\t\t') + + p1 = Poly(deterministic_poly(q, n), q, 'NTTIterative') + T = p1.NTT + tmp = p1.ntt() + t3 = time() + for i in range(iterations): + tmp = T.ntt(tmp) + t4 = time() + print("{:.0f} μs".format((t4-t3) * 10**6/iterations)) + + def bench_intt(iterations): + print("Bench INTT") + print("({} iterations)".format(iterations)) + + print("\tq\tn\tRecursive\tIterative") + for (q, two_adicity) in PARAMS: + + # for two sizes of polynomials + for n in [1 << (two_adicity-2), 1 << (two_adicity-1)]: + print("{:10.0f}\t{}".format(q, n), end='\t') + p1 = Poly(deterministic_poly(q, n), q, 'NTTRecursive') + T = p1.NTT + tmp = p1.ntt() + t1 = time() + for i in range(iterations): + tmp = T.intt(tmp) + t2 = time() + print("{:.0f} μs".format( + (t2-t1) * 10**6/iterations), end='\t\t') + + p1 = Poly(deterministic_poly(q, n), q, 'NTTIterative') + T = p1.NTT + tmp = p1.ntt() + t3 = time() + for i in range(iterations): + tmp = T.intt(tmp) + t4 = time() + print("{:.0f} μs".format((t4-t3) * 10**6/iterations)) + + +BenchIterativeRecursive.bench_ntt(1000) +BenchIterativeRecursive.bench_intt(1000) diff --git a/assets/eip-NTT/pythonref/polyntt/ntt.py b/assets/eip-NTT/pythonref/polyntt/ntt.py new file mode 100755 index 00000000000000..ac126afed94d0a --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/ntt.py @@ -0,0 +1,42 @@ +from polyntt.utils import batch_modular_inversion + + +class NTT(): + """Base class for Number Theoretic Transform""" + + def __init__(self, q): + """Implements Number Theoretic Transform for fast polynomial multiplication.""" + self.q = q + # ratio between degree n and number of complex coefficients of the NTT + # while here this ratio is 1, it is possible to develop a short NTT such that it is 2. + self.ntt_ratio = 1 + + def ntt(self, poly): + raise NotImplementedError("Subclasses must implement NTT") + + def intt(self, poly): + raise NotImplementedError( + "Subclasses must implement inverse NTT") + + def vec_add(self, f_ntt, g_ntt): + """Addition of two polynomials (NTT representation).""" + return [(x+y) % self.q for (x, y) in zip(f_ntt, g_ntt)] + + def vec_sub(self, f_ntt, g_ntt): + """Substraction of two polynomials (NTT representation).""" + return self.vec_add(f_ntt, [(-x) % self.q for x in g_ntt]) + + def vec_mul(self, f_ntt, g_ntt): + """Multiplication of two polynomials (NTT representation).""" + assert len(f_ntt) == len(g_ntt) + deg = len(f_ntt) + return [(f_ntt[i] * g_ntt[i]) % self.q for i in range(deg)] + + def vec_div(self, f_ntt, g_ntt): + """Division of two polynomials (NTT representation).""" + assert len(f_ntt) == len(g_ntt) + deg = len(f_ntt) + if any(elt == 0 for elt in g_ntt): + raise ZeroDivisionError + inv_g_ntt = batch_modular_inversion(g_ntt, self.q) + return self.vec_mul(f_ntt, inv_g_ntt) diff --git a/assets/eip-NTT/pythonref/polyntt/ntt_constants_iterative.py b/assets/eip-NTT/pythonref/polyntt/ntt_constants_iterative.py new file mode 100644 index 00000000000000..36e7910770666b --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/ntt_constants_iterative.py @@ -0,0 +1,91 @@ +# File generated with `python polyntt/generate_ntt_constants.py`. +# Precomputations for NTT. + +# Dictionary containing the powers ψ, a 2^n-th root of unity. +ψ = { + # ψ = 3296, ψ has multiplicative order 128. + 3329 : [1, 3296, 1089, 682, 797, 331, 2393, 927, 2699, 816, 3033, 3110, 569, 1197, 447, 1894, 749, 1915, 56, 1481, 1062, 1573, 1355, 1891, 848, 1977, 1339, 2419, 69, 1052, 1903, 452, 1729, 2865, 1996, 712, 3136, 3040, 2879, 1534, 2642, 2697, 882, 855, 1746, 2304, 535, 2319, 40, 2009, 283, 648, 1919, 3253, 2508, 461, 1432, 2679, 1476, 1227, 2786, 1274, 1235, 2522], + # ψ = 1826, ψ has multiplicative order 2048. + 12289 : [1, 1826, 3957, 11839, 1663, 1255, 5876, 1279, 544, 10224, 2033, 980, 7575, 6825, 1404, 7592, 1000, 7228, 12231, 4693, 3985, 1522, 1858, 944, 3284, 11841, 5315, 9169, 4976, 4605, 3054, 9687, 4591, 2068, 3445, 10891, 3364, 10453, 2361, 10036, 2837, 6693, 6152, 1406, 11244, 8914, 6328, 3268, 7203, 3448, 4080, 2946, 9103, 7350, 1512, 8176, 10530, 7784, 7500, 5054, 11854, 4475, 11454, 11415, 1646, 7080, 52, 8929, 9140, 1178, 453, 3815, 10616, 5063, 3710, 3221, 7404, 1804, 652, 10808, 11563, 1536, 2844, 7186, 9273, 10545, 10596, 5410, 10593, 12221, 11011, 1282, 6022, 9806, 683, 5969, 11340, 12164, 5241, 9224, 7094, 1038, 2882, 2840, 12171, 5734, 56, 3944, 390, 11667, 7105, 8835, 9542, 10179, 5886, 7250, 3247, 5724, 6374, 1241, 4890, 7326, 6844, 11520, 9041, 4739, 1958, 11498, 5736, 3708, 11858, 11779, 2704, 9615, 8298, 12100, 11267, 1756, 11316, 5207, 8585, 7735, 4049, 7785, 9326, 9011, 11404, 6138, 420, 5002, 2925, 7624, 10276, 10962, 10120, 8753, 7278, 5219, 5919, 6063, 10938, 3163, 12097, 5789, 2174, 377, 218, 4820, 2396, 212, 6153, 3232, 2912, 8464, 7991, 4523, 790, 4727, 4624, 881, 11136, 8330, 9087, 2712, 11934, 3087, 8500, 12282, 11796, 9168, 3150, 648, 3504, 8024, 3336, 8481, 2166, 10347, 5429, 8420, 1381, 2461, 8301, 5289, 10849, 406, 4016, 8972, 1635, 11572, 5681, 1590, 3136, 11951, 9551, 2035, 4632, 3200, 5925, 4730, 10102, 463, 9786, 1030, 563, 8051, 3482, 4719, 2305, 6092, 2447, 7315, 11336, 4860, 1702, 11024, 442, 8307, 3956, 10013, 9995, 1705, 4213, 24, 6957, 8945, 1489, 3045, 5542, 5845, 6118, 767, 11885, 11925, 11231, 9754, 4043, 9118, 10162, 11711, 1426, 10897, 2031, 9617, 11950, 7725, 10367, 5082, 1537, 4670, 11143, 8823, 12208, 11851, 11286, 11872, 476, 8946, 3315, 7002, 5092, 7508, 7373, 6643, 875, 180, 9166, 11787, 5023, 4404, 4698, 826, 9018, 11897, 9259, 9559, 4354, 11710, 11889, 6940, 2481, 7954, 10695, 1849, 9088, 4538, 3602, 2637, 10163, 1248, 5383, 10447, 3694, 10872, 5537, 9004, 10911, 3017, 3570, 5650, 6429, 3359, 1323, 7154, 12286, 6811, 418, 1350, 7300, 8524, 6950, 8452, 10657, 6195, 6190, 9349, 1853, 4103, 8077, 1802, 9289, 2894, 174, 10499, 334, 7723, 6715, 9457, 2437, 1344, 8633, 9360, 9650, 10763, 3127, 7806, 10805, 6085, 1954, 4194, 2197, 5508, 5206, 6759, 3778, 4499, 6122, 8071, 3135, 10125, 5594, 2485, 2969, 1945, 49, 3451, 9558, 2528, 7753, 50, 5277, 1226, 2078, 9416, 1305, 11153, 2505, 2622, 7351, 3338, 12133, 10080, 9447, 8755, 10930, 844, 5019, 9389, 1159, 2626, 2366, 6877, 10333, 4443, 2178, 7681, 3757, 3020, 9048, 5232, 5079, 8348, 5088, 204, 3834, 8443, 6512, 7449, 10240, 6671, 2847, 375, 8855, 9195, 3296, 9175, 3643, 3769, 354, 7376, 12121, 457, 11119, 1866, 3263, 10362, 8241, 6330, 6920, 2828, 2548, 7406, 5456, 8566, 9908, 2600, 4046, 2307, 9744, 10361, 6415, 2373, 7370, 1165, 1293, 1530, 4177, 8022, 11973, 567, 3066, 7021, 2919, 8957, 11112, 1373, 142, 1223, 8889, 9834, 2655, 6164, 11029, 9572, 3514, 1706, 6039, 3981, 6507, 10608, 2744, 8921, 6821, 6389, 4053, 2800, 576, 7211, 5767, 11158, 11635, 10118, 5101, 11653, 6119, 2593, 3553, 11475, 605, 11009, 9919, 10397, 10706, 9646, 3459, 11877, 9606, 4153, 1065, 3028, 11367, 21, 1479, 9363, 2839, 10345, 1777, 506, 2281, 11424, 5791, 5826, 8291, 11607, 8146, 4906, 11964, 8711, 4320, 11071, 241, 9951, 7384, 2151, 7535, 7519, 2881, 1014, 8214, 6184, 10682, 2689, 6803, 10388, 6561, 10900, 7509, 9199, 10600, 425, 1843, 10421, 5374, 6302, 4948, 2633, 2859, 9998, 7183, 3795, 10963, 11946, 421, 6828, 6882, 7174, 11939, 12217, 3707, 10032, 7822, 3154, 7952, 7043, 6224, 9988, 1212, 1092, 3174, 7605, 160, 9513, 6381, 1734, 8011, 4176, 6196, 8016, 1017, 1403, 5766, 9332, 7678, 10568, 3438, 10398, 243, 1314, 3009, 1251, 10861, 10029, 2344, 3572, 9302, 2054, 2459, 4649, 9664, 11749, 9369, 1506, 9509, 11366, 10484, 9811, 9813, 1176, 9090, 8190, 11516, 1737, 1200, 3758, 4846, 716, 4782, 6742, 9603, 10964, 1483, 4378, 6378, 8545, 8429, 5526, 1207, 4251, 7967, 9855, 4134, 3238, 1579, 7628, 5291, 2212, 8320, 3116, 9, 4145, 11035, 8239, 2678, 11295, 3728, 11511, 4896, 5993, 6008, 8820, 6730, 12269, 347, 6883, 9000, 3607, 11767, 5370, 11287, 1409, 4433, 8496, 4978, 8257, 10968, 8787, 7917, 4578, 2908, 1160, 4452, 6323, 6427, 11996, 5698, 8054, 8960, 4301, 955, 11081, 6212, 365, 2884, 6492, 7796, 4834, 3382, 6454, 12142, 1936, 8193, 4705, 1319, 12139, 8747, 8611, 6055, 8619, 8374, 3408, 4774, 4423, 2525, 2275, 468, 6627, 8526, 10602, 4077, 9757, 9521, 8700, 8812, 4411, 5191, 3947, 5868, 11249, 5755, 1535, 1018, 3229, 9723, 8882, 9341, 11823, 9314, 11677, 787, 11538, 5042, 2231, 6147, 4565, 3748, 11164, 10302, 9282, 2401, 9342, 1360, 982, 11227, 2450, 504, 10918, 3510, 6691, 2500, 5781, 12144, 5588, 3818, 3805, 4645, 2360, 8210, 11169, 7143, 4489, 151, 5368, 7635, 5784, 5333, 5170, 2468, 8794, 8410, 7699, 12047, 512, 948, 10588, 3091, 3515, 3532, 9996, 3531, 8170, 11863, 8620, 10200, 7365, 4324, 6086, 3780, 8151, 1747, 7171, 6461, 346, 5057, 5043, 4057, 10104, 4115, 5411, 130, 3889, 10561, 2945, 7277, 3393, 1962, 6513, 9275, 1908, 6221, 4510, 1630, 2442, 10474, 3840, 7110, 5676, 4749, 7929, 1912, 1236, 8049, 12119, 9094, 3205, 2766, 12226, 7852, 8778, 3772, 5832, 6958, 10771, 5446, 2595, 7205, 7100, 11994, 2046, 140, 9860, 975, 10734, 11618, 3654, 11566, 7014, 2426, 5836, 1973, 2021, 3646, 9247, 12225, 6026, 4821, 4222, 4169, 5703, 4895, 4167, 2051, 9270, 5067, 11014, 6760, 5604, 8456, 5672, 9734, 4390, 3712, 6873, 3029, 904, 3978, 1029, 11026, 4094, 3932, 3056, 1050, 216, 1168, 6771, 1112, 2827, 722, 3449, 5906, 6903, 8653, 9013, 2767, 1763, 11809, 8328, 5435, 7087, 545, 12050, 5990, 530, 9238, 8080, 7280, 8871, 1544, 5163, 1975, 5673, 11560, 8347, 3262, 8536, 4284, 6780, 5257, 1573, 8961, 6127, 4912, 10631, 7875, 1620, 8760, 7771, 8340, 2769, 5415, 7434, 7428, 8761, 9597, 8, 2319, 7078, 8689, 1015, 10040, 10141, 10232, 4352, 8058, 3975, 7840, 11444, 5444, 11232, 11580, 8000, 8668, 11825, 677, 7302, 12176, 2575, 7552, 1694, 8705, 5653, 11907, 2941, 12262, 12143, 3762, 12150, 4255, 2982, 1105, 2334, 9890, 6599, 6554, 10407, 4388, 60, 11248, 3929, 9867, 1468, 1566, 8468, 3006, 8062, 11279, 11379, 9644, 12096, 3963, 10506, 827, 10844, 3565, 8809, 11222, 5609, 5297, 879, 7484, 416, 9987, 11675, 9424, 3624, 5942, 11194, 3637, 5102, 1190, 10076, 2143, 5216, 441, 6481], + # ψ = 1538055801, ψ has multiplicative order 1024. + 2013265921 : [1, 1538055801, 65325759, 1498411975, 1453957774, 1079510470, 1887204546, 1652297787, 1881002012, 699963264, 499191403, 1693022490, 1337919487, 966894100, 1163641283, 1298988134, 918899846, 902670107, 1260838569, 1301810925, 1019558016, 801741323, 499211237, 473278611, 465770369, 638480448, 941078395, 1226580279, 1093861978, 634315427, 1223962526, 891397885, 740045640, 947511624, 1949477615, 1492960486, 46459047, 1005139167, 304241146, 811647064, 1790034783, 616765052, 1894741346, 1854554209, 853920820, 96492575, 1161664892, 1671523028, 406991886, 693003489, 1009148102, 925085853, 1902300049, 101097920, 1238135095, 962732090, 7658004, 359605831, 554611272, 229345643, 967916413, 826566549, 614517289, 1239391225, 567209306, 960902556, 297485602, 180573769, 653926008, 897422076, 1648600907, 694103802, 521584899, 73770957, 217063694, 1202679189, 1934245099, 1975180852, 1191392863, 1614733641, 328902512, 1771189799, 798913693, 790641965, 545609191, 182432197, 116472350, 155500786, 1381260716, 1875241739, 1700772453, 1800181464, 1227468989, 1913630869, 1753498361, 319478536, 1424376889, 946834815, 1151641261, 1764228536, 1765190681, 1468006065, 158345500, 88333380, 465591760, 164840089, 48601359, 840813797, 307689481, 826033367, 554029437, 220989387, 602251207, 996054106, 1366756594, 953846434, 183298559, 954269726, 992414313, 618023419, 1458586410, 1973109411, 651630201, 801533095, 532061447, 1458961756, 1062944807, 848315956, 1801542727, 1773495781, 1496573916, 205759399, 1673043050, 183435231, 995360546, 360188726, 156720578, 1389274075, 648310687, 1121138119, 1646117152, 873795258, 468145086, 994844775, 939236864, 1043129245, 362142462, 736574697, 1696563825, 11377661, 1003192097, 854286761, 1100427872, 1015693157, 955088864, 1277697182, 834684902, 1958833021, 275504490, 27917520, 1561292356, 142732462, 957344008, 1516385965, 1107635944, 747668786, 1029482427, 369272395, 235470082, 910584622, 195805946, 206662642, 1766247011, 420359000, 1027907695, 22390456, 1521113831, 1615428947, 1084160312, 1870933372, 587815148, 978548027, 825263949, 510429024, 603134811, 239994991, 1121491039, 1581583815, 1732600167, 1502690779, 620829980, 1397748829, 612986503, 915974299, 1741454723, 976656163, 1793641395, 1413796675, 90122987, 872185898, 1274926174, 1077735155, 523526901, 22571986, 1046213239, 50919606, 1491193515, 1676670413, 871747792, 1850868284, 45829716, 852402100, 1682376658, 1356277371, 384930484, 1758413169, 1469048545, 1417830607, 152531954, 138908754, 590934102, 791910063, 311597072, 1807183113, 889310574, 90747312, 246465063, 1038386231, 911579644, 1372318971, 470508934, 369650096, 1594590875, 1941222826, 700857190, 953323125, 1296157798, 651991775, 1370897881, 992260941, 291676017, 327499958, 1836561861, 1626389917, 1084539453, 1424628584, 1064048025, 975775959, 1474561152, 294125599, 500320476, 211479835, 878329138, 1666285661, 544357701, 129181676, 284861408, 1041959802, 1665268308, 1373630726, 693603880, 1123251618, 1489450621, 1049785661, 1225971559, 924047256, 1566768539, 25315497, 1560005336, 1057032035, 1397122709, 616122234, 662200255, 1981385565, 1479559300, 386226799, 871478870, 225891607, 270494564, 81349300, 850926282, 121142626, 1690440436, 388031860, 1774958336, 220764936, 1173666668, 1179823440, 1399190761, 1030244776, 1545654860, 1116696117, 790599944, 1778657233, 1223429502, 238550150, 1990050040, 166266897, 184376542, 1997282453, 1034693672, 1886258255, 1609316305, 20030527, 55559234, 1286952490, 1779171120, 36912599, 1100898629, 594576074, 876475152, 1223071487, 1553007845, 485379991, 1040346932, 1462122087, 1675422010, 1515713240, 340017354, 44969099, 1934320121, 1674487280, 1771232847, 748691214, 401423760, 1157466072, 1745735301, 679196388, 1230352802, 811406562, 1694511695, 1168321518, 1391257054, 220012388, 1160239277, 337798486, 680048719, 1804613546, 1098589615, 1248866176, 693716688, 851713125, 191135112, 101134301, 533223002, 1925383489, 532377562, 248573371, 1446699395, 210229806, 1119836042, 638844410, 177390144, 649950427, 560830922, 1079587086, 1089565990, 1671569725, 1750265429, 1112101197, 1296060322, 1011338527, 1637321720, 202622549, 171472309, 1334819145, 819969577, 1507718384, 686375053, 1387569610, 506893846, 1336351010, 1774476878, 355659693, 804607908, 377699504, 397765732, 768747113, 875419223, 958457006, 706618036, 1461280432, 1188041066, 43058684, 420899707, 692518243, 1380244045, 733760284, 512246112, 1816450756, 734062623, 668961676, 189150126, 2001362807, 247608396, 1726888383, 1868457133, 621988538, 676628134, 1601179793, 1171812390, 582615757, 1452112566, 728851248, 531313786, 389284838, 1371888568, 620341062, 1238851905, 124118485, 1626176176, 1691818002, 630736300, 1346078151, 328034906, 194600929, 1240658731, 1813625208, 151590722, 1860705787, 1050632275, 667494993, 198061491, 1146355800, 49104518, 119152282, 971835916, 395270786, 1989594128, 576939764, 1642974608, 1402515894, 95586718, 1082641354, 1800668518, 1341024353, 1431239371, 403207511, 47754187, 815435751, 820688302, 637921271, 567968687, 1261510560, 565387341, 78266780, 1318972766, 1033194287, 1816869661, 546480661, 1645637339, 435219963, 856866068, 842932410, 1951701838, 875812410, 1700600734, 699201851, 1471171080, 1558729249, 725165828, 1334022602, 1922447922, 1602661413, 1724976031, 774513262, 1953043262, 991101839, 1829064104, 40880350, 1817307375, 1032440859, 1229036302, 602499496, 1013135552, 150607448, 702101935, 1179486814, 1410760984, 1584950725, 760005850, 165322140, 1984600303, 1695995145, 1817843431, 333338269, 746672932, 627668726, 112574482, 1164908896, 16303300, 1059646462, 1734697937, 669637055, 53576782, 924491753, 1941224298, 1808101058, 476076165, 419135582, 776161080, 1943498052, 650381673, 448785913, 921820247, 1390334917, 1594073968, 790434535, 1868042710, 953157078, 566445764, 982469450], + # ψ = 2926054, ψ has multiplicative order 512. + 8380417 : [1, 2926054, 6026202, 89301, 6526611, 5767564, 4892034, 27812, 5564778, 770441, 3035980, 6653329, 6308588, 1179613, 2508980, 6444997, 7159240, 3974485, 2778788, 2039144, 6444618, 4399818, 7897768, 2455377, 676590, 7826699, 2391089, 5737437, 5269599, 1994046, 5307408, 3284915, 3201430, 2358373, 6458164, 1354892, 5188063, 5346675, 5130263, 4620952, 5926434, 5360024, 3901472, 4768667, 7986269, 6275131, 4222329, 8321269, 2129892, 6480365, 6067579, 7921677, 1759347, 5011144, 5917973, 3768948, 2663378, 3430436, 4898211, 6084318, 3602218, 7655613, 8106357, 7743490, 4618904, 5365997, 8052569, 5382198, 5034454, 4022750, 1987814, 5926272, 642628, 8165537, 7062739, 6607829, 2156050, 7852436, 3815725, 5720892, 7375178, 303005, 2775755, 2178965, 6663429, 1723229, 7231559, 7744461, 4855975, 7277073, 2579253, 7126227, 7220542, 2491325, 3852015, 3956745, 2883726, 6195333, 6143691, 6161950, 7823561, 5370669, 1736313, 1596822, 3227876, 11879, 5006167, 8111961, 4874037, 2374402, 2983781, 2660408, 3542485, 7648983, 653275, 3472069, 7562881, 2028038, 565603, 3410568, 5010068, 140244, 6018354, 7140506, 3586446, 507927, 5157610, 7198174, 3572223, 392707, 1011223, 2513018, 5463079, 4231948, 4213992, 822541, 2283733, 7835041, 7759253, 2192938, 4361428, 7794176, 6352299, 1753, 557458, 4606686, 5697147, 1879878, 3756790, 2569011, 6852351, 250446, 1335936, 508145, 6125690, 5184741, 6279007, 6903432, 1277625, 4663471, 3145678, 2193087, 4561790, 613238, 2897314, 338420, 5121960, 4423473, 1460718, 1370517, 1226661, 2387513, 928749, 1623354, 1109516, 5607817, 2682288, 7598542, 3467665, 1921994, 3415069, 1163598, 5046034, 5702139, 1674615, 860144, 4197502, 4633167, 5197539, 1834526, 5258977, 4415111, 4614810, 1716814, 348812, 141835, 1858416, 7630840, 3197248, 1009365, 4795319, 5016875, 5919030, 4234153, 3241972, 5637006, 6442847, 1455890, 3764867, 3531229, 7023969, 818761, 3950053, 6764887, 5418153, 3551006, 434125, 3105558, 1787943, 8368000, 4675594, 1393159, 5744944, 6084020, 3201494, 5256655, 6635910, 7070156, 3870317, 5732423, 8145010, 6400920, 1714295, 4385746, 5454601, 3180456, 1095468, 6346610, 5569126, 1780227, 7778734, 1054478, 7921254, 4340221, 3574466, 1665318, 169688, 1685153, 4063053, 1514152, 7080401, 4541938, 5639874, 1187885, 4183372, 87208], +} + +# Dictionary containing the powers of ψ_inv. +ψ_inv = { + # ψ_inv = 807, ψ*ψ_inv = 1. + 3329 : [1, 807, 2094, 2055, 543, 2102, 1853, 650, 1897, 2868, 821, 76, 1410, 2681, 3046, 1320, 3289, 1010, 2794, 1025, 1583, 2474, 2447, 632, 687, 1795, 450, 289, 193, 2617, 1333, 464, 1600, 2877, 1426, 2277, 3260, 910, 1990, 1352, 2481, 1438, 1974, 1756, 2267, 1848, 3273, 1414, 2580, 1435, 2882, 2132, 2760, 219, 296, 2513, 630, 2402, 936, 2998, 2532, 2647, 2240, 33], + # ψ_inv = 5808, ψ*ψ_inv = 1. + 12289 : [1, 5808, 11848, 7073, 10146, 2213, 11099, 7187, 8652, 1095, 6347, 8665, 2865, 614, 2302, 11873, 4805, 11410, 6992, 6680, 1067, 3480, 8724, 1445, 11462, 1783, 8326, 193, 2645, 910, 1010, 4227, 9283, 3821, 10723, 10821, 2422, 8360, 1041, 12229, 7901, 1882, 5735, 5690, 2399, 9955, 11184, 9307, 8034, 139, 8527, 146, 27, 9348, 382, 6636, 3584, 10595, 4737, 9714, 113, 4987, 11612, 464, 3621, 4289, 709, 1057, 6845, 845, 4449, 8314, 4231, 7937, 2057, 2148, 2249, 11274, 3600, 5211, 9970, 12281, 2692, 3528, 4861, 4855, 6874, 9520, 3949, 4518, 3529, 10669, 4414, 1658, 7377, 6162, 3328, 10716, 7032, 5509, 8005, 3753, 9027, 3942, 729, 6616, 10314, 7126, 10745, 3418, 5009, 4209, 3051, 11759, 6299, 239, 11744, 5202, 6854, 3961, 480, 10526, 9522, 3276, 3636, 5386, 6383, 8840, 11567, 9462, 11177, 5518, 11121, 12073, 11239, 9233, 8357, 8195, 1263, 11260, 8311, 11385, 9260, 5416, 8577, 7899, 2555, 6617, 3833, 6685, 5529, 1275, 7222, 3019, 10238, 8122, 7394, 6586, 8120, 8067, 7468, 6263, 64, 3042, 8643, 10268, 10316, 6453, 9863, 5275, 723, 8635, 671, 1555, 11314, 2429, 12149, 10243, 295, 5189, 5084, 9694, 6843, 1518, 5331, 6457, 8517, 3511, 4437, 63, 9523, 9084, 3195, 170, 4240, 11053, 10377, 4360, 7540, 6613, 5179, 8449, 1815, 9847, 10659, 7779, 6068, 10381, 3014, 5776, 10327, 8896, 5012, 9344, 1728, 8400, 12159, 6878, 8174, 2185, 8232, 7246, 7232, 11943, 5828, 5118, 10542, 4138, 8509, 6203, 7965, 4924, 2089, 3669, 426, 4119, 8758, 2293, 8757, 8774, 9198, 1701, 11341, 11777, 242, 4590, 3879, 3495, 9821, 7119, 6956, 6505, 4654, 6921, 12138, 7800, 5146, 1120, 4079, 9929, 7644, 8484, 8471, 6701, 145, 6508, 9789, 5598, 8779, 1371, 11785, 9839, 1062, 11307, 10929, 2947, 9888, 3007, 1987, 1125, 8541, 7724, 6142, 10058, 7247, 751, 11502, 612, 2975, 466, 2948, 3407, 2566, 9060, 11271, 10754, 6534, 1040, 6421, 8342, 7098, 7878, 3477, 3589, 2768, 2532, 8212, 1687, 3763, 5662, 11821, 10014, 9764, 7866, 7515, 8881, 3915, 3670, 6234, 3678, 3542, 150, 10970, 7584, 4096, 10353, 147, 5835, 8907, 7455, 4493, 5797, 9405, 11924, 6077, 1208, 11334, 7988, 3329, 4235, 6591, 293, 5862, 5966, 7837, 11129, 9381, 7711, 4372, 3502, 1321, 4032, 7311, 3793, 7856, 10880, 1002, 6919, 522, 8682, 3289, 5406, 11942, 20, 5559, 3469, 6281, 6296, 7393, 778, 8561, 994, 9611, 4050, 1254, 8144, 12280, 9173, 3969, 10077, 6998, 4661, 10710, 9051, 8155, 2434, 4322, 8038, 11082, 6763, 3860, 3744, 5911, 7911, 10806, 1325, 2686, 5547, 7507, 11573, 7443, 8531, 11089, 10552, 773, 4099, 3199, 11113, 2476, 2478, 1805, 923, 2780, 10783, 2920, 540, 2625, 7640, 9830, 10235, 2987, 8717, 9945, 2260, 1428, 11038, 9280, 10975, 12046, 1891, 8851, 1721, 4611, 2957, 6523, 10886, 11272, 4273, 6093, 8113, 4278, 10555, 5908, 2776, 12129, 4684, 9115, 11197, 11077, 2301, 6065, 5246, 4337, 9135, 4467, 2257, 8582, 72, 350, 5115, 5407, 5461, 11868, 343, 1326, 8494, 5106, 2291, 9430, 9656, 7341, 5987, 6915, 1868, 10446, 11864, 1689, 3090, 4780, 1389, 5728, 1901, 5486, 9600, 1607, 6105, 4075, 11275, 9408, 4770, 4754, 10138, 4905, 2338, 12048, 1218, 7969, 3578, 325, 7383, 4143, 682, 3998, 6463, 6498, 865, 10008, 11783, 10512, 1944, 9450, 2926, 10810, 12268, 922, 9261, 11224, 8136, 2683, 412, 8830, 2643, 1583, 1892, 2370, 1280, 11684, 814, 8736, 9696, 6170, 636, 7188, 2171, 654, 1131, 6522, 5078, 11713, 9489, 8236, 5900, 5468, 3368, 9545, 1681, 5782, 8308, 6250, 10583, 8775, 2717, 1260, 6125, 9634, 2455, 3400, 11066, 12147, 10916, 1177, 3332, 9370, 5268, 9223, 11722, 316, 4267, 8112, 10759, 10996, 11124, 4919, 9916, 5874, 1928, 2545, 9982, 8243, 9689, 2381, 3723, 6833, 4883, 9741, 9461, 5369, 5959, 4048, 1927, 9026, 10423, 1170, 11832, 168, 4913, 11935, 8520, 8646, 3114, 8993, 3094, 3434, 11914, 9442, 5618, 2049, 4840, 5777, 3846, 8455, 12085, 7201, 3941, 7210, 7057, 3241, 9269, 8532, 4608, 10111, 7846, 1956, 5412, 9923, 9663, 11130, 2900, 7270, 11445, 1359, 3534, 2842, 2209, 156, 8951, 4938, 9667, 9784, 1136, 10984, 2873, 10211, 11063, 7012, 12239, 4536, 9761, 2731, 8838, 12240, 10344, 9320, 9804, 6695, 2164, 9154, 4218, 6167, 7790, 8511, 5530, 7083, 6781, 10092, 8095, 10335, 6204, 1484, 4483, 9162, 1526, 2639, 2929, 3656, 10945, 9852, 2832, 5574, 4566, 11955, 1790, 12115, 9395, 3000, 10487, 4212, 8186, 10436, 2940, 6099, 6094, 1632, 3837, 5339, 3765, 4989, 10939, 11871, 5478, 3, 5135, 10966, 8930, 5860, 6639, 8719, 9272, 1378, 3285, 6752, 1417, 8595, 1842, 6906, 11041, 2126, 9652, 8687, 7751, 3201, 10440, 1594, 4335, 9808, 5349, 400, 579, 7935, 2730, 3030, 392, 3271, 11463, 7591, 7885, 7266, 502, 3123, 12109, 11414, 5646, 4916, 4781, 7197, 5287, 8974, 3343, 11813, 417, 1003, 438, 81, 3466, 1146, 7619, 10752, 7207, 1922, 4564, 339, 2672, 10258, 1392, 10863, 578, 2127, 3171, 8246, 2535, 1058, 364, 404, 11522, 6171, 6444, 6747, 9244, 10800, 3344, 5332, 12265, 8076, 10584, 2294, 2276, 8333, 3982, 11847, 1265, 10587, 7429, 953, 4974, 9842, 6197, 9984, 7570, 8807, 4238, 11726, 11259, 2503, 11826, 2187, 7559, 6364, 9089, 7657, 10254, 2738, 338, 9153, 10699, 6608, 717, 10654, 3317, 8273, 11883, 1440, 7000, 3988, 9828, 10908, 3869, 6860, 1942, 10123, 3808, 8953, 4265, 8785, 11641, 9139, 3121, 493, 7, 3789, 9202, 355, 9577, 3202, 3959, 1153, 11408, 7665, 7562, 11499, 7766, 4298, 3825, 9377, 9057, 6136, 12077, 9893, 7469, 12071, 11912, 10115, 6500, 192, 9126, 1351, 6226, 6370, 7070, 5011, 3536, 2169, 1327, 2013, 4665, 9364, 7287, 11869, 6151, 885, 3278, 2963, 4504, 8240, 4554, 3704, 7082, 973, 10533, 1022, 189, 3991, 2674, 9585, 510, 431, 8581, 6553, 791, 10331, 7550, 3248, 769, 5445, 4963, 7399, 11048, 5915, 6565, 9042, 5039, 6403, 2110, 2747, 3454, 5184, 622, 11899, 8345, 12233, 6555, 118, 9449, 9407, 11251, 5195, 3065, 7048, 125, 949, 6320, 11606, 2483, 6267, 11007, 1278, 68, 1696, 6879, 1693, 1744, 3016, 5103, 9445, 10753, 726, 1481, 11637, 10485, 4885, 9068, 8579, 7226, 1673, 8474, 11836, 11111, 3149, 3360, 12237, 5209, 10643, 874, 835, 7814, 435, 7235, 4789, 4505, 1759, 4113, 10777, 4939, 3186, 9343, 8209, 8841, 5086, 9021, 5961, 3375, 1045, 10883, 6137, 5596, 9452, 2253, 9928, 1836, 8925, 1398, 8844, 10221, 7698, 2602, 9235, 7684, 7313, 3120, 6974, 448, 9005, 11345, 10431, 10767, 8304, 7596, 58, 5061, 11289, 4697, 10885, 5464, 4714, 11309, 10256, 2065, 11745, 11010, 6413, 11034, 10626, 450, 8332, 10463], + # ψ_inv = 1030796471, ψ*ψ_inv = 1. + 2013265921 : [1, 1030796471, 1446820157, 1060108843, 145223211, 1222831386, 419191953, 622931004, 1091445674, 1564480008, 1362884248, 69767869, 1237104841, 1594130339, 1537189756, 205164863, 72041623, 1088774168, 1959689139, 1343628866, 278567984, 953619459, 1996962621, 848357025, 1900691439, 1385597195, 1266592989, 1679927652, 195422490, 317270776, 28665618, 1847943781, 1253260071, 428315196, 602504937, 833779107, 1311163986, 1862658473, 1000130369, 1410766425, 784229619, 980825062, 195958546, 1972385571, 184201817, 1022164082, 60222659, 1238752659, 288289890, 410604508, 90817999, 679243319, 1288100093, 454536672, 542094841, 1314064070, 312665187, 1137453511, 61564083, 1170333511, 1156399853, 1578045958, 367628582, 1466785260, 196396260, 980071634, 694293155, 1934999141, 1447878580, 751755361, 1445297234, 1375344650, 1192577619, 1197830170, 1965511734, 1610058410, 582026550, 672241568, 212597403, 930624567, 1917679203, 610750027, 370291313, 1436326157, 23671793, 1617995135, 1041430005, 1894113639, 1964161403, 866910121, 1815204430, 1345770928, 962633646, 152560134, 1861675199, 199640713, 772607190, 1818664992, 1685231015, 667187770, 1382529621, 321447919, 387089745, 1889147436, 774414016, 1392924859, 641377353, 1623981083, 1481952135, 1284414673, 561153355, 1430650164, 841453531, 412086128, 1336637787, 1391277383, 144808788, 286377538, 1765657525, 11903114, 1824115795, 1344304245, 1279203298, 196815165, 1501019809, 1279505637, 633021876, 1320747678, 1592366214, 1970207237, 825224855, 551985489, 1306647885, 1054808915, 1137846698, 1244518808, 1615500189, 1635566417, 1208658013, 1657606228, 238789043, 676914911, 1506372075, 625696311, 1326890868, 505547537, 1193296344, 678446776, 1841793612, 1810643372, 375944201, 1001927394, 717205599, 901164724, 263000492, 341696196, 923699931, 933678835, 1452434999, 1363315494, 1835875777, 1374421511, 893429879, 1803036115, 566566526, 1764692550, 1480888359, 87882432, 1480042919, 1912131620, 1822130809, 1161552796, 1319549233, 764399745, 914676306, 208652375, 1333217202, 1675467435, 853026644, 1793253533, 622008867, 844944403, 318754226, 1201859359, 782913119, 1334069533, 267530620, 855799849, 1611842161, 1264574707, 242033074, 338778641, 78945800, 1968296822, 1673248567, 497552681, 337843911, 551143834, 972918989, 1527885930, 460258076, 790194434, 1136790769, 1418689847, 912367292, 1976353322, 234094801, 726313431, 1957706687, 1993235394, 403949616, 127007666, 978572249, 15983468, 1828889379, 1846999024, 23215881, 1774715771, 789836419, 234608688, 1222665977, 896569804, 467611061, 983021145, 614075160, 833442481, 839599253, 1792500985, 238307585, 1625234061, 322825485, 1892123295, 1162339639, 1931916621, 1742771357, 1787374314, 1141787051, 1627039122, 533706621, 31880356, 1351065666, 1397143687, 616143212, 956233886, 453260585, 1987950424, 446497382, 1089218665, 787294362, 963480260, 523815300, 890014303, 1319662041, 639635195, 347997613, 971306119, 1728404513, 1884084245, 1468908220, 346980260, 1134936783, 1801786086, 1512945445, 1719140322, 538704769, 1037489962, 949217896, 588637337, 928726468, 386876004, 176704060, 1685765963, 1721589904, 1021004980, 642368040, 1361274146, 717108123, 1059942796, 1312408731, 72043095, 418675046, 1643615825, 1542756987, 640946950, 1101686277, 974879690, 1766800858, 1922518609, 1123955347, 206082808, 1701668849, 1221355858, 1422331819, 1874357167, 1860733967, 595435314, 544217376, 254852752, 1628335437, 656988550, 330889263, 1160863821, 1967436205, 162397637, 1141518129, 336595508, 522072406, 1962346315, 967052682, 1990693935, 1489739020, 935530766, 738339747, 1141080023, 1923142934, 599469246, 219624526, 1036609758, 271811198, 1097291622, 1400279418, 615517092, 1392435941, 510575142, 280665754, 431682106, 891774882, 1773270930, 1410131110, 1502836897, 1188001972, 1034717894, 1425450773, 142332549, 929105609, 397836974, 492152090, 1990875465, 985358226, 1592906921, 247018910, 1806603279, 1817459975, 1102681299, 1777795839, 1643993526, 983783494, 1265597135, 905629977, 496879956, 1055921913, 1870533459, 451973565, 1985348401, 1737761431, 54432900, 1178581019, 735568739, 1058177057, 997572764, 912838049, 1158979160, 1010073824, 2001888260, 316702096, 1276691224, 1651123459, 970136676, 1074029057, 1018421146, 1545120835, 1139470663, 367148769, 892127802, 1364955234, 623991846, 1856545343, 1653077195, 1017905375, 1829830690, 340222871, 1807506522, 516692005, 239770140, 211723194, 1164949965, 950321114, 554304165, 1481204474, 1211732826, 1361635720, 40156510, 554679511, 1395242502, 1020851608, 1058996195, 1829967362, 1059419487, 646509327, 1017211815, 1411014714, 1792276534, 1459236484, 1187232554, 1705576440, 1172452124, 1964664562, 1848425832, 1547674161, 1924932541, 1854920421, 545259856, 248075240, 249037385, 861624660, 1066431106, 588889032, 1693787385, 259767560, 99635052, 785796932, 213084457, 312493468, 138024182, 632005205, 1857765135, 1896793571, 1830833724, 1467656730, 1222623956, 1214352228, 242076122, 1684363409, 398532280, 821873058, 38085069, 79020822, 810586732, 1796202227, 1939494964, 1491681022, 1319162119, 364665014, 1115843845, 1359339913, 1832692152, 1715780319, 1052363365, 1446056615, 773874696, 1398748632, 1186699372, 1045349508, 1783920278, 1458654649, 1653660090, 2005607917, 1050533831, 775130826, 1912168001, 110965872, 1088180068, 1004117819, 1320262432, 1606274035, 341742893, 851601029, 1916773346, 1159345101, 158711712, 118524575, 1396500869, 223231138, 1201618857, 1709024775, 1008126754, 1966806874, 520305435, 63788306, 1065754297, 1273220281, 1121868036, 789303395, 1378950494, 919403943, 786685642, 1072187526, 1374785473, 1547495552, 1539987310, 1514054684, 1211524598, 993707905, 711454996, 752427352, 1110595814, 1094366075, 714277787, 849624638, 1046371821, 675346434, 320243431, 1514074518, 1313302657, 132263909, 360968134, 126061375, 933755451, 559308147, 514853946, 1947940162, 475210120], + # ψ_inv = 8293209, ψ*ψ_inv = 1. + 8380417 : [1, 8293209, 4197045, 7192532, 2740543, 3838479, 1300016, 6866265, 4317364, 6695264, 8210729, 6715099, 4805951, 4040196, 459163, 7325939, 601683, 6600190, 2811291, 2033807, 7284949, 5199961, 2925816, 3994671, 6666122, 1979497, 235407, 2647994, 4510100, 1310261, 1744507, 3123762, 5178923, 2296397, 2635473, 6987258, 3704823, 12417, 6592474, 5274859, 7946292, 4829411, 2962264, 1615530, 4430364, 7561656, 1356448, 4849188, 4615550, 6924527, 1937570, 2743411, 5138445, 4146264, 2461387, 3363542, 3585098, 7371052, 5183169, 749577, 6522001, 8238582, 8031605, 6663603, 3765607, 3965306, 3121440, 6545891, 3182878, 3747250, 4182915, 7520273, 6705802, 2678278, 3334383, 7216819, 4965348, 6458423, 4912752, 781875, 5698129, 2772600, 7270901, 6757063, 7451668, 5992904, 7153756, 7009900, 6919699, 3956944, 3258457, 8041997, 5483103, 7767179, 3818627, 6187330, 5234739, 3716946, 7102792, 1476985, 2101410, 3195676, 2254727, 7872272, 7044481, 8129971, 1528066, 5811406, 4623627, 6500539, 2683270, 3773731, 7822959, 8378664, 2028118, 586241, 4018989, 6187479, 621164, 545376, 6096684, 7557876, 4166425, 4148469, 2917338, 5867399, 7369194, 7987710, 4808194, 1182243, 3222807, 7872490, 4793971, 1239911, 2362063, 8240173, 3370349, 4969849, 7814814, 6352379, 817536, 4908348, 7727142, 731434, 4837932, 5720009, 5396636, 6006015, 3506380, 268456, 3374250, 8368538, 5152541, 6783595, 6644104, 3009748, 556856, 2218467, 2236726, 2185084, 5496691, 4423672, 4528402, 5889092, 1159875, 1254190, 5801164, 1103344, 3524442, 635956, 1148858, 6657188, 1716988, 6201452, 5604662, 8077412, 1005239, 2659525, 4564692, 527981, 6224367, 1772588, 1317678, 214880, 7737789, 2454145, 6392603, 4357667, 3345963, 2998219, 327848, 3014420, 3761513, 636927, 274060, 724804, 4778199, 2296099, 3482206, 4949981, 5717039, 4611469, 2462444, 3369273, 6621070, 458740, 2312838, 1900052, 6250525, 59148, 4158088, 2105286, 394148, 3611750, 4478945, 3020393, 2453983, 3759465, 3250154, 3033742, 3192354, 7025525, 1922253, 6022044, 5178987, 5095502, 3073009, 6386371, 3110818, 2642980, 5989328, 553718, 7703827, 5925040, 482649, 3980599, 1935799, 6341273, 5601629, 4405932, 1221177, 1935420, 5871437, 7200804, 2071829, 1727088, 5344437, 7609976, 2815639, 8352605, 3488383, 2612853, 1853806, 8291116, 2354215, 5454363], +} + +# The table ψ, but in bit-reversed order, i.e. the i-th element corresponds to ψ^{BitReversed(i)}. +ψ_rev = { + 3329 : [1, 1729, 749, 40, 2699, 2642, 848, 1432, 797, 3136, 1062, 1919, 569, 1746, 69, 2786, 1089, 1996, 56, 283, 3033, 882, 1339, 1476, 2393, 2879, 1355, 2508, 447, 535, 1903, 1235, 3296, 2865, 1915, 2009, 816, 2697, 1977, 2679, 331, 3040, 1573, 3253, 1197, 2304, 1052, 1274, 682, 712, 1481, 648, 3110, 855, 2419, 1227, 927, 1534, 1891, 461, 1894, 2319, 452, 2522], + 12289 : [1, 1479, 4043, 7143, 5736, 4134, 1305, 722, 1646, 1212, 6429, 9094, 3504, 8747, 9744, 8668, 4591, 6561, 5023, 6461, 10938, 4978, 6512, 8961, 11340, 9664, 9650, 4821, 563, 9314, 2744, 3006, 1000, 4320, 12208, 3091, 9326, 4896, 2366, 9238, 11563, 7678, 1853, 140, 1635, 9521, 11112, 4255, 7203, 10963, 9088, 9275, 790, 955, 11119, 2319, 9542, 4846, 3135, 3712, 9995, 11227, 3553, 7484, 544, 5791, 11950, 2468, 11267, 9, 9447, 11809, 10616, 8011, 7300, 6958, 1381, 2525, 4177, 8705, 2837, 5374, 4354, 130, 2396, 4452, 3296, 8340, 12171, 9813, 2197, 5067, 11336, 3748, 5767, 827, 3284, 2881, 5092, 10200, 10276, 9000, 9048, 11560, 10593, 10861, 334, 2426, 4632, 5755, 11029, 4388, 10530, 3707, 3694, 7110, 11934, 3382, 2548, 8058, 4890, 6378, 9558, 3932, 5542, 12144, 3459, 3637, 1663, 1777, 1426, 7635, 2704, 5291, 7351, 8653, 9140, 160, 12286, 7852, 2166, 8374, 7370, 12176, 3364, 10600, 9018, 4057, 2174, 7917, 2847, 7875, 7094, 9509, 10805, 4895, 2305, 5042, 4053, 9644, 3985, 7384, 476, 3531, 420, 6730, 2178, 1544, 9273, 243, 9289, 11618, 3136, 5191, 8889, 9890, 9103, 6882, 10163, 1630, 11136, 2884, 8241, 10040, 3247, 9603, 2969, 3978, 6957, 3510, 9919, 9424, 7575, 8146, 1537, 12047, 8585, 2678, 5019, 545, 7404, 1017, 10657, 7205, 10849, 8526, 3066, 12262, 11244, 2859, 2481, 7277, 2912, 5698, 354, 7428, 390, 11516, 3778, 8456, 442, 2401, 5101, 11222, 4976, 10682, 875, 3780, 7278, 11287, 5088, 4284, 6022, 9302, 2437, 3646, 10102, 9723, 6039, 9867, 11854, 7952, 10911, 1912, 11796, 8193, 9908, 5444, 9041, 1207, 5277, 1168, 11885, 4645, 1065, 2143, 3957, 2839, 10162, 151, 11858, 1579, 2505, 5906, 52, 3174, 1323, 2766, 3336, 6055, 6415, 677, 3445, 7509, 4698, 5057, 12097, 10968, 10240, 4912, 5241, 9369, 3127, 4169, 3482, 787, 6821, 11279, 12231, 241, 11286, 3532, 11404, 6008, 10333, 7280, 2844, 3438, 8077, 975, 5681, 8812, 142, 1105, 4080, 421, 3602, 6221, 4624, 6212, 3263, 8689, 5886, 4782, 5594, 3029, 4213, 504, 605, 9987, 2033, 8291, 10367, 8410, 11316, 11035, 10930, 5435, 3710, 6196, 6950, 5446, 8301, 468, 11973, 11907, 6152, 4948, 11889, 10561, 6153, 6427, 3643, 5415, 56, 9090, 5206, 6760, 1702, 10302, 11635, 3565, 5315, 8214, 7373, 4324, 10120, 11767, 5079, 3262, 11011, 2344, 6715, 1973, 5925, 1018, 3514, 11248, 7500, 7822, 5537, 4749, 8500, 12142, 5456, 7840, 6844, 8429, 7753, 1050, 6118, 3818, 9606, 1190, 5876, 2281, 2031, 5333, 8298, 8320, 12133, 2767, 453, 6381, 418, 3772, 5429, 4774, 1293, 7552, 2361, 1843, 9259, 4115, 218, 2908, 8855, 8760, 2882, 10484, 1954, 2051, 2447, 6147, 576, 3963, 1858, 7535, 3315, 11863, 2925, 347, 3757, 1975, 10596, 3009, 174, 11566, 9551, 5868, 2655, 6554, 1512, 11939, 5383, 10474, 9087, 7796, 6920, 10232, 6374, 1483, 49, 11026, 1489, 2500, 10706, 5942, 1404, 11964, 11143, 948, 4049, 3728, 1159, 5990, 652, 5766, 6190, 11994, 4016, 4077, 2919, 3762, 6328, 7183, 10695, 1962, 7991, 8960, 12121, 9597, 7105, 1200, 6122, 9734, 3956, 1360, 6119, 5297, 3054, 6803, 9166, 1747, 5919, 4433, 3834, 5257, 683, 2459, 8633, 12225, 9786, 9341, 6507, 1566, 11454, 6224, 3570, 8049, 3150, 1319, 4046, 11580, 1958, 7967, 2078, 1112, 11231, 8210, 11367, 441, 1826, 9363, 9118, 4489, 3708, 3238, 11153, 3449, 7080, 1092, 3359, 3205, 8024, 8611, 10361, 11825, 2068, 10900, 4404, 346, 3163, 8257, 7449, 6127, 12164, 11749, 10763, 4222, 8051, 11677, 8921, 8062, 7228, 11071, 11851, 3515, 9011, 5993, 6877, 8080, 1536, 10568, 4103, 9860, 11572, 8700, 1373, 2982, 3448, 11946, 4538, 1908, 4727, 11081, 1866, 7078, 10179, 716, 10125, 6873, 1705, 2450, 11475, 416, 10224, 5826, 7725, 8794, 1756, 4145, 8755, 8328, 5063, 4176, 8524, 10771, 2461, 2275, 8022, 5653, 6693, 6302, 11710, 3889, 212, 6323, 9175, 2769, 5734, 1176, 5508, 11014, 4860, 11164, 11158, 10844, 11841, 1014, 7508, 7365, 10962, 3607, 5232, 8347, 12221, 10029, 7723, 5836, 3200, 1535, 9572, 60, 7784, 10032, 10872, 5676, 3087, 6454, 7406, 3975, 7326, 8545, 2528, 3056, 5845, 5588, 11877, 5102, 1255, 506, 10897, 5784, 9615, 2212, 3338, 9013, 1178, 9513, 6811, 8778, 10347, 3408, 1165, 2575, 10453, 425, 11897, 10104, 377, 4578, 375, 1620, 1038, 11366, 6085, 4167, 6092, 2231, 2800, 12096, 1522, 2151, 8946, 8170, 5002, 12269, 7681, 5163, 10545, 1314, 2894, 3654, 11951, 3947, 9834, 6599, 7350, 7174, 1248, 2442, 8330, 6492, 6330, 10141, 5724, 10964, 1945, 1029, 8945, 6691, 10397, 3624, 6825, 4906, 4670, 512, 7735, 11295, 9389, 12050, 1804, 1403, 6195, 7100, 406, 10602, 7021, 12143, 8914, 9998, 7954, 3393, 8464, 8054, 7376, 8761, 11667, 1737, 4499, 5672, 8307, 9342, 11653, 5609, 4605, 2689, 180, 8151, 5219, 1409, 204, 6780, 9806, 2054, 1344, 9247, 463, 8882, 3981, 1468, 4475, 7043, 3017, 1236, 9168, 4705, 2600, 11232, 4739, 4251, 1226, 6771, 11925, 2360, 3028, 5216, 11839, 10345, 11711, 5368, 11779, 7628, 2622, 6903, 8929, 7605, 7154, 12226, 8481, 8619, 2373, 7302, 10891, 9199, 826, 5043, 5789, 8787, 6671, 10631, 9224, 1506, 7806, 5703, 4719, 11538, 6389, 11379, 4693, 9951, 11872, 9996, 6138, 8820, 4443, 8871, 7186, 10398, 1802, 10734, 1590, 4411, 1223, 2334, 2946, 6828, 2637, 4510, 881, 365, 10362, 1015, 7250, 6742, 2485, 904, 24, 10918, 11009, 11675, 980, 11607, 5082, 7699, 5207, 8239, 844, 7087, 3221, 8016, 8452, 2595, 5289, 6627, 567, 2941, 1406, 2633, 6940, 2945, 3232, 11996, 3769, 7434, 3944, 8190, 6759, 5604, 11024, 9282, 10118, 8809, 9169, 6184, 6643, 6086, 8753, 5370, 8348, 8536, 1282, 3572, 9457, 2021, 4730, 3229, 1706, 3929, 5054, 3154, 9004, 7929, 12282, 1936, 8566, 11444, 11520, 5526, 50, 216, 767, 3805, 4153, 10076, 1279, 11424, 9617, 5170, 12100, 3116, 10080, 1763, 3815, 1734, 1350, 5832, 8420, 4423, 1530, 1694, 10036, 10421, 9559, 5411, 4820, 1160, 9195, 7771, 2840, 9811, 4194, 9270, 7315, 4565, 7211, 10506, 944, 7519, 7002, 8620, 7624, 6883, 3020, 5673, 5410, 1251, 10499, 7014, 2035, 11249, 6164, 10407, 8176, 12217, 10447, 3840, 2712, 4834, 2828, 4352, 1241, 4378, 3451, 4094, 3045, 5781, 9646, 11194, 7592, 8711, 8823, 10588, 7785, 11511, 2626, 530, 10808, 9332, 9349, 2046, 8972, 9757, 8957, 12150, 3268, 3795, 1849, 6513, 4523, 4301, 457, 8, 8835, 3758, 8071, 4390, 10013, 982, 2593, 879, 9687, 10388, 11787, 7171, 6063, 8496, 8443, 1573, 5969, 4649, 9360, 6026, 1030, 11823, 10608, 8468, 11415, 9988, 5650, 12119, 648, 12139, 2307, 8000, 11498, 9855, 9416, 2827, 9754, 11169, 21, 6481], + 2013265921 : [1, 284861408, 1801542727, 420899707, 567209306, 1934320121, 612986503, 1816869661, 740045640, 1399190761, 1561292356, 1240658731, 1424376889, 177390144, 889310574, 760005850, 918899846, 662200255, 939236864, 1171812390, 328902512, 680048719, 871747792, 1724976031, 406991886, 55559234, 1521113831, 95586718, 602251207, 686375053, 291676017, 1941224298, 1881002012, 1225971559, 156720578, 189150126, 521584899, 1230352802, 1274926174, 1700600734, 1790034783, 1990050040, 235470082, 49104518, 465591760, 1296060322, 1594590875, 112574482, 465770369, 850926282, 1100427872, 1238851905, 1381260716, 533223002, 1469048545, 1229036302, 7658004, 1553007845, 603134811, 820688302, 1458586410, 397765732, 1474561152, 921820247, 1453957774, 693603880, 1673043050, 512246112, 653926008, 401423760, 1793641395, 856866068, 46459047, 790599944, 1107635944, 1050632275, 1765190681, 1089565990, 911579644, 1817843431, 1019558016, 871478870, 1696563825, 531313786, 545609191, 693716688, 1682376658, 1829064104, 1902300049, 1100898629, 587815148, 1431239371, 183298559, 1774476878, 1084539453, 776161080, 1337919487, 1560005336, 1646117152, 1868457133, 1934245099, 1391257054, 1046213239, 725165828, 853920820, 1034693672, 1766247011, 1989594128, 307689481, 171472309, 1296157798, 1734697937, 1093861978, 1774958336, 834684902, 630736300, 1227468989, 1446699395, 590934102, 702101935, 967916413, 1675422010, 1732600167, 565387341, 532061447, 706618036, 878329138, 1868042710, 65325759, 1665268308, 1496573916, 1380244045, 297485602, 1771232847, 1741454723, 1645637339, 1949477615, 1545654860, 957344008, 151590722, 1151641261, 560830922, 246465063, 1984600303, 1260838569, 1479559300, 362142462, 1452112566, 798913693, 1098589615, 45829716, 1953043262, 1009148102, 1779171120, 1084160312, 1800668518, 1366756594, 506893846, 1836561861, 476076165, 499191403, 1566768539, 648310687, 247608396, 217063694, 1694511695, 523526901, 1471171080, 1894741346, 184376542, 195805946, 971835916, 48601359, 1637321720, 700857190, 16303300, 941078395, 1690440436, 955088864, 1626176176, 1700772453, 532377562, 152531954, 1013135552, 554611272, 1040346932, 1121491039, 567968687, 651630201, 875419223, 500320476, 1594073968, 1887204546, 1489450621, 995360546, 734062623, 1648600907, 1745735301, 90122987, 1951701838, 304241146, 1223429502, 1029482427, 198061491, 158345500, 1750265429, 470508934, 746672932, 499211237, 270494564, 1003192097, 1371888568, 116472350, 191135112, 384930484, 1817307375, 1238135095, 876475152, 825263949, 47754187, 992414313, 804607908, 1064048025, 650381673, 1163641283, 1397122709, 468145086, 676628134, 1191392863, 1160239277, 1491193515, 1922447922, 1161664892, 1609316305, 1027907695, 1642974608, 554029437, 819969577, 1370897881, 53576782, 1223962526, 1173666668, 275504490, 328034906, 1753498361, 1119836042, 311597072, 1410760984, 614517289, 340017354, 620829980, 1318972766, 1062944807, 1188041066, 544357701, 566445764, 1538055801, 1041959802, 1773495781, 692518243, 960902556, 1674487280, 915974299, 546480661, 947511624, 1030244776, 142732462, 1813625208, 946834815, 649950427, 90747312, 165322140, 902670107, 1981385565, 1043129245, 582615757, 1771189799, 1804613546, 1850868284, 774513262, 693003489, 1286952490, 1615428947, 1082641354, 996054106, 1387569610, 327499958, 1808101058, 699963264, 924047256, 1389274075, 2001362807, 73770957, 811406562, 1077735155, 699201851, 616765052, 166266897, 910584622, 119152282, 164840089, 1011338527, 1941222826, 1164908896, 638480448, 121142626, 1015693157, 124118485, 1875241739, 1925383489, 1417830607, 602499496, 359605831, 485379991, 239994991, 637921271, 1973109411, 768747113, 294125599, 1390334917, 1079510470, 1123251618, 183435231, 1816450756, 897422076, 1157466072, 1413796675, 842932410, 1005139167, 1778657233, 747668786, 667494993, 1468006065, 1671569725, 1372318971, 333338269, 801741323, 225891607, 11377661, 389284838, 182432197, 851713125, 1356277371, 40880350, 101097920, 594576074, 978548027, 403207511, 954269726, 355659693, 1424628584, 1943498052, 966894100, 1057032035, 873795258, 621988538, 1975180852, 220012388, 50919606, 1334022602, 96492575, 1886258255, 420359000, 576939764, 826033367, 1334819145, 651991775, 669637055, 634315427, 220764936, 1958833021, 1346078151, 1913630869, 210229806, 791910063, 1179486814, 826566549, 1515713240, 1502690779, 78266780, 1458961756, 1461280432, 1666285661, 953157078, 1498411975, 1373630726, 205759399, 733760284, 180573769, 748691214, 976656163, 435219963, 1492960486, 1116696117, 1516385965, 1860705787, 1764228536, 1079587086, 1038386231, 1695995145, 1301810925, 386226799, 736574697, 728851248, 790641965, 1248866176, 852402100, 991101839, 925085853, 36912599, 1870933372, 1341024353, 953846434, 1336351010, 1626389917, 419135582, 1693022490, 25315497, 1121138119, 1726888383, 1202679189, 1168321518, 22571986, 1558729249, 1854554209, 1997282453, 206662642, 395270786, 840813797, 202622549, 953323125, 1059646462, 1226580279, 388031860, 1277697182, 1691818002, 1800181464, 248573371, 138908754, 150607448, 229345643, 1462122087, 1581583815, 1261510560, 801533095, 958457006, 211479835, 790434535, 1652297787, 1049785661, 360188726, 668961676, 694103802, 679196388, 872185898, 875812410, 811647064, 238550150, 369272395, 1146355800, 88333380, 1112101197, 369650096, 627668726, 473278611, 81349300, 854286761, 620341062, 155500786, 101134301, 1758413169, 1032440859, 962732090, 1223071487, 510429024, 815435751, 618023419, 377699504, 975775959, 448785913, 1298988134, 616122234, 994844775, 1601179793, 1614733641, 337798486, 1676670413, 1602661413, 1671523028, 20030527, 22390456, 1402515894, 220989387, 1507718384, 992260941, 924491753, 891397885, 1179823440, 27917520, 194600929, 319478536, 638844410, 1807183113, 1584950725, 1239391225, 44969099, 1397748829, 1033194287, 848315956, 43058684, 129181676, 982469450], + 8380417 : [1, 3572223, 4618904, 4614810, 3201430, 3145678, 2883726, 3201494, 7159240, 557458, 7375178, 3764867, 2129892, 2682288, 3542485, 7778734, 5564778, 2283733, 642628, 4795319, 5926434, 1460718, 3227876, 1714295, 676590, 1335936, 4855975, 434125, 2663378, 1674615, 5010068, 4063053, 6526611, 5463079, 5034454, 1858416, 5188063, 2897314, 7823561, 3870317, 6444618, 3756790, 6663429, 3950053, 1759347, 3415069, 7562881, 3574466, 6308588, 4361428, 2156050, 3241972, 7986269, 928749, 4874037, 1095468, 5269599, 6279007, 7220542, 4675594, 3602218, 5197539, 3586446, 5639874, 6026202, 1011223, 8052569, 348812, 6458164, 4561790, 6143691, 6635910, 2778788, 5697147, 2775755, 7023969, 6067579, 3467665, 653275, 7921254, 3035980, 7759253, 7062739, 5919030, 3901472, 1226661, 5006167, 5454601, 2391089, 6125690, 2579253, 1787943, 4898211, 4197502, 6018354, 7080401, 4892034, 4213992, 1987814, 3197248, 5130263, 5121960, 1736313, 8145010, 7897768, 6852351, 7231559, 5418153, 5917973, 5046034, 565603, 169688, 2508980, 6352299, 3815725, 6442847, 4222329, 1109516, 2983781, 5569126, 5307408, 1277625, 3852015, 5744944, 8106357, 5258977, 5157610, 4183372, 2926054, 392707, 5365997, 1716814, 2358373, 2193087, 6195333, 5256655, 3974485, 4606686, 303005, 3531229, 6480365, 7598542, 7648983, 1054478, 770441, 7835041, 8165537, 5016875, 5360024, 1370517, 11879, 4385746, 7826699, 508145, 7277073, 3105558, 3430436, 860144, 140244, 1514152, 5767564, 4231948, 4022750, 7630840, 5346675, 338420, 5370669, 5732423, 4399818, 2569011, 1723229, 6764887, 5011144, 1163598, 2028038, 1665318, 1179613, 7794176, 7852436, 5637006, 6275131, 1623354, 2374402, 6346610, 1994046, 6903432, 2491325, 1393159, 7655613, 1834526, 507927, 1187885, 89301, 2513018, 5382198, 141835, 1354892, 613238, 6161950, 7070156, 2039144, 1879878, 2178965, 818761, 7921677, 1921994, 3472069, 4340221, 6653329, 2192938, 6607829, 4234153, 4768667, 2387513, 8111961, 3180456, 5737437, 5184741, 7126227, 8368000, 6084318, 4633167, 7140506, 4541938, 27812, 822541, 5926272, 1009365, 4620952, 4423473, 1596822, 6400920, 2455377, 250446, 7744461, 3551006, 3768948, 5702139, 3410568, 1685153, 6444997, 1753, 5720892, 1455890, 8321269, 5607817, 2660408, 1780227, 3284915, 4663471, 3956745, 6084020, 7743490, 4415111, 7198174, 87208], +} + +# The table ψ_inv, but in bit-reversed order, i.e. the i-th element corresponds to ψ^{BitReversed(-i)}. +ψ_inv_rev = { + 3329 : [1, 1600, 3289, 2580, 1897, 2481, 687, 630, 543, 3260, 1583, 2760, 1410, 2267, 193, 2532, 2094, 1426, 2794, 2882, 821, 1974, 450, 936, 1853, 1990, 2447, 296, 3046, 3273, 1333, 2240, 807, 2877, 1010, 1435, 2868, 1438, 1795, 2402, 2102, 910, 2474, 219, 2681, 1848, 2617, 2647, 2055, 2277, 1025, 2132, 76, 1756, 289, 2998, 650, 1352, 632, 2513, 1320, 1414, 464, 33], + 12289 : [1, 10810, 5146, 8246, 11567, 10984, 8155, 6553, 3621, 2545, 3542, 8785, 3195, 5860, 11077, 10643, 9283, 9545, 2975, 11726, 7468, 2639, 2625, 949, 3328, 5777, 7311, 1351, 5828, 7266, 5728, 7698, 4805, 8736, 1062, 2294, 8577, 9154, 7443, 2747, 9970, 1170, 11334, 11499, 3014, 3201, 1326, 5086, 8034, 1177, 2768, 10654, 12149, 10436, 4611, 726, 3051, 9923, 7393, 2963, 9198, 81, 7969, 11289, 8652, 8830, 145, 6747, 8357, 2731, 5911, 7399, 4231, 9741, 8907, 355, 5179, 8595, 8582, 1759, 7901, 1260, 6534, 7657, 9863, 11955, 1428, 1696, 729, 3241, 3289, 2013, 2089, 7197, 9408, 9005, 11462, 6522, 8541, 953, 7222, 10092, 2476, 118, 3949, 8993, 7837, 9893, 12159, 7935, 6915, 9452, 3584, 8112, 9764, 10908, 5331, 4989, 4278, 1673, 480, 2842, 12280, 1022, 9821, 339, 6498, 11745, 10146, 11224, 7644, 404, 11121, 7012, 11082, 3248, 6845, 2381, 4096, 493, 10377, 1378, 4337, 435, 2422, 6250, 2566, 2187, 8643, 9852, 2987, 6267, 8005, 7201, 1002, 5011, 8509, 11414, 1607, 7313, 1067, 7188, 9888, 11847, 3833, 8511, 773, 11899, 4861, 11935, 6591, 9377, 5012, 9808, 9430, 1045, 27, 9223, 3763, 1440, 5084, 1632, 11272, 4885, 11744, 7270, 9611, 3704, 242, 10752, 4143, 4714, 2865, 2370, 8779, 5332, 8311, 9320, 2686, 9042, 2249, 4048, 9405, 1153, 10659, 2126, 5407, 3186, 2399, 3400, 7098, 9153, 671, 3000, 12046, 3016, 10745, 10111, 5559, 11869, 8758, 11813, 4905, 8304, 2645, 8236, 7247, 9984, 7394, 1484, 2780, 5195, 4414, 9442, 4372, 10115, 8232, 3271, 1689, 8925, 113, 4919, 3915, 10123, 4437, 3, 12129, 3149, 3636, 4938, 6998, 9585, 4654, 10863, 10512, 10626, 11848, 922, 4079, 1058, 11177, 10211, 4322, 10331, 709, 8243, 10970, 9139, 4240, 8719, 6065, 835, 10723, 5782, 2948, 2503, 64, 3656, 9830, 11606, 7032, 8455, 7856, 6370, 10542, 3123, 5486, 9235, 6992, 6170, 10929, 8333, 2555, 6167, 11089, 5184, 2692, 168, 3329, 4298, 10327, 1594, 5106, 5961, 8527, 9370, 8212, 8273, 295, 6099, 6523, 11637, 6299, 11130, 8561, 8240, 11341, 1146, 325, 10885, 6347, 1583, 9789, 10800, 1263, 12240, 10806, 5915, 2057, 5369, 4493, 3202, 1815, 6906, 350, 10777, 5735, 9634, 6421, 2738, 723, 12115, 9280, 1693, 10314, 8532, 11942, 9364, 426, 8974, 4754, 10431, 8326, 11713, 6142, 9842, 10238, 10335, 1805, 9407, 3529, 3434, 9381, 12071, 8174, 3030, 10446, 9928, 4737, 10996, 7515, 6860, 8517, 11871, 5908, 11836, 9522, 156, 3969, 3991, 6956, 10258, 10008, 6413, 11099, 2683, 8471, 6171, 11239, 4536, 3860, 5445, 4449, 6833, 147, 3789, 7540, 6752, 4467, 4789, 1041, 8775, 11271, 6364, 10316, 5574, 9945, 1278, 9027, 7210, 522, 2169, 7965, 4916, 4075, 6974, 8724, 654, 1987, 10587, 5529, 7083, 3199, 12233, 6874, 8646, 5862, 6136, 1728, 400, 7341, 6137, 382, 316, 11821, 3988, 6843, 5339, 6093, 8579, 6854, 1359, 1254, 973, 3879, 1922, 3998, 10256, 2302, 11684, 11785, 8076, 9260, 6695, 7507, 6403, 3600, 9026, 6077, 7665, 6068, 8687, 11868, 8209, 11184, 12147, 3477, 6608, 11314, 4212, 8851, 9445, 5009, 1956, 6281, 885, 8757, 1003, 12048, 58, 1010, 5468, 11502, 8807, 8120, 9162, 2920, 7048, 7377, 2049, 1321, 192, 7232, 7591, 4780, 8844, 11612, 5874, 6234, 8953, 9523, 10966, 9115, 12237, 6383, 9784, 10710, 431, 12138, 2127, 9450, 8332, 5808, 12268, 1120, 2535, 9462, 2873, 2434, 791, 4289, 9982, 150, 11641, 170, 6639, 2301, 874, 3821, 1681, 466, 11259, 6263, 2929, 7640, 6320, 10716, 3846, 3793, 6226, 5118, 502, 1901, 2602, 11410, 9696, 11307, 2276, 7899, 4218, 8531, 3454, 12281, 11832, 7988, 7766, 5776, 10440, 8494, 9021, 139, 3332, 2532, 3317, 10243, 2940, 2957, 1481, 11759, 9663, 778, 4504, 1701, 3466, 3578, 4697, 1095, 2643, 6508, 9244, 8195, 8838, 7911, 11048, 7937, 9461, 7455, 9577, 8449, 1842, 72, 4113, 1882, 6125, 1040, 10254, 5275, 1790, 11038, 6879, 6616, 9269, 5406, 4665, 3669, 5287, 4770, 11345, 1783, 5078, 7724, 4974, 3019, 8095, 2478, 9449, 4518, 3094, 11129, 7469, 6878, 2730, 1868, 2253, 10595, 10759, 7866, 3869, 6457, 10939, 10555, 8474, 10526, 2209, 9173, 189, 7119, 2672, 865, 11010, 2213, 8136, 8484, 11522, 12073, 12239, 6763, 769, 845, 3723, 10353, 7, 4360, 3285, 9135, 7235, 8360, 10583, 9060, 7559, 10268, 2832, 8717, 11007, 3753, 3941, 6919, 3536, 6203, 5646, 6105, 3120, 3480, 2171, 3007, 1265, 6685, 5530, 4099, 8345, 4855, 8520, 293, 9057, 9344, 5349, 9656, 10883, 9348, 11722, 5662, 7000, 9694, 3837, 4273, 9068, 5202, 11445, 4050, 7082, 4590, 7207, 682, 11309, 614, 1280, 1371, 12265, 11385, 9804, 5547, 5039, 11274, 1927, 11924, 11408, 7779, 9652, 5461, 9343, 9955, 11066, 7878, 10699, 1555, 10487, 1891, 5103, 3418, 7846, 3469, 6151, 2293, 417, 2338, 7596, 910, 5900, 751, 7570, 6586, 4483, 10783, 3065, 1658, 5618, 3502, 6500, 7246, 11463, 3090, 1398, 4987, 9916, 3670, 3808, 63, 5135, 4684, 3360, 5386, 9667, 4661, 510, 6921, 578, 1944, 450, 7073, 9261, 9929, 364, 5518, 11063, 8038, 7550, 1057, 9689, 7584, 3121, 11053, 9272, 5246, 7814, 10821, 8308, 3407, 11826, 3042, 10945, 10235, 2483, 5509, 12085, 10880, 7070, 4138, 12109, 9600, 7684, 6680, 636, 2947, 3982, 6617, 7790, 10552, 622, 3528, 4913, 4235, 3825, 8896, 4335, 2291, 3375, 146, 5268, 1687, 11883, 5189, 6094, 10886, 10485, 239, 2900, 994, 4554, 11777, 7619, 7383, 5464, 8665, 1892, 5598, 3344, 11260, 10344, 1325, 6565, 2148, 5959, 5797, 3959, 9847, 11041, 5115, 4939, 5690, 2455, 8342, 338, 8635, 9395, 10975, 1744, 7126, 4608, 20, 7287, 4119, 3343, 10138, 10767, 193, 9489, 10058, 6197, 8122, 6204, 923, 11251, 10669, 11914, 7711, 11912, 2185, 392, 11864, 1836, 9714, 11124, 8881, 1942, 3511, 5478, 2776, 11111, 3276, 8951, 10077, 2674, 6505, 1392, 11783, 11034, 7187, 412, 6701, 6444, 9233, 9761, 3744, 4963, 8314, 4883, 5835, 9202, 6613, 1417, 2257, 4505, 12229, 2717, 10754, 9089, 6453, 4566, 2260, 68, 3942, 7057, 8682, 1327, 4924, 4781, 11275, 448, 1445, 1131, 1125, 7429, 1275, 6781, 11113, 6555, 9520, 3114, 5966, 12077, 8400, 579, 5987, 5596, 6636, 4267, 10014, 9828, 1518, 3765, 8113, 7226, 3961, 3534, 8144, 10533, 3495, 4564, 6463, 2065, 11873, 814, 9839, 10584, 5416, 2164, 11573, 2110, 5211, 10423, 1208, 7562, 10381, 7751, 343, 8841, 9307, 10916, 3589, 717, 2429, 8186, 1721, 10753, 4209, 5412, 6296, 3278, 8774, 438, 1218, 5061, 4227, 3368, 612, 4238, 8067, 1526, 540, 125, 6162, 4840, 4032, 9126, 11943, 7885, 1389, 10221, 464, 1928, 3678, 4265, 9084, 8930, 11197, 5209, 8840, 1136, 9051, 8581, 7800, 3171, 2926, 10463], + 2013265921 : [1, 1728404513, 1592366214, 211723194, 196396260, 1400279418, 78945800, 1446056615, 1253260071, 1123955347, 1835875777, 588889032, 772607190, 451973565, 614075160, 1273220281, 72041623, 1721589904, 1326890868, 1411014714, 1917679203, 492152090, 1957706687, 1606274035, 288289890, 1141518129, 1333217202, 1684363409, 841453531, 1074029057, 1351065666, 1094366075, 1091445674, 538704769, 1615500189, 554679511, 1192577619, 1410131110, 460258076, 2005607917, 784229619, 544217376, 1480042919, 632005205, 774414016, 912838049, 1162339639, 1547495552, 1900691439, 418675046, 717205599, 1547674161, 1964161403, 1777795839, 23215881, 223231138, 312665187, 738339747, 782913119, 1491681022, 1824115795, 1856545343, 787294362, 132263909, 145223211, 1134936783, 1306647885, 1481204474, 1447878580, 280665754, 337843911, 1045349508, 1311163986, 1422331819, 566566526, 785796932, 1382529621, 1178581019, 238307585, 919403943, 278567984, 717108123, 1841793612, 1705576440, 23671793, 247018910, 978572249, 1159345101, 1288100093, 967052682, 622008867, 79020822, 144808788, 367148769, 453260585, 675346434, 1237104841, 928726468, 238789043, 1829967362, 582026550, 1425450773, 912367292, 110965872, 184201817, 330889263, 1319549233, 1467656730, 1481952135, 316702096, 1141787051, 993707905, 195422490, 1101686277, 923699931, 248075240, 962633646, 905629977, 1222665977, 1966806874, 1156399853, 219624526, 1611842161, 1359339913, 1501019809, 340222871, 1319662041, 559308147, 1446820157, 1468908220, 825224855, 950321114, 694293155, 1392435941, 1673248567, 1398748632, 602504937, 1701668849, 893429879, 259767560, 1685231015, 1737761431, 839599253, 789303395, 1959689139, 642368040, 1193296344, 1459236484, 370291313, 985358226, 403949616, 851601029, 90817999, 522072406, 853026644, 821873058, 1336637787, 1545120835, 616143212, 849624638, 1362884248, 949217896, 1208658013, 1020851608, 1965511734, 1188001972, 1136790769, 775130826, 195958546, 1628335437, 1822130809, 1896793571, 641377353, 1010073824, 1742771357, 1514054684, 1266592989, 1542756987, 263000492, 1854920421, 1815204430, 983783494, 789836419, 1709024775, 61564083, 1923142934, 267530620, 364665014, 1279203298, 1017905375, 523815300, 126061375, 419191953, 1512945445, 1137846698, 1361635720, 1445297234, 891774882, 972918989, 1458654649, 1000130369, 1860733967, 1480888359, 312493468, 387089745, 1058177057, 322825485, 1072187526, 1996962621, 1312408731, 375944201, 1964664562, 1041430005, 1817459975, 1828889379, 118524575, 542094841, 1489739020, 318754226, 1796202227, 1765657525, 1364955234, 446497382, 1514074518, 1537189756, 176704060, 1506372075, 646509327, 212597403, 929105609, 234094801, 1004117819, 60222659, 1967436205, 914676306, 1214352228, 561153355, 1651123459, 533706621, 752427352, 28665618, 1766800858, 1452434999, 861624660, 1861675199, 1055921913, 467611061, 63788306, 367628582, 271811198, 242033074, 1715780319, 633021876, 516692005, 347997613, 1947940162, 1030796471, 1884084245, 1970207237, 1164949965, 980071634, 615517092, 1968296822, 773874696, 428315196, 206082808, 1374421511, 1693787385, 1818664992, 1985348401, 833442481, 1121868036, 1088774168, 1021004980, 505547537, 1792276534, 610750027, 1990875465, 1993235394, 341742893, 410604508, 336595508, 1675467435, 398532280, 412086128, 1018421146, 1397143687, 714277787, 1564480008, 1037489962, 1635566417, 1395242502, 1197830170, 1502836897, 790194434, 1050533831, 980825062, 254852752, 1912131620, 1857765135, 1392924859, 1158979160, 1931916621, 1539987310, 1385597195, 1643615825, 901164724, 1924932541, 866910121, 1643993526, 1774715771, 1201618857, 1137453511, 1141080023, 1334069533, 1319162119, 1344304245, 1653077195, 963480260, 360968134, 1222831386, 1801786086, 1054808915, 1211732826, 751755361, 431682106, 551143834, 1783920278, 1862658473, 1874357167, 1764692550, 213084457, 321447919, 735568739, 1625234061, 786685642, 953619459, 1059942796, 1810643372, 1172452124, 1617995135, 1806603279, 15983468, 158711712, 454536672, 1990693935, 844944403, 810586732, 286377538, 892127802, 1987950424, 320243431, 1594130339, 386876004, 676914911, 1059419487, 672241568, 142332549, 1976353322, 1088180068, 1022164082, 1160863821, 764399745, 1222623956, 1284414673, 1276691224, 1627039122, 711454996, 317270776, 974879690, 933678835, 249037385, 152560134, 496879956, 896569804, 520305435, 1578045958, 1036609758, 1264574707, 1832692152, 1279505637, 1807506522, 639635195, 514853946, 1060108843, 346980260, 551985489, 554304165, 1934999141, 510575142, 497552681, 1186699372, 833779107, 1221355858, 1803036115, 99635052, 667187770, 54432900, 1792500985, 1378950494, 1343628866, 1361274146, 678446776, 1187232554, 1436326157, 1592906921, 127007666, 1916773346, 679243319, 1962346315, 1793253533, 38085069, 1391277383, 1139470663, 956233886, 1046371821, 69767869, 588637337, 1657606228, 1058996195, 1610058410, 1034717894, 1418689847, 1912168001, 1972385571, 656988550, 1161552796, 1830833724, 1623981083, 2001888260, 1787374314, 1211524598, 1679927652, 640946950, 341696196, 545259856, 1345770928, 1265597135, 234608688, 1008126754, 1170333511, 599469246, 855799849, 1115843845, 196815165, 1829830690, 890014303, 933755451, 622931004, 1719140322, 1244518808, 40156510, 1375344650, 1773270930, 1527885930, 1653660090, 1410766425, 595435314, 87882432, 138024182, 1889147436, 997572764, 1892123295, 1374785473, 848357025, 72043095, 1001927394, 1848425832, 1894113639, 1102681299, 1846999024, 1396500869, 1314064070, 935530766, 1201859359, 1939494964, 11903114, 623991846, 1089218665, 1313302657, 205164863, 1685765963, 625696311, 1017211815, 930624567, 397836974, 726313431, 1320262432, 1238752659, 162397637, 208652375, 242076122, 1430650164, 970136676, 31880356, 1110595814, 1847943781, 1922518609, 1363315494, 1066431106, 199640713, 1870533459, 983021145, 1065754297, 1466785260, 1097291622, 338778641, 1052363365, 1320747678, 239770140, 971306119, 475210120], + 8380417 : [1, 4808194, 3765607, 3761513, 5178923, 5496691, 5234739, 5178987, 601683, 4837932, 5698129, 6250525, 4615550, 1005239, 7822959, 1221177, 4317364, 3370349, 6705802, 5717039, 7946292, 3524442, 7044481, 7703827, 6666122, 5152541, 6919699, 2453983, 3585098, 7737789, 6096684, 2815639, 2740543, 4793971, 3182878, 4778199, 3704823, 1159875, 2101410, 3110818, 7284949, 3506380, 7451668, 394148, 5138445, 6224367, 4018989, 2071829, 4805951, 817536, 4965348, 6621070, 4430364, 1716988, 4623627, 1935799, 4510100, 556856, 5483103, 3192354, 6522001, 3345963, 2917338, 1853806, 4197045, 3222807, 3121440, 274060, 2635473, 4528402, 7102792, 3073009, 2811291, 5396636, 7270901, 4158088, 1937570, 4564692, 2028118, 5871437, 8210729, 7814814, 3334383, 2462444, 2962264, 1148858, 1528066, 482649, 235407, 6644104, 3258457, 3250154, 5183169, 6392603, 4166425, 3488383, 1300016, 2362063, 4182915, 3482206, 6592474, 5801164, 2254727, 5989328, 2925816, 3374250, 7153756, 4478945, 2461387, 1317678, 621164, 5344437, 459163, 7727142, 4912752, 2312838, 1356448, 5604662, 2683270, 5601629, 1744507, 2236726, 3818627, 1922253, 8031605, 327848, 7369194, 2354215, 8293209, 1182243, 3965306, 636927, 2296397, 4423672, 3716946, 5095502, 6600190, 5720009, 2772600, 59148, 6924527, 2659525, 8378664, 1935420, 6695264, 4969849, 2678278, 4611469, 4829411, 635956, 8129971, 5925040, 1979497, 6783595, 3956944, 3759465, 7371052, 2454145, 7557876, 8352605, 3838479, 1239911, 3747250, 2296099, 12417, 1254190, 3195676, 2642980, 5199961, 268456, 5992904, 3611750, 4146264, 1772588, 6187479, 1727088, 4040196, 4908348, 6458423, 458740, 7561656, 6201452, 6500539, 6341273, 1310261, 2218467, 7767179, 7025525, 8238582, 2998219, 5867399, 8291116, 7192532, 7872490, 6545891, 724804, 6987258, 5889092, 1476985, 6386371, 2033807, 6006015, 6757063, 2105286, 2743411, 527981, 586241, 7200804, 6715099, 6352379, 7216819, 3369273, 1615530, 6657188, 5811406, 3980599, 2647994, 3009748, 8041997, 3033742, 749577, 4357667, 4148469, 2612853, 6866265, 8240173, 7520273, 4949981, 5274859, 1103344, 7872272, 553718, 3994671, 8368538, 7009900, 3020393, 3363542, 214880, 545376, 7609976, 7325939, 731434, 781875, 1900052, 4849188, 8077412, 3773731, 4405932, 3123762, 2185084, 6187330, 6022044, 6663603, 3014420, 7987710, 5454363], +} + +# The inverses of powers of 2 mod q +n_inv = { + 3329: { + 2: 1665, + 4: 2497, + 8: 2913, + 16: 3121, + 32: 3225, + 64: 3277, + 128: 3303, + }, + 12289: { + 2: 6145, + 4: 9217, + 8: 10753, + 16: 11521, + 32: 11905, + 64: 12097, + 128: 12193, + 256: 12241, + 512: 12265, + 1024: 12277, + 2048: 12283, + }, + 2013265921: { + 2: 1006632961, + 4: 1509949441, + 8: 1761607681, + 16: 1887436801, + 32: 1950351361, + 64: 1981808641, + 128: 1997537281, + 256: 2005401601, + 512: 2009333761, + 1024: 2011299841, + }, + 8380417: { + 2: 4190209, + 4: 6285313, + 8: 7332865, + 16: 7856641, + 32: 8118529, + 64: 8249473, + 128: 8314945, + 256: 8347681, + 512: 8364049, + }, +} \ No newline at end of file diff --git a/assets/eip-NTT/pythonref/polyntt/ntt_constants_recursive.py b/assets/eip-NTT/pythonref/polyntt/ntt_constants_recursive.py new file mode 100644 index 00000000000000..4d661a680812c2 --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/ntt_constants_recursive.py @@ -0,0 +1,46 @@ +# Roots of the cyclotomic polynomials mod q for the recursive ntt implementation +# File generated using `generate_contants_recursive.sage` +# roots_dict_mod[q][n] corresponds to the roots of x^{2n} + 1 mod q +roots_dict_mod = { + 3329: { + 2 : [1600, 1729], + 4 : [40, 3289, 749, 2580], + 8 : [2481, 848, 1432, 1897, 2699, 630, 687, 2642], + 16 : [1746, 1583, 569, 2760, 3260, 69, 2786, 543, 797, 2532, 193, 3136, 1919, 1410, 1062, 2267], + 32 : [1974, 1355, 2508, 821, 2393, 936, 450, 2879, 535, 2794, 447, 2882, 1426, 1903, 1235, 2094, 1089, 2240, 1333, 1996, 283, 3046, 56, 3273, 1990, 1339, 1476, 1853, 3033, 296, 2447, 882], + 64 : [2474, 855, 219, 3110, 2419, 910, 2102, 1227, 2647, 682, 712, 2617, 2681, 648, 1848, 1481, 1891, 1438, 2868, 461, 2402, 927, 1534, 1795, 1010, 2319, 1435, 1894, 452, 2877, 807, 2522, 33, 3296, 2865, 464, 1320, 2009, 1414, 1915, 1977, 1352, 650, 2679, 2513, 816, 2697, 632, 1025, 2304, 2132, 1197, 1052, 2277, 2055, 1274, 2998, 331, 3040, 289, 76, 3253, 1756, 1573], + }, + 12289: { + 2 : [1479, 10810], + 4 : [4043, 8246, 7143, 5146], + 8 : [6553, 5736, 8155, 4134, 10984, 1305, 11567, 722], + 16 : [11077, 1212, 1646, 10643, 3195, 9094, 6429, 5860, 8747, 3542, 8785, 3504, 8668, 3621, 2545, 9744], + 32 : [6461, 5828, 7266, 5023, 7698, 4591, 5728, 6561, 3328, 8961, 6512, 5777, 10938, 1351, 4978, 7311, 9650, 2639, 4821, 7468, 9664, 2625, 949, 11340, 9545, 2744, 9283, 3006, 2975, 9314, 563, 11726], + 64 : [9923, 2366, 3051, 9238, 7393, 4896, 9326, 2963, 4320, 7969, 11289, 1000, 3091, 9198, 81, 12208, 4255, 8034, 1177, 11112, 10654, 1635, 2768, 9521, 11563, 726, 7678, 4611, 1853, 10436, 140, 12149, 790, 11499, 955, 11334, 11119, 1170, 2319, 9970, 3201, 9088, 3014, 9275, 1326, 10963, 7203, 5086, 1062, 11227, 9995, 2294, 4805, 7484, 3553, 8736, 3712, 8577, 9154, 3135, 2747, 9542, 7443, 4846], + 128 : [9764, 2525, 1381, 10908, 3584, 8705, 4177, 8112, 6958, 5331, 4989, 7300, 1673, 10616, 4278, 8011, 339, 11950, 9821, 2468, 6498, 5791, 544, 11745, 9447, 2842, 11809, 480, 9, 12280, 1022, 11267, 5767, 6522, 827, 11462, 3748, 8541, 953, 11336, 2476, 9813, 12171, 118, 7222, 5067, 2197, 10092, 2837, 9452, 5374, 6915, 4354, 7935, 130, 12159, 9893, 2396, 7837, 4452, 8993, 3296, 3949, 8340, 1696, 10593, 1428, 10861, 11955, 334, 9863, 2426, 4632, 7657, 5755, 6534, 11029, 1260, 4388, 7901, 9000, 3289, 2013, 10276, 11560, 729, 3241, 9048, 2089, 10200, 5092, 7197, 3284, 9005, 2881, 9408, 8357, 3932, 9558, 2731, 4890, 7399, 6378, 5911, 3637, 8652, 8830, 3459, 6747, 5542, 145, 12144, 9741, 2548, 4231, 8058, 8907, 3382, 11934, 355, 3707, 8582, 1759, 10530, 7110, 5179, 8595, 3694], + 256 : [4895, 7394, 1484, 10805, 5195, 7094, 2780, 9509, 2645, 9644, 4053, 8236, 2305, 9984, 5042, 7247, 2847, 9442, 7875, 4414, 7917, 4372, 10115, 2174, 1689, 10600, 3364, 8925, 8232, 4057, 9018, 3271, 6998, 5291, 2704, 9585, 3636, 8653, 7351, 4938, 7635, 4654, 10863, 1426, 10626, 1663, 10512, 1777, 10123, 2166, 3915, 8374, 4919, 7370, 113, 12176, 12286, 3, 7852, 4437, 160, 12129, 3149, 9140, 6957, 5332, 3510, 8779, 9919, 2370, 9424, 2865, 9320, 2969, 8311, 3978, 2686, 9603, 3247, 9042, 10659, 1630, 10163, 2126, 9103, 3186, 6882, 5407, 10040, 2249, 4048, 8241, 1153, 11136, 9405, 2884, 8304, 3985, 4905, 7384, 11813, 476, 8758, 3531, 420, 11869, 6730, 5559, 2178, 10111, 1544, 10745, 243, 12046, 3016, 9273, 11618, 671, 3000, 9289, 7098, 5191, 3136, 9153, 2399, 9890, 8889, 3400, 2859, 9430, 1045, 11244, 7277, 5012, 9808, 2481, 6591, 5698, 2912, 9377, 4861, 7428, 354, 11935, 390, 11899, 11516, 773, 3778, 8511, 8456, 3833, 11847, 442, 9888, 2401, 7188, 5101, 1067, 11222, 1632, 10657, 5084, 7205, 11272, 1017, 7404, 4885, 3066, 9223, 12262, 27, 8526, 3763, 1440, 10849, 545, 11744, 7270, 5019, 3704, 8585, 9611, 2678, 7575, 4714, 8146, 4143, 1537, 10752, 12047, 242, 6845, 5444, 9908, 2381, 11796, 493, 8193, 4096, 435, 11854, 4337, 7952, 1378, 10911, 10377, 1912, 11224, 1065, 10146, 2143, 7644, 4645, 11885, 404, 1207, 11082, 3248, 9041, 1168, 11121, 7012, 5277, 9723, 2566, 2187, 10102, 9867, 2422, 6250, 6039, 8643, 3646, 2437, 9852, 6022, 6267, 9302, 2987, 875, 11414, 3780, 8509, 10682, 1607, 7313, 4976, 7201, 5088, 8005, 4284, 1002, 11287, 7278, 5011], + 512 : [3263, 9026, 8689, 3600, 6212, 6077, 7665, 4624, 11868, 421, 4080, 8209, 6068, 6221, 3602, 8687, 2302, 9987, 605, 11684, 4213, 8076, 504, 11785, 6403, 5886, 7507, 4782, 6695, 5594, 9260, 3029, 6608, 5681, 3477, 8812, 12147, 142, 11184, 1105, 8077, 4212, 975, 11314, 3438, 8851, 9445, 2844, 3532, 8757, 1003, 11286, 58, 12231, 12048, 241, 5009, 7280, 10333, 1956, 11404, 885, 6008, 6281, 9523, 2766, 1323, 10966, 52, 12237, 3174, 9115, 677, 11612, 5874, 6415, 8953, 3336, 6234, 6055, 9784, 2505, 6383, 5906, 10710, 1579, 11858, 431, 2839, 9450, 8332, 3957, 151, 12138, 2127, 10162, 9369, 2920, 7048, 5241, 4169, 8120, 9162, 3127, 11502, 787, 3482, 8807, 1010, 11279, 6821, 5468, 12097, 192, 10968, 1321, 10240, 2049, 4912, 7377, 7591, 4698, 7232, 5057, 4780, 7509, 3445, 8844, 6844, 5445, 8429, 3860, 7753, 4536, 1050, 11239, 6171, 6118, 8471, 3818, 2683, 9606, 11099, 1190, 147, 12142, 8500, 3789, 4449, 7840, 5456, 6833, 4749, 7540, 6752, 5537, 4789, 7500, 4467, 7822, 3262, 9027, 7210, 5079, 2169, 10120, 522, 11767, 5315, 6974, 8214, 4075, 7373, 4916, 4324, 7965, 3514, 8775, 11248, 1041, 1018, 11271, 6364, 5925, 9945, 2344, 11011, 1278, 10316, 1973, 6715, 5574, 3998, 8291, 2033, 10256, 3879, 8410, 10367, 1922, 11035, 1254, 973, 11316, 5435, 6854, 1359, 10930, 8579, 3710, 6093, 6196, 5339, 6950, 6843, 5446, 8301, 3988, 468, 11821, 11973, 316, 11907, 382, 11889, 400, 10561, 1728, 4948, 7341, 6137, 6152, 8646, 3643, 6874, 5415, 5862, 6427, 6153, 6136, 5529, 6760, 5206, 7083, 56, 12233, 9090, 3199, 3565, 8724, 654, 11635, 10587, 1702, 1987, 10302, 8974, 3315, 426, 11863, 4754, 7535, 1858, 10431, 3757, 8532, 1975, 10314, 347, 11942, 9364, 2925, 11566, 723, 12115, 174, 1693, 10596, 9280, 3009, 5735, 6554, 2655, 9634, 9551, 2738, 5868, 6421, 1512, 10777, 11939, 350, 5383, 6906, 10474, 1815, 3202, 9087, 4493, 7796, 5369, 6920, 2057, 10232, 10806, 1483, 6374, 5915, 1263, 11026, 49, 12240, 2500, 9789, 10800, 1489, 5942, 6347, 1583, 10706, 2908, 9381, 12071, 218, 8760, 3529, 3434, 8855, 8174, 4115, 9259, 3030, 2361, 9928, 1843, 10446, 2447, 9842, 6147, 6142, 576, 11713, 3963, 8326, 10335, 1954, 10238, 2051, 1805, 10484, 2882, 9407, 10996, 1293, 4737, 7552, 7515, 4774, 5429, 6860, 6381, 5908, 11836, 453, 3772, 8517, 11871, 418, 6413, 5876, 10008, 2281, 10258, 2031, 6956, 5333, 8298, 3991, 8320, 3969, 12133, 156, 2767, 9522, 1566, 10723, 5782, 6507, 2503, 9786, 2948, 9341, 683, 11606, 2459, 9830, 8633, 3656, 12225, 64, 6803, 5486, 9235, 3054, 1747, 10542, 3123, 9166, 7856, 4433, 5919, 6370, 7032, 5257, 3834, 8455, 4079, 8210, 11231, 1058, 11848, 441, 11367, 922, 1112, 11177, 10211, 2078, 10331, 1958, 4322, 7967, 8719, 3570, 4240, 8049, 6065, 6224, 11454, 835, 4046, 8243, 11580, 709, 1319, 10970, 9139, 3150, 6122, 6167, 9734, 2555, 1200, 11089, 5184, 7105, 6170, 6119, 6992, 5297, 10929, 1360, 3956, 8333, 2692, 9597, 12121, 168, 7991, 4298, 8960, 3329, 5961, 6328, 5106, 7183, 1594, 10695, 10327, 1962, 8240, 4049, 8561, 3728, 11130, 1159, 6299, 5990, 11143, 1146, 948, 11341, 11964, 325, 10885, 1404, 4077, 8212, 8273, 4016, 3762, 8527, 9370, 2919, 295, 11994, 6190, 6099, 652, 11637, 5766, 6523], + 1024 : [9089, 3200, 10754, 1535, 2717, 9572, 12229, 60, 7723, 4566, 5836, 6453, 10029, 2260, 68, 12221, 7365, 4924, 4781, 7508, 448, 11841, 11275, 1014, 3942, 8347, 5232, 7057, 10962, 1327, 3607, 8682, 7187, 5102, 11877, 412, 5845, 6444, 5588, 6701, 4963, 7326, 3744, 8545, 9761, 2528, 9233, 3056, 2257, 10032, 7784, 4505, 6613, 5676, 10872, 1417, 6454, 5835, 9202, 3087, 3975, 8314, 4883, 7406, 1176, 11113, 6555, 5734, 11014, 1275, 6781, 5508, 1125, 11164, 4860, 7429, 1445, 10844, 11158, 1131, 212, 12077, 6323, 5966, 9175, 3114, 2769, 9520, 579, 11710, 8400, 3889, 5987, 6302, 6693, 5596, 3534, 8755, 3961, 8328, 8144, 4145, 1756, 10533, 5826, 6463, 2065, 10224, 8794, 3495, 4564, 7725, 5653, 6636, 4267, 8022, 9828, 2461, 10014, 2275, 5063, 7226, 4176, 8113, 8524, 3765, 10771, 1518, 6127, 6162, 4840, 7449, 9126, 3163, 4032, 8257, 2068, 10221, 10900, 1389, 4404, 7885, 346, 11943, 8921, 3368, 8062, 4227, 11677, 612, 4238, 8051, 540, 11749, 12164, 125, 8067, 4222, 10763, 1526, 3678, 8611, 8024, 4265, 464, 11825, 10361, 1928, 3205, 9084, 8930, 3359, 5209, 7080, 11197, 1092, 3171, 9118, 7800, 4489, 2926, 9363, 1826, 10463, 11153, 1136, 3449, 8840, 3238, 9051, 8581, 3708, 4538, 7751, 1908, 10381, 11946, 343, 8841, 3448, 10423, 1866, 5211, 7078, 1208, 11081, 4727, 7562, 5416, 6873, 10125, 2164, 10179, 2110, 716, 11573, 416, 11873, 814, 11475, 10584, 1705, 9839, 2450, 10753, 1536, 1721, 10568, 8186, 4103, 2429, 9860, 11572, 717, 8700, 3589, 1373, 10916, 2982, 9307, 5993, 6296, 3278, 9011, 8080, 4209, 5412, 6877, 8774, 3515, 11851, 438, 7228, 5061, 11071, 1218, 4475, 7814, 7043, 5246, 3017, 9272, 1236, 11053, 3121, 9168, 7584, 4705, 9689, 2600, 1057, 11232, 8038, 4251, 4739, 7550, 5518, 6771, 1226, 11063, 2360, 9929, 364, 11925, 5216, 7073, 9261, 3028, 9247, 3042, 10945, 1344, 2483, 9806, 10235, 2054, 10821, 1468, 3981, 8308, 463, 11826, 8882, 3407, 204, 12085, 6780, 5509, 1409, 10880, 7070, 5219, 9600, 2689, 4605, 7684, 4138, 8151, 180, 12109, 5268, 7021, 146, 12143, 1687, 10602, 406, 11883, 1403, 10886, 10485, 1804, 7100, 5189, 6094, 6195, 5464, 6825, 7383, 4906, 7619, 4670, 11777, 512, 7735, 4554, 11295, 994, 9389, 2900, 12050, 239, 8307, 3982, 9342, 2947, 11653, 636, 5609, 6680, 7790, 4499, 6617, 5672, 10552, 1737, 11667, 622, 8896, 3393, 7954, 4335, 8914, 3375, 9998, 2291, 8761, 3528, 4913, 7376, 3825, 8464, 4235, 8054, 6505, 5784, 10897, 1392, 1255, 11034, 506, 11783, 9013, 3276, 8951, 3338, 2674, 9615, 10077, 2212, 5478, 6811, 3511, 8778, 2776, 9513, 1178, 11111, 1165, 11124, 2575, 9714, 3408, 8881, 1942, 10347, 425, 11864, 1836, 10453, 10104, 2185, 392, 11897, 7711, 4578, 377, 11912, 10669, 1620, 375, 11914, 1038, 11251, 11366, 923, 6085, 6204, 4167, 8122, 6197, 6092, 10058, 2231, 9489, 2800, 193, 12096, 7287, 5002, 20, 12269, 4608, 7681, 7126, 5163, 8946, 3343, 8170, 4119, 2151, 10138, 10767, 1522, 3947, 8342, 338, 11951, 6599, 5690, 2455, 9834, 8635, 3654, 2894, 9395, 10545, 1744, 1314, 10975, 2148, 10141, 6330, 5959, 8330, 3959, 6492, 5797, 4939, 7350, 5115, 7174, 11041, 1248, 9847, 2442, 1892, 10397, 8665, 3624, 5598, 6691, 8945, 3344, 10964, 1325, 6565, 5724, 1029, 11260, 10344, 1945, 4050, 8239, 5207, 7082, 5202, 7087, 844, 11445, 7699, 4590, 7207, 5082, 11309, 980, 682, 11607, 7000, 5289, 5662, 6627, 11722, 567, 9348, 2941, 8452, 3837, 2595, 9694, 8016, 4273, 9068, 3221, 3769, 8520, 7434, 4855, 11996, 293, 9057, 3232, 9656, 2633, 1406, 10883, 9344, 2945, 6940, 5349, 3480, 8809, 10118, 2171, 11024, 1265, 9282, 3007, 8345, 3944, 4099, 8190, 5530, 6759, 6685, 5604, 3120, 9169, 6105, 6184, 5646, 6643, 6203, 6086, 8753, 3536, 5370, 6919, 8348, 3941, 8536, 3753, 3572, 8717, 11007, 1282, 2021, 10268, 2832, 9457, 9060, 3229, 4730, 7559, 8360, 3929, 1706, 10583, 4360, 7929, 9004, 3285, 5054, 7235, 3154, 9135, 11444, 845, 3723, 8566, 7, 12282, 10353, 1936, 12239, 50, 12073, 216, 6763, 5526, 11520, 769, 4153, 8136, 10076, 2213, 3805, 8484, 11522, 767, 10487, 1802, 1555, 10734, 1891, 10398, 7186, 5103, 1223, 11066, 2334, 9955, 4411, 7878, 10699, 1590, 8871, 3418, 7846, 4443, 6151, 6138, 3469, 8820, 4693, 7596, 9951, 2338, 11872, 417, 9996, 2293, 7250, 5039, 6742, 5547, 2485, 9804, 904, 11385, 12265, 24, 1371, 10918, 1280, 11009, 614, 11675, 11924, 365, 881, 11408, 11274, 1015, 10362, 1927, 4510, 7779, 9652, 2637, 9343, 2946, 5461, 6828, 11538, 751, 7570, 4719, 11379, 910, 5900, 6389, 6586, 5703, 7806, 4483, 9224, 3065, 1506, 10783, 826, 11463, 5043, 7246, 9199, 3090, 1398, 10891, 5618, 6671, 1658, 10631, 3502, 8787, 5789, 6500, 1944, 10345, 11839, 450, 6921, 5368, 11711, 578, 7628, 4661, 510, 11779, 6903, 5386, 9667, 2622, 3360, 8929, 4684, 7605, 5135, 7154, 63, 12226, 8481, 3808, 8619, 3670, 2373, 9916, 7302, 4987, 2593, 9696, 879, 11410, 982, 11307, 2276, 10013, 8531, 3758, 8835, 3454, 7899, 4390, 8071, 4218, 3268, 9021, 3795, 8494, 1849, 10440, 6513, 5776, 7766, 4523, 7988, 4301, 11832, 457, 12281, 8, 3466, 8823, 1701, 10588, 3578, 8711, 7592, 4697, 2626, 9663, 530, 11759, 11511, 778, 4504, 7785, 2046, 10243, 2940, 9349, 1481, 10808, 2957, 9332, 139, 12150, 8957, 3332, 8972, 3317, 9757, 2532, 9462, 2827, 9416, 2873, 11498, 791, 9855, 2434, 6481, 5808, 12268, 21, 2535, 9754, 1120, 11169, 9982, 2307, 4289, 8000, 150, 12139, 648, 11641, 9988, 2301, 874, 11415, 12119, 170, 6639, 5650, 8496, 3793, 6226, 6063, 1573, 10716, 3846, 8443, 5118, 7171, 11787, 502, 9687, 2602, 10388, 1901, 1030, 11259, 11823, 466, 10608, 1681, 8468, 3821, 2929, 9360, 6263, 6026, 7640, 4649, 5969, 6320, 2712, 9577, 4834, 7455, 2828, 9461, 4352, 7937, 1842, 10447, 8449, 3840, 72, 12217, 8176, 4113, 6508, 5781, 3045, 9244, 1095, 11194, 9646, 2643, 4094, 8195, 8838, 3451, 11048, 1241, 7911, 4378, 10407, 1882, 6125, 6164, 10254, 2035, 1040, 11249, 5410, 6879, 1251, 11038, 10499, 1790, 7014, 5275, 7519, 4770, 11345, 944, 8620, 3669, 5287, 7002, 5406, 6883, 7624, 4665, 6616, 5673, 3020, 9269, 10555, 1734, 3815, 8474, 6457, 5832, 1350, 10939, 4423, 7866, 3869, 8420, 1694, 10595, 10759, 1530, 189, 12100, 9173, 3116, 2209, 10080, 10526, 1763, 9617, 2672, 5170, 7119, 11424, 865, 11010, 1279, 4194, 8095, 9270, 3019, 9811, 2478, 9449, 2840, 5078, 7211, 1783, 10506, 7724, 4565, 7315, 4974, 4518, 7771, 9195, 3094, 4820, 7469, 1160, 11129, 2253, 10036, 1868, 10421, 2730, 9559, 6878, 5411], + }, + 2013265921: { + 2 : [1728404513, 284861408], + 4 : [420899707, 1592366214, 1801542727, 211723194], + 8 : [1400279418, 612986503, 1816869661, 196396260, 567209306, 1446056615, 78945800, 1934320121], + 16 : [177390144, 1835875777, 1424376889, 588889032, 1123955347, 889310574, 760005850, 1253260071, 740045640, 1273220281, 614075160, 1399190761, 1240658731, 772607190, 1561292356, 451973565], + 32 : [492152090, 1521113831, 95586718, 1917679203, 406991886, 1606274035, 1957706687, 55559234, 686375053, 1326890868, 602251207, 1411014714, 1721589904, 291676017, 1941224298, 72041623, 918899846, 1094366075, 1351065666, 662200255, 1171812390, 841453531, 939236864, 1074029057, 1141518129, 871747792, 1724976031, 288289890, 328902512, 1684363409, 1333217202, 680048719], + 64 : [533223002, 1480042919, 1381260716, 632005205, 544217376, 1469048545, 1229036302, 784229619, 465770369, 1547495552, 1162339639, 850926282, 1238851905, 774414016, 1100427872, 912838049, 1410131110, 603134811, 820688302, 1192577619, 7658004, 2005607917, 460258076, 1553007845, 397765732, 1615500189, 1458586410, 554679511, 538704769, 1474561152, 921820247, 1091445674, 1881002012, 132263909, 787294362, 1225971559, 189150126, 1824115795, 156720578, 1856545343, 738339747, 1274926174, 1700600734, 312665187, 521584899, 1491681022, 782913119, 1230352802, 1296060322, 717205599, 465591760, 1547674161, 418675046, 1594590875, 112574482, 1900691439, 1790034783, 223231138, 23215881, 1990050040, 49104518, 1964161403, 235470082, 1777795839], + 128 : [1766247011, 247018910, 23671793, 1989594128, 1159345101, 853920820, 1034693672, 978572249, 1841793612, 171472309, 1705576440, 307689481, 1296157798, 717108123, 278567984, 1734697937, 675346434, 1337919487, 1560005336, 453260585, 144808788, 1868457133, 367148769, 1646117152, 1046213239, 967052682, 1288100093, 725165828, 79020822, 1934245099, 1391257054, 622008867, 566566526, 1446699395, 785796932, 1227468989, 590934102, 1422331819, 1311163986, 702101935, 919403943, 1093861978, 1774958336, 238307585, 1382529621, 630736300, 1178581019, 834684902, 1732600167, 280665754, 1447878580, 565387341, 1045349508, 967916413, 1675422010, 337843911, 1306647885, 706618036, 1481204474, 532061447, 878329138, 1134936783, 145223211, 1868042710, 559308147, 1453957774, 693603880, 1319662041, 1501019809, 512246112, 340222871, 1673043050, 1793641395, 219624526, 1156399853, 856866068, 1359339913, 653926008, 401423760, 1611842161, 923699931, 1089565990, 248075240, 1765190681, 911579644, 1101686277, 195422490, 1817843431, 1966806874, 46459047, 790599944, 1222665977, 962633646, 1050632275, 905629977, 1107635944, 587815148, 1425450773, 582026550, 1431239371, 110965872, 1902300049, 1100898629, 912367292, 238789043, 1774476878, 1829967362, 183298559, 1084539453, 928726468, 1237104841, 776161080, 993707905, 1019558016, 871478870, 1141787051, 1481952135, 531313786, 316702096, 1696563825, 1682376658, 330889263, 184201817, 1829064104, 1467656730, 545609191, 693716688, 1319549233], + 256 : [116472350, 1896793571, 1822130809, 191135112, 1817307375, 195958546, 384930484, 1628335437, 1742771357, 270494564, 1514054684, 499211237, 1003192097, 1010073824, 641377353, 1371888568, 47754187, 1965511734, 825263949, 1188001972, 1136790769, 876475152, 775130826, 1238135095, 992414313, 1020851608, 1208658013, 804607908, 650381673, 1362884248, 1064048025, 949217896, 523815300, 1489450621, 126061375, 1887204546, 995360546, 1017905375, 1279203298, 734062623, 1951701838, 61564083, 90122987, 1923142934, 267530620, 1745735301, 364665014, 1648600907, 158345500, 1854920421, 263000492, 1750265429, 746672932, 1266592989, 470508934, 1542756987, 789836419, 1223429502, 1709024775, 304241146, 1029482427, 983783494, 1815204430, 198061491, 1642974608, 370291313, 1027907695, 985358226, 403949616, 1609316305, 851601029, 1161664892, 554029437, 1459236484, 1193296344, 819969577, 53576782, 1959689139, 1370897881, 642368040, 616143212, 1397122709, 849624638, 1163641283, 468145086, 1545120835, 1336637787, 676628134, 1922447922, 90817999, 1491193515, 522072406, 853026644, 1160239277, 821873058, 1191392863, 1753498361, 259767560, 893429879, 1119836042, 1410760984, 602504937, 311597072, 1701668849, 839599253, 1173666668, 789303395, 1223962526, 275504490, 1737761431, 1685231015, 328034906, 1318972766, 694293155, 620829980, 1392435941, 1673248567, 340017354, 1398748632, 614517289, 1062944807, 950321114, 825224855, 1188041066, 566445764, 1446820157, 544357701, 1468908220, 1665268308, 347997613, 65325759, 1947940162, 516692005, 1496573916, 1380244045, 633021876, 367628582, 1645637339, 271811198, 1741454723, 1771232847, 242033074, 297485602, 1715780319, 861624660, 1151641261, 560830922, 1452434999, 28665618, 1984600303, 1766800858, 246465063, 1545654860, 467611061, 1949477615, 63788306, 1055921913, 957344008, 151590722, 1861675199, 212597403, 1800668518, 929105609, 1084160312, 1779171120, 234094801, 1009148102, 1004117819, 646509327, 1366756594, 506893846, 1506372075, 1537189756, 476076165, 176704060, 1836561861, 1479559300, 533706621, 1260838569, 752427352, 1651123459, 362142462, 1452112566, 561153355, 60222659, 1953043262, 1967436205, 45829716, 1098589615, 914676306, 798913693, 1214352228, 312493468, 1700772453, 532377562, 1480888359, 1000130369, 1013135552, 1860733967, 152531954, 1690440436, 322825485, 941078395, 1072187526, 1058177057, 955088864, 1626176176, 387089745, 1445297234, 567968687, 891774882, 1121491039, 1040346932, 972918989, 554611272, 1458654649, 1361635720, 651630201, 875419223, 1137846698, 419191953, 1594073968, 1512945445, 500320476, 1566768539, 446497382, 499191403, 1514074518, 1364955234, 648310687, 247608396, 1765657525, 542094841, 1471171080, 1489739020, 523526901, 1694511695, 318754226, 217063694, 1796202227, 1964664562, 48601359, 1637321720, 375944201, 1996962621, 16303300, 1312408731, 700857190, 184376542, 1828889379, 1894741346, 118524575, 1817459975, 195805946, 971835916, 1041430005], + 512 : [158711712, 1854554209, 1997282453, 15983468, 1617995135, 395270786, 1806603279, 206662642, 953323125, 1059942796, 953619459, 1059646462, 1172452124, 840813797, 202622549, 1810643372, 286377538, 1726888383, 892127802, 1121138119, 25315497, 1987950424, 1693022490, 320243431, 810586732, 1202679189, 1168321518, 844944403, 454536672, 1558729249, 1990693935, 22571986, 138908754, 1874357167, 1862658473, 150607448, 213084457, 1800181464, 248573371, 1764692550, 321447919, 1691818002, 735568739, 1277697182, 388031860, 1625234061, 1226580279, 786685642, 1783920278, 229345643, 1462122087, 551143834, 751755361, 1261510560, 431682106, 1581583815, 211479835, 1801786086, 1222831386, 790434535, 1211732826, 801533095, 958457006, 1054808915, 1279505637, 733760284, 1807506522, 205759399, 1373630726, 639635195, 1498411975, 514853946, 1832692152, 180573769, 748691214, 1264574707, 1578045958, 435219963, 1036609758, 976656163, 1038386231, 974879690, 317270776, 1695995145, 249037385, 1764228536, 1079587086, 933678835, 152560134, 1860705787, 496879956, 1516385965, 1116696117, 896569804, 1492960486, 520305435, 1088180068, 925085853, 36912599, 1976353322, 672241568, 1341024353, 142332549, 1870933372, 1626389917, 386876004, 1594130339, 419135582, 1059419487, 953846434, 1336351010, 676914911, 1284414673, 728851248, 1276691224, 736574697, 386226799, 1627039122, 1301810925, 711454996, 1222623956, 790641965, 1248866176, 764399745, 1022164082, 991101839, 1160863821, 852402100, 254852752, 1758413169, 1032440859, 980825062, 155500786, 1857765135, 1912131620, 101134301, 620341062, 1392924859, 854286761, 1158979160, 1931916621, 81349300, 1539987310, 473278611, 962732090, 1050533831, 790194434, 1223071487, 815435751, 1197830170, 510429024, 1502836897, 1037489962, 975775959, 448785913, 1564480008, 618023419, 1395242502, 1635566417, 377699504, 668961676, 1344304245, 360188726, 1653077195, 963480260, 1049785661, 360968134, 1652297787, 694103802, 1319162119, 1334069533, 679196388, 875812410, 1137453511, 872185898, 1141080023, 1643615825, 369650096, 627668726, 1385597195, 88333380, 1924932541, 901164724, 1112101197, 1146355800, 866910121, 369272395, 1643993526, 1774715771, 238550150, 1201618857, 811647064, 1671523028, 341742893, 1993235394, 20030527, 1402515894, 610750027, 22390456, 1990875465, 1021004980, 992260941, 924491753, 1088774168, 220989387, 1792276534, 505547537, 1507718384, 1601179793, 412086128, 994844775, 1018421146, 1397143687, 616122234, 714277787, 1298988134, 1614733641, 398532280, 1675467435, 337798486, 1602661413, 410604508, 1676670413, 336595508, 206082808, 1807183113, 1584950725, 428315196, 319478536, 1693787385, 1374421511, 638844410, 194600929, 1818664992, 27917520, 1985348401, 833442481, 1179823440, 1121868036, 891397885, 1239391225, 773874696, 1968296822, 44969099, 1033194287, 980071634, 1397748829, 615517092, 1884084245, 129181676, 982469450, 1030796471, 848315956, 1164949965, 1970207237, 43058684, 239770140, 1773495781, 692518243, 1320747678, 1538055801, 475210120, 971306119, 1041959802, 1674487280, 338778641, 960902556, 1052363365, 1097291622, 915974299, 546480661, 1466785260, 1847943781, 165322140, 1922518609, 90747312, 649950427, 1363315494, 946834815, 1066431106, 1870533459, 142732462, 1813625208, 199640713, 947511624, 1065754297, 983021145, 1030244776, 1286952490, 726313431, 693003489, 1320262432, 397836974, 1615428947, 1082641354, 930624567, 205164863, 1808101058, 1685765963, 327499958, 1387569610, 625696311, 996054106, 1017211815, 970136676, 1043129245, 582615757, 1430650164, 902670107, 1110595814, 31880356, 1981385565, 1804613546, 208652375, 1771189799, 242076122, 162397637, 1850868284, 774513262, 1238752659, 1410766425, 602499496, 595435314, 1417830607, 1925383489, 87882432, 1875241739, 138024182, 997572764, 1015693157, 124118485, 1889147436, 638480448, 1374785473, 1892123295, 121142626, 485379991, 1527885930, 359605831, 1653660090, 1773270930, 239994991, 637921271, 1375344650, 622931004, 1390334917, 1719140322, 294125599, 768747113, 1244518808, 1973109411, 40156510, 623991846, 1389274075, 2001362807, 11903114, 699963264, 1313302657, 1089218665, 924047256, 811406562, 1201859359, 73770957, 1939494964, 935530766, 1077735155, 699201851, 1314064070, 848357025, 1164908896, 72043095, 1941222826, 1011338527, 1001927394, 164840089, 1848425832, 1102681299, 910584622, 119152282, 1894113639, 616765052, 1396500869, 1846999024, 166266897, 127007666, 1886258255, 1916773346, 96492575, 420359000, 1592906921, 1436326157, 576939764, 669637055, 1343628866, 651991775, 1361274146, 678446776, 1334819145, 1187232554, 826033367, 873795258, 1139470663, 1391277383, 621988538, 1046371821, 966894100, 1057032035, 956233886, 1793253533, 220012388, 38085069, 1975180852, 50919606, 1962346315, 679243319, 1334022602, 1179486814, 833779107, 791910063, 1221355858, 1803036115, 210229806, 99635052, 1913630869, 1958833021, 54432900, 667187770, 1346078151, 1378950494, 634315427, 220764936, 1792500985, 497552681, 1515713240, 1186699372, 826566549, 1502690779, 510575142, 1934999141, 78266780, 953157078, 1060108843, 1666285661, 346980260, 551985489, 1461280432, 554304165, 1458961756, 183435231, 1829830690, 196815165, 1816450756, 933755451, 1079510470, 1123251618, 890014303, 855799849, 1157466072, 1115843845, 897422076, 1413796675, 599469246, 1170333511, 842932410, 333338269, 1679927652, 1372318971, 640946950, 341696196, 1671569725, 545259856, 1468006065, 747668786, 1265597135, 1345770928, 667494993, 1008126754, 1005139167, 1778657233, 234608688, 1418689847, 594576074, 1912168001, 101097920, 978548027, 1034717894, 1610058410, 403207511, 1943498052, 69767869, 1424628584, 588637337, 1657606228, 355659693, 1058996195, 954269726, 11377661, 2001888260, 1623981083, 389284838, 1211524598, 801741323, 225891607, 1787374314, 1161552796, 851713125, 1830833724, 182432197, 1356277371, 656988550, 1972385571, 40880350], + }, + 8380417: { + 2 : [3572223, 4808194], + 4 : [3761513, 4618904, 3765607, 4614810], + 8 : [5234739, 3145678, 3201430, 5178987, 3201494, 5178923, 5496691, 2883726], + 16 : [3764867, 4615550, 1005239, 7375178, 7159240, 1221177, 557458, 7822959, 3542485, 4837932, 7778734, 601683, 5698129, 2682288, 2129892, 6250525], + 32 : [5152541, 3227876, 6666122, 1714295, 1460718, 6919699, 2453983, 5926434, 2815639, 5564778, 6096684, 2283733, 642628, 7737789, 4795319, 3585098, 2663378, 5717039, 1674615, 6705802, 3370349, 5010068, 4317364, 4063053, 7946292, 434125, 4855975, 3524442, 7703827, 676590, 7044481, 1335936], + 64 : [3415069, 4965348, 6621070, 1759347, 4805951, 3574466, 7562881, 817536, 6663429, 1716988, 3950053, 4430364, 4623627, 3756790, 6444618, 1935799, 2917338, 5463079, 6526611, 1853806, 1858416, 6522001, 3345963, 5034454, 3192354, 5188063, 5483103, 2897314, 7823561, 556856, 3870317, 4510100, 5269599, 3110818, 6279007, 2101410, 1159875, 7220542, 3704823, 4675594, 3182878, 5197539, 3602218, 4778199, 5639874, 2740543, 4793971, 3586446, 1095468, 7284949, 3506380, 4874037, 7986269, 394148, 928749, 7451668, 4361428, 4018989, 2071829, 6308588, 5138445, 3241972, 2156050, 6224367], + 128 : [5801164, 2579253, 6592474, 1787943, 6125690, 2254727, 5989328, 2391089, 7080401, 1300016, 2362063, 6018354, 4898211, 3482206, 4197502, 4182915, 3901472, 4478945, 1226661, 7153756, 3374250, 5006167, 2925816, 5454601, 2461387, 5919030, 7062739, 1317678, 5344437, 3035980, 621164, 7759253, 348812, 8031605, 327848, 8052569, 6026202, 2354215, 1011223, 7369194, 6143691, 2236726, 6635910, 1744507, 3818627, 4561790, 6458164, 1922253, 2683270, 5697147, 2778788, 5601629, 7023969, 1356448, 5604662, 2775755, 2312838, 6067579, 4912752, 3467665, 653275, 7727142, 7921254, 459163, 2508980, 5871437, 6352299, 2028118, 4564692, 3815725, 1937570, 6442847, 7270901, 1109516, 4222329, 4158088, 5569126, 2811291, 5396636, 2983781, 5744944, 2635473, 4528402, 3852015, 5307408, 3073009, 1277625, 7102792, 5157610, 3222807, 4183372, 4197045, 3121440, 5258977, 8106357, 274060, 7814814, 565603, 8210729, 169688, 5046034, 3334383, 2462444, 5917973, 482649, 7897768, 1528066, 6852351, 7231559, 1148858, 5418153, 2962264, 1987814, 6392603, 3197248, 5183169, 4166425, 4213992, 4892034, 3488383, 235407, 8145010, 1736313, 6644104, 3250154, 5130263, 3258457, 5121960], + 256 : [6757063, 1623354, 6275131, 2105286, 6346610, 2033807, 6006015, 2374402, 527981, 7852436, 2743411, 5637006, 7794176, 586241, 7200804, 1179613, 507927, 7872490, 1187885, 7192532, 6545891, 1834526, 7655613, 724804, 1994046, 6386371, 6903432, 1476985, 5889092, 2491325, 6987258, 1393159, 3980599, 4399818, 5811406, 2569011, 1723229, 6657188, 6764887, 1615530, 1163598, 7216819, 3369273, 5011144, 6715099, 1665318, 2028038, 6352379, 2647994, 5732423, 5370669, 3009748, 3033742, 5346675, 8041997, 338420, 4148469, 4231948, 5767564, 2612853, 7630840, 749577, 4357667, 4022750, 6195333, 2185084, 5256655, 3123762, 6187330, 2193087, 2358373, 6022044, 2926054, 5454363, 392707, 7987710, 3014420, 5365997, 6663603, 1716814, 1900052, 6480365, 781875, 7598542, 7648983, 731434, 1054478, 7325939, 3531229, 4849188, 8077412, 303005, 3974485, 4405932, 4606686, 3773731, 3363542, 5016875, 8165537, 214880, 7609976, 770441, 545376, 7835041, 8368538, 11879, 3994671, 4385746, 1370517, 7009900, 3020393, 5360024, 508145, 7872272, 553718, 7826699, 5274859, 3105558, 7277073, 1103344, 3430436, 4949981, 860144, 7520273, 8240173, 140244, 6866265, 1514152, 8352605, 27812, 7557876, 822541, 5926272, 2454145, 1009365, 7371052, 4423473, 3956944, 3759465, 4620952, 1979497, 6400920, 1596822, 6783595, 4829411, 3551006, 7744461, 635956, 5925040, 2455377, 8129971, 250446, 4969849, 3410568, 6695264, 1685153, 5702139, 2678278, 4611469, 3768948, 2660408, 5720009, 1780227, 6600190, 2772600, 5607817, 8321269, 59148, 6444997, 1935420, 1753, 8378664, 2659525, 5720892, 6924527, 1455890, 636927, 7743490, 3965306, 4415111, 7198174, 1182243, 87208, 8293209, 6084020, 2296397, 4423672, 3956745, 3284915, 5095502, 4663471, 3716946, 3747250, 4633167, 6084318, 2296099, 4541938, 3838479, 1239911, 7140506, 1254190, 7126227, 12417, 8368000, 5184741, 3195676, 2642980, 5737437, 2192938, 6187479, 1727088, 6653329, 4146264, 4234153, 6607829, 1772588, 4768667, 3611750, 2387513, 5992904, 268456, 8111961, 5199961, 3180456, 7025525, 1354892, 7767179, 613238, 6161950, 2218467, 7070156, 1310261, 141835, 8238582, 2998219, 5382198, 89301, 8291116, 2513018, 5867399, 4040196, 4340221, 3472069, 4908348, 458740, 7921677, 6458423, 1921994, 6500539, 1879878, 2039144, 6341273, 818761, 7561656, 6201452, 2178965], + }, +} diff --git a/assets/eip-NTT/pythonref/polyntt/ntt_iterative.py b/assets/eip-NTT/pythonref/polyntt/ntt_iterative.py new file mode 100755 index 00000000000000..3324f081262cb8 --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/ntt_iterative.py @@ -0,0 +1,70 @@ +"""This file contains an iterative implementation of the NTT. + +The NTT implemented here is for polynomials in Z_q[x]/(phi), with: +- The integer modulus q = 12 * 1024 + 1 = 12289 +- The polynomial modulus phi = x ** n + 1, with n a power of two, n =< 1024 +""" +from polyntt.ntt_constants_iterative import * +from polyntt.ntt import NTT + + +class NTTIterative(NTT): + + def __init__(self, q): + """Implements Number Theoretic Transform for fast polynomial multiplication.""" + self.q = q + # can be removed if run for nodes, increases efficiency of Poly.mul_pwc with a larger storage + self.ψ = ψ[q] + # can be removed if run for nodes, increases efficiency of Poly.mul_pwc with a larger storage + self.ψ_inv = ψ_inv[q] + # useful for efficiency (even in nodes) + self.ψ_rev = ψ_rev[q] + # useful for efficiency (even in nodes) + self.ψ_inv_rev = ψ_inv_rev[q] + # ratio between degree n and number of complex coefficients of the NTT + # while here this ratio is 1, it is possible to develop a short NTT such that it is 2. + self.ntt_ratio = 1 + + def ntt(self, f): + # following eprint 2016/504 Algorithm 1 + a = [_ for _ in f] + n = len(a) + t = n + m = 1 + while m < n: + t //= 2 + for i in range(m): + j1 = 2*i*t + j2 = j1+t-1 + S = self.ψ_rev[m+i] + for j in range(j1, j2+1): + U = a[j] + V = a[j+t]*S + a[j] = (U+V) % self.q + a[j+t] = (U-V) % self.q + m = 2*m + return a + + def intt(self, f_ntt): + # following eprint 2016/504 Algorithm 2 + a = [_ for _ in f_ntt] + n = len(a) + t = 1 + m = n + while m > 1: + j1 = 0 + h = m//2 + for i in range(h): + j2 = j1+t-1 + S = self.ψ_inv_rev[h+i] + for j in range(j1, j2+1): + U = a[j] + V = a[j+t] + a[j] = (U+V) % self.q + a[j+t] = ((U-V) * S) % self.q + j1 += 2*t + t *= 2 + m //= 2 + for j in range(n): + a[j] = (a[j] * n_inv[self.q][n]) % self.q + return a diff --git a/assets/eip-NTT/pythonref/polyntt/ntt_recursive.py b/assets/eip-NTT/pythonref/polyntt/ntt_recursive.py new file mode 100755 index 00000000000000..a5a6afa219c10a --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/ntt_recursive.py @@ -0,0 +1,122 @@ +"""This file contains a recursive implementation of the NTT. + +The NTT implemented here is for polynomials in Z_q[x]/(phi), with: +- The integer modulus q = 12 * 1024 + 1 = 12289 +- The polynomial modulus phi = x ** n + 1, with n a power of two, n =< 1024 + +The code is voluntarily very similar to the code of the FFT. +It is probably possible to use templating to merge both implementations. +""" + +from polyntt.ntt import NTT +from polyntt.utils import inv_mod +from polyntt.ntt_constants_recursive import roots_dict_mod + + +def merge(f_list): + """Merge two polynomials into a single polynomial f. + + Args: + f_list: a list of polynomials + + Format: coefficient + + Function from Thomas Prest repository + """ + f0, f1 = f_list + n = 2 * len(f0) + f = [0] * n + f[::2] = f0 + f[1::2] = f1 + return f + + +class NTTRecursive(NTT): + + def __init__(self, q): + """Implements Number Theoretic Transform for fast polynomial multiplication.""" + self.q = q + # i2 is the inverse of 2 mod q + self.i2 = inv_mod(2, self.q) + # sqr1 is a square root of (-1) mod q (currently, sqr1 = 1479) + self.sqr1 = roots_dict_mod[q][2][0] + self.roots_dict_mod = roots_dict_mod[q] + # ratio between degree n and number of complex coefficients of the NTT + # while here this ratio is 1, it is possible to develop a short NTT such that it is 2. + self.ntt_ratio = 1 + + def split_ntt(self, f_ntt): + """Split a polynomial f in two or three polynomials. + + Args: + f_ntt: a polynomial + + Format: NTT + """ + n = len(f_ntt) + w = self.roots_dict_mod[n] + f0_ntt = [0] * (n // 2) + f1_ntt = [0] * (n // 2) + for i in range(n // 2): + f0_ntt[i] = (self.i2 * (f_ntt[2 * i] + f_ntt[2 * i + 1])) % self.q + f1_ntt[i] = (self.i2 * (f_ntt[2 * i] - f_ntt[2 * i + 1]) + * inv_mod(w[2 * i], self.q)) % self.q + return [f0_ntt, f1_ntt] + + def merge_ntt(self, f_list_ntt): + """Merge two or three polynomials into a single polynomial f. + + Args: + f_list_ntt: a list of polynomials + + Format: NTT + """ + f0_ntt, f1_ntt = f_list_ntt + n = 2 * len(f0_ntt) + w = self.roots_dict_mod[n] + f_ntt = [0] * n + for i in range(n // 2): + f_ntt[2 * i + 0] = (f0_ntt[i] + w[2 * i] * f1_ntt[i]) % self.q + f_ntt[2 * i + 1] = (f0_ntt[i] - w[2 * i] * f1_ntt[i]) % self.q + return f_ntt + + def ntt(self, f): + """Compute the NTT of a polynomial. + + Args: + f: a polynomial + + Format: input as coefficients, output as NTT + """ + n = len(f) + if (n > 2): + f0, f1 = f[::2], f[1::2] + f0_ntt = self.ntt(f0) + f1_ntt = self.ntt(f1) + f_ntt = self.merge_ntt([f0_ntt, f1_ntt]) + elif (n == 2): + f_ntt = [0] * n + f_ntt[0] = (f[0] + self.sqr1 * f[1]) % self.q + f_ntt[1] = (f[0] - self.sqr1 * f[1]) % self.q + return f_ntt + + def intt(self, f_ntt): + """Compute the inverse NTT of a polynomial. + + Args: + f_ntt: a NTT of a polynomial + + Format: input as NTT, output as coefficients + """ + n = len(f_ntt) + if (n > 2): + f0_ntt, f1_ntt = self.split_ntt(f_ntt) + f0 = self.intt(f0_ntt) + f1 = self.intt(f1_ntt) + f = merge([f0, f1]) + elif (n == 2): + f = [0] * n + f[0] = (self.i2 * (f_ntt[0] + f_ntt[1])) % self.q + f[1] = (self.i2 * inv_mod(self.sqr1, self.q) + * (f_ntt[0] - f_ntt[1])) % self.q + return f diff --git a/assets/eip-NTT/pythonref/polyntt/params.py b/assets/eip-NTT/pythonref/polyntt/params.py new file mode 100644 index 00000000000000..3f104b3452a659 --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/params.py @@ -0,0 +1,8 @@ +PARAMS = [ + (12289, 11), # Falcon + (8380417, 9), # Dilithium + (2013265921, 10), # Babybear + # (2013265921, 27), # Babybear with full 2-adicity # !WARNING big storage + # (3329, 7), # Kyber + # (18446744069414584321, 32), # Plonky2 # !WARNING, 4.2Gb of storage needed! +] diff --git a/assets/eip-NTT/pythonref/polyntt/poly.py b/assets/eip-NTT/pythonref/polyntt/poly.py new file mode 100755 index 00000000000000..774f8f2db1f222 --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/poly.py @@ -0,0 +1,144 @@ +"""This file contains the implementation of the polynomial arithmetic modulo the cyclotomic polynomial x**n+1 (where n is a power of 2).""" +from polyntt.ntt_iterative import NTTIterative +from polyntt.ntt_recursive import NTTRecursive +from polyntt.utils import batch_modular_inversion, bit_reverse_order + + +class Poly: + def __init__(self, coeffs, q, ntt='NTTIterative'): + self.coeffs = coeffs + self.q = q + if ntt == 'NTTIterative': + self.NTT = NTTIterative(q) + elif ntt == 'NTTRecursive': + self.NTT = NTTRecursive(q) + + def __eq__(self, other): + for (a, b) in zip(self.coeffs, other.coeffs): + if (a-b) % self.q != 0: + return False + return True + + def __add__(self, other): + f = self.coeffs + g = other.coeffs + assert len(f) == len(g) + deg = len(f) + return Poly([(f[i] + g[i]) % self.q for i in range(deg)], self.q) + + def __neg__(self): + """Negation of a polynomials (any representation).""" + f = self.coeffs + deg = len(f) + return Poly([(- f[i]) % self.q for i in range(deg)], self.q) + + def __sub__(self, other): + """Substraction of two polynomials (any representation).""" + return self + (-other) + + def __mul__(self, other): + """Multiplication of two polynomials (coefficient representation).""" + f = self.coeffs + g = other.coeffs + T = self.NTT + f_ntt = T.ntt(f) + g_ntt = T.ntt(g) + return Poly(T.intt(T.vec_mul(f_ntt, g_ntt)), self.q) + + def mul_schoolbook(self, other): + """Multiplication of two polynomials using the schoolbook algorithm.""" + f = self.coeffs + g = other.coeffs + n = len(f) + assert n == len(g) + C = [0] * (2 * n) + D = [0] * (n) + for j, f_j in enumerate(f): + for k, g_k in enumerate(g): + C[j+k] = (C[j+k] + f_j * g_k) % self.q + # reduction modulo x^n + 1 + for i in range(n): + D[i] = (C[i] - C[i+n]) % self.q + return Poly(D, self.q) + + def mul_pwc(self, other, NODE=False): + """ + Multiplication of `self` by `other` modulo x^n -1 (and not x^n+1). + PWC means Positive Wrapped Convolution. + In this context, the multiplication is the same as when x^n+1, but + with pre- and post-computation. + """ + f = self.coeffs + g = other.coeffs + n = len(f) + # pre-processing + # list of roots for the precomputations + if NODE: + bit_rev_index = bit_reverse_order( + range(0, len(self.NTT.ψ_inv), len(self.NTT.ψ_inv)//n)) + ψ0_inv = [self.NTT.ψ_inv_rev[j] for j in bit_rev_index] + else: + ψ0_inv = self.NTT.ψ_inv[::len(self.NTT.ψ_inv)//n] + fp = Poly([(x * y) % self.q for (x, y) in zip(f, ψ0_inv)], self.q) + gp = Poly([(x * y) % self.q for (x, y) in zip(g, ψ0_inv)], self.q) + fp_mul_gp = fp*gp + # post processing + if NODE: + bit_rev_index = bit_reverse_order( + range(0, len(self.NTT.ψ_inv), len(self.NTT.ψ)//n)) + ψ0 = [self.NTT.ψ_rev[j] for j in bit_rev_index] + else: + ψ0 = self.NTT.ψ[::len(self.NTT.ψ)//n] + f_mul_g = [(x * y) % self.q for (x, y) in zip(fp_mul_gp.coeffs, ψ0)] + return Poly(f_mul_g, self.q) + + def mul_schoolbook_pwc(self, other): + """Multiplication of two polynomials using the schoolbook algorithm.""" + f = self.coeffs + g = other.coeffs + n = len(f) + assert n == len(g) + C = [0] * (2 * n) + D = [0] * (n) + for j, f_j in enumerate(f): + for k, g_k in enumerate(g): + C[j+k] = (C[j+k] + f_j * g_k) % self.q + # reduction modulo x^n - 1 + for i in range(n): + D[i] = (C[i] + C[i+n]) % self.q + return Poly(D, self.q) + + def __truediv__(self, other): + """Division of two polynomials (coefficient representation).""" + try: + f = self.coeffs + g = other.coeffs + T = self.NTT + f_ntt = T.ntt(f) + g_ntt = T.ntt(g) + return Poly(T.intt(T.vec_div(f_ntt, g_ntt)), self.q) + except ZeroDivisionError: + raise + + def inverse(self): + T = self.NTT + f_ntt = T.ntt(self.coeffs) + try: + one_over_f_ntt = batch_modular_inversion(f_ntt, self.q) + except ZeroDivisionError: + raise + return Poly(T.intt(one_over_f_ntt), self.q) + + def ntt(self): + return self.NTT.ntt(self.coeffs) + + def mul_opt(self, other_ntt): + f = self.coeffs + T = self.NTT + f_ntt = T.ntt(f) + g_ntt = other_ntt + return Poly(T.intt(T.vec_mul(f_ntt, g_ntt)), self.q) + + # def adj(f): + # """Ajoint of a polynomial (coefficient representation).""" + # return intt(adj_ntt(ntt(f))) diff --git a/assets/eip-NTT/pythonref/polyntt/scripts/generate_ntt_constants.py b/assets/eip-NTT/pythonref/polyntt/scripts/generate_ntt_constants.py new file mode 100644 index 00000000000000..5589389307c948 --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/scripts/generate_ntt_constants.py @@ -0,0 +1,126 @@ +from polyntt.params import PARAMS +from polyntt.utils import bit_reverse_order, sqrt_mod + + +# +# Generate constants for the iterative case +# + +f = open("polyntt/ntt_constants_iterative.py", "w") +f.write("# File generated with `python polyntt/generate_ntt_constants.py`.\n") +f.write( + "# Precomputations for NTT.\n\n" +) + +ψ_table = dict() +ψ_inv_table = dict() +ψ_rev = dict() +ψ_inv_rev = dict() +n_inv = dict() + +for (q, two_adicity) in PARAMS: + # list of roots of cyclotomic polynomials + if q == 12*1024 + 1: + # Falcon + # ψ is a root of the 2¹¹-th cyclotomic polynomial + ψ = 1826 + elif q == 3329: + # Kyber + # ψ is a root of the 2⁷-th cyclotomic polynomial + ψ = 3296 + elif q == 8380417: + # Dilithium + # ψ is a root of the 2⁹-th cyclotomic polynomial + ψ = 2926054 + elif q == 2013265921: + # BabyBear + # ψ is a root of the 2¹⁰-th cyclotomic polynomial + # (larger 2-adicity can be considered) + ψ = 1538055801 + else: + print("NOT DEFINED YET") + n = 1 << (two_adicity-1) + assert pow(ψ, 2*n, q) == 1 and pow(ψ, n, q) != 1 + + ψ_inv = pow(ψ, -1, q) + assert (ψ*ψ_inv) % q == 1 + + # Precompute powers of ψ to speedup main NTT process. + ψ_table[q] = [1] * n + ψ_inv_table[q] = [1] * n + for i in range(1, n): + ψ_table[q][i] = ((ψ_table[q][i-1] * ψ) % q) + ψ_inv_table[q][i] = ((ψ_inv_table[q][i-1] * ψ_inv) % q) + + # Change the lists into bit-reverse order. + ψ_rev[q] = bit_reverse_order(ψ_table[q]) + ψ_inv_rev[q] = bit_reverse_order(ψ_inv_table[q]) + +# writing ψ +f.write("# Dictionary containing the powers ψ, a 2^n-th root of unity.\n") +f.write("ψ = {\n") +for (q, two_adicity) in PARAMS: + f.write("\t# ψ = {}, ψ has multiplicative order {}.\n".format( + ψ_table[q][1], 1 << two_adicity)) + f.write("\t{} : {},\n".format(q, ψ_table[q])) +f.write("}\n\n") + +# writing ψ_inv +f.write("# Dictionary containing the powers of ψ_inv.\n") +f.write("ψ_inv = {\n") +for (q, two_adicity) in PARAMS: + f.write("\t # ψ_inv = {}, ψ*ψ_inv = 1.\n".format(ψ_inv_table[q][1])) + f.write("\t{} : {},\n".format(q, ψ_inv_table[q])) +f.write("}\n\n") + +# writing ψ_rev +f.write( + "# The table ψ, but in bit-reversed order, i.e. the i-th element corresponds to ψ^{BitReversed(i)}.\n") +f.write("ψ_rev = {\n") +for (q, two_adicity) in PARAMS: + f.write("\t{} : {},\n".format(q, ψ_rev[q])) +f.write("}\n\n") + +# writing ψ_rev_inv +f.write( + "# The table ψ_inv, but in bit-reversed order, i.e. the i-th element corresponds to ψ^{BitReversed(-i)}.\n") +f.write("ψ_inv_rev = {\n") +for (q, two_adicity) in PARAMS: + f.write("\t{} : {},\n".format(q, ψ_inv_rev[q])) +f.write("}\n\n") + +# writing n_inv +f.write("# The inverses of powers of 2 mod q\n") +f.write("n_inv = {\n") +for (q, two_adicity) in PARAMS: + f.write("\t{}: {{\n".format(q)) + # n_inv[{}] = {{\n".format(q)) + for j in range(1, two_adicity+1): + f.write("\t\t{}: {},\n".format(1 << j, pow(1 << j, -1, q))) + f.write("\t},\n") +f.write("}") + +f.close() + +# +# Generate constants for the recursive case +# + +file = open("polyntt/ntt_constants_recursive.py", 'w') +file.write( + "# Roots of the cyclotomic polynomials mod q for the recursive ntt implementation\n") +file.write("# File generated using `generate_contants_recursive.sage`\n") +file.write( + "# roots_dict_mod[q][n] corresponds to the roots of x^{2n} + 1 mod q\n") +file.write("roots_dict_mod = {\n") + +for (q, two_adicity) in PARAMS: + file.write("\t{}: {{\n".format(q)) + phi_roots_Zq = [sqrt_mod(-1, q), q-sqrt_mod(-1, q)] + for k in range(1, two_adicity): + file.write("\t\t{} : {},\n".format(1 << k, phi_roots_Zq)) + phi_roots_Zq = sum([[sqrt_mod(elt, q), q - sqrt_mod(elt, q)] + for elt in phi_roots_Zq], []) + file.write("\t},\n") +file.write("}\n") +file.close() diff --git a/assets/eip-NTT/pythonref/polyntt/scripts/generate_test_vectors.py b/assets/eip-NTT/pythonref/polyntt/scripts/generate_test_vectors.py new file mode 100644 index 00000000000000..070c122355c574 --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/scripts/generate_test_vectors.py @@ -0,0 +1,125 @@ +import hashlib +from polyntt.ntt_iterative import NTTIterative +from polyntt.poly import Poly +from polyntt.params import PARAMS + + +def write_test(f, input, name, expected, gas, final=False): + f.write("\t{\n") + f.write("\t\t\"Input\": \"{}\",\n".format(input)) + f.write("\t\t\"Name\": \"{}\",\n".format(name)) + f.write("\t\t\"Expected\": \"{}\",\n".format(expected)) + f.write("\t\t\"Gas\": {}\n".format(gas)) + if final: + f.write("\t}\n") + else: + f.write("\t},\n") + + +def encode(poly, q): + # By default, q is a 32-bit integer. + # NB: this does not apply to q_baby_bear nor q_plonky2. + assert all([x < q for x in poly]) + size_q = (q.bit_length()+7)//8 + byte_string = b''.join(num.to_bytes(size_q, 'big') for num in poly) + return byte_string.hex() + + +def decode(hex_poly, q): + # By default, q is a 32-bit integer. + # NB: this does not apply to q_baby_bear nor q_plonky2. + size_q = (q.bit_length()+7)//8 + bytes_poly = bytes.fromhex(hex_poly) + return [int.from_bytes(bytes_poly[i:i+size_q], 'big') for i in range(0, len(bytes_poly), size_q)] + + +def deterministic_poly(q, n, seed="fixed_seed"): + # This function is used for generating polynomials for the tests. + # No randomness. + return [int(hashlib.sha256(f"{seed}{i}".encode()).hexdigest(), 16) % q for i in range(n)] + + +f = deterministic_poly(8, 3329) +assert decode(encode(f, 3329), 3329) == f +assert decode(encode(f, 3329), 3329) == f + +for (q, two_adicity) in PARAMS: + + for n in [1 << (two_adicity-2), 1 << (two_adicity-1)]: # for two sizes of polynomials + file = open("../test_vectors/q{}_n{}.json".format(q, n), "w") + + f = deterministic_poly(q, n, seed="seed_f") + g = deterministic_poly(q, n, seed="seed_g") + + T = NTTIterative(q) + f_ntt = T.ntt(f) + g_ntt = T.ntt(g) + f_ntt_intt = T.intt(f_ntt) + g_ntt_intt = T.intt(g_ntt) + assert f_ntt_intt == f + assert g_ntt_intt == g + + f_ntt_mul_g_ntt = T.vec_mul(f_ntt, g_ntt) + f_mul_g = T.intt(f_ntt_mul_g_ntt) + assert f_mul_g == (Poly(f, q) * Poly(g, q)).coeffs + + f_ntt_add_g_ntt = T.vec_add(f_ntt, g_ntt) + f_ntt_sub_g_ntt = T.vec_sub(f_ntt, g_ntt) + + file.write("[\n") + # 1. ntt + # Input: f,g + # Output: ntt(f), ntt(g). + input = encode(f, q)+encode(g, q) + name = "q{}_n{}_{{ntt_of_two_polynomials}}".format(q, n) + expected = encode(f_ntt, q)+encode(g_ntt, q) + gas = 600 + write_test(file, input, name, expected, gas) + + # 2. intt + # Input: f_ntt,g_ntt + # Output: f, g + input = encode(f_ntt, q)+encode(g_ntt, q) + name = "q{}_n{}_{{intt_of_two_polynomials}}".format(q, n) + expected = encode(f_ntt_intt, q)+encode(g_ntt_intt, q) + gas = 600 + write_test(file, input, name, expected, gas) + + # 3. vec_mul + # Input: f_ntt, g_ntt, + # Output: f_ntt*g_ntt. + input = encode(f_ntt, q)+encode(g_ntt, q) + name = "q{}_n{}_{{vec_mul}}".format(q, n) + expected = encode(f_ntt_mul_g_ntt, q) + gas = 300 + write_test(file, input, name, expected, gas) + + # 4. pol_mul + # Input: f,g, + # Output: f*g. + input = encode(f, q)+encode(g, q) + name = "q{}_n{}_{{pol_mul}}".format(q, n) + expected = encode(f_mul_g, q) + gas = 600 + 600 + 300 + 600 + write_test(file, input, name, expected, gas) + + # 5. vec_add + # Input: f_ntt, g_ntt, + # Output: f_ntt + g_ntt. + input = encode(f_ntt, q)+encode(g_ntt, q) + name = "q{}_n{}_{{vec_add}}".format(q, n) + expected = encode(f_ntt_add_g_ntt, q) + gas = 50 # TODO + write_test(file, input, name, expected, gas) + + # 6. vec_sub + # Input: f_ntt, g_ntt, + # Output: f_ntt - g_ntt. + input = encode(f_ntt, q)+encode(g_ntt, q) + name = "q{}_n{}_{{vec_sub}}".format(q, n) + expected = encode(f_ntt_sub_g_ntt, q) + gas = 100 # TODO + write_test(file, input, name, expected, gas, final=True) + + file.write("]\n") + file.close() diff --git a/assets/eip-NTT/pythonref/polyntt/scripts/generate_test_vectors_solidity.py b/assets/eip-NTT/pythonref/polyntt/scripts/generate_test_vectors_solidity.py new file mode 100644 index 00000000000000..49511dca1aa4ed --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/scripts/generate_test_vectors_solidity.py @@ -0,0 +1,105 @@ +import hashlib +from polyntt.ntt_iterative import NTTIterative +from polyntt.poly import Poly +from polyntt.params import PARAMS + + +def encode(poly, q): + # By default, q is a 32-bit integer. + # NB: this does not apply to q_baby_bear nor q_plonky2. + assert all([x < q for x in poly]) + size_q = (q.bit_length()+7)//8 + byte_string = b''.join(num.to_bytes(size_q, 'big') for num in poly) + return byte_string.hex() + + +def deterministic_poly(q, n, seed="fixed_seed"): + # This function is used for generating polynomials for the tests. + # No randomness. + return [int(hashlib.sha256(f"{seed}{i}".encode()).hexdigest(), 16) % q for i in range(n)] + + +for (q, two_adicity) in PARAMS: + + for n in [1 << (two_adicity-2), 1 << (two_adicity-1)]: # for two sizes of polynomials + file = open("../test_vectors/q{}_n{}.sol".format(q, n), "w") + + file.write( + "// File generated using ../pythonref/scripts/generate_test_vectors_solidity.py\n\n") + f = deterministic_poly(q, n, seed="seed_f") + g = deterministic_poly(q, n, seed="seed_g") + + T = NTTIterative(q) + f_ntt = T.ntt(f) + g_ntt = T.ntt(g) + f_ntt_intt = T.intt(f_ntt) + g_ntt_intt = T.intt(g_ntt) + assert f_ntt_intt == f + assert g_ntt_intt == g + + f_ntt_mul_g_ntt = T.vec_mul(f_ntt, g_ntt) + f_mul_g = T.intt(f_ntt_mul_g_ntt) + assert f_mul_g == (Poly(f, q) * Poly(g, q)).coeffs + + f_ntt_add_g_ntt = T.vec_add(f_ntt, g_ntt) + f_ntt_sub_g_ntt = T.vec_sub(f_ntt, g_ntt) + + # 1. ntt + # Input: f,g + # Output: ntt(f), ntt(g). + file.write("// ntt of f and g;\n") + file.write("uint{}[] f = {};\n".format(len(f), f)) + file.write("uint{}[] g = {};\n".format(len(g), g)) + file.write("uint{}[] f_ntt = {};\n".format(len(f_ntt), f_ntt)) + file.write("uint{}[] g_ntt = {};\n".format(len(g_ntt), g_ntt)) + file.write("\n") + + # 2. intt + # Input: f_ntt,g_ntt + # Output: f, g + file.write("// intt of f_ntt and g_ntt;\n") + file.write("uint{}[] f_ntt = {};\n".format(len(f_ntt), f_ntt)) + file.write("uint{}[] g_ntt = {};\n".format(len(g_ntt), g_ntt)) + file.write("uint{}[] f = {};\n".format(len(f), f)) + file.write("uint{}[] g = {};\n".format(len(g), g)) + file.write("\n") + + # 3. vec_mul + # Input: f_ntt, g_ntt, + # Output: f_ntt*g_ntt. + file.write("// vec_mul of f_ntt and g_ntt;\n") + file.write("uint{}[] f_ntt = {};\n".format(len(f_ntt), f_ntt)) + file.write("uint{}[] g_ntt = {};\n".format(len(g_ntt), g_ntt)) + file.write("uint{}[] f_ntt_mul_g_ntt = {};\n".format( + len(f_ntt_mul_g_ntt), f_ntt_mul_g_ntt)) + file.write("\n") + + # 4. pol_mul + # Input: f,g, + # Output: f*g. + file.write("// pol_mul of f and g;\n") + file.write("uint{}[] f = {};\n".format(len(f), f)) + file.write("uint{}[] g = {};\n".format(len(g), g)) + file.write("uint{}[] f_mul_g = {};\n".format(len(f_mul_g), f_mul_g)) + file.write("\n") + + # 5. vec_add + # Input: f_ntt, g_ntt, + # Output: f_ntt + g_ntt. + file.write("// vec_add of f_ntt and g_ntt;\n") + file.write("uint{}[] f_ntt = {};\n".format(len(f_ntt), f_ntt)) + file.write("uint{}[] g_ntt = {};\n".format(len(g_ntt), g_ntt)) + file.write("uint{}[] f_ntt_add_g_ntt = {};\n".format( + len(f_ntt_add_g_ntt), f_ntt_add_g_ntt)) + file.write("\n") + + # 6. vec_sub + # Input: f_ntt, g_ntt, + # Output: f_ntt - g_ntt. + file.write("// vec_sub of f_ntt and g_ntt;\n") + file.write("uint{}[] f_ntt = {};\n".format(len(f_ntt), f_ntt)) + file.write("uint{}[] g_ntt = {};\n".format(len(g_ntt), g_ntt)) + file.write("uint{}[] f_ntt_sub_g_ntt = {};\n".format( + len(f_ntt_sub_g_ntt), f_ntt_sub_g_ntt)) + file.write("\n") + file.close() diff --git a/assets/eip-NTT/pythonref/polyntt/tests/__init__.py b/assets/eip-NTT/pythonref/polyntt/tests/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/assets/eip-NTT/pythonref/polyntt/tests/test_ntt_iterative.py b/assets/eip-NTT/pythonref/polyntt/tests/test_ntt_iterative.py new file mode 100644 index 00000000000000..899b22cdb7463d --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/tests/test_ntt_iterative.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +from random import randint +from polyntt.ntt_iterative import NTTIterative +import unittest +from polyntt.params import PARAMS + + +class TestNTTIterative(unittest.TestCase): + def shortDescription(self): + return None # This prevents unittest from printing docstrings + + def test_ntt_intt(self, iterations=100): + """Test if ntt and intt are indeed inverses of each other.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + T = NTTIterative(q) + for i in range(iterations): + f = [randint(0, T.q-1) for j in range(n)] + self.assertEqual(T.intt(T.ntt(f)), f) + + def test_ntt_linearity(self, iterations=100): + """Test the linearity of NTT.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + T = NTTIterative(q) + for i in range(iterations): + f = [randint(0, T.q - 1) for j in range(n)] + g = [randint(0, T.q - 1) for j in range(n)] + λ = randint(0, T.q-1) + μ = randint(0, T.q-1) + λ_f_plus_μ_g = [(λ*x+μ*y) % T.q for (x, y) in zip(f, g)] + f_ntt = T.ntt(f) + g_ntt = T.ntt(g) + self.assertEqual( + T.ntt(λ_f_plus_μ_g), + [(λ*x+μ*y) % T.q for (x, y) in zip(f_ntt, g_ntt)] + ) + + # TODO TEST ADD AND SUB HERE diff --git a/assets/eip-NTT/pythonref/polyntt/tests/test_ntt_recursive.py b/assets/eip-NTT/pythonref/polyntt/tests/test_ntt_recursive.py new file mode 100644 index 00000000000000..0779744935aef3 --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/tests/test_ntt_recursive.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from random import randint +from polyntt.ntt_recursive import NTTRecursive +import unittest +from polyntt.params import PARAMS + + +class TestNTTRecursive(unittest.TestCase): + def shortDescription(self): + return None # This prevents unittest from printing docstrings + + def test_ntt_intt(self, iterations=100): + """Test if ntt and intt are indeed inverses of each other.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + T = NTTRecursive(q) + for i in range(iterations): + f = [randint(0, T.q-1) for j in range(n)] + self.assertEqual(T.intt(T.ntt(f)), f) + + def test_ntt_linearity(self, iterations=100): + """Test the linearity of NTT.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + T = NTTRecursive(q) + for i in range(iterations): + f = [randint(0, T.q - 1) for j in range(n)] + g = [randint(0, T.q - 1) for j in range(n)] + λ = randint(0, T.q-1) + μ = randint(0, T.q-1) + λ_f_plus_μ_g = [(λ*x+μ*y) % T.q for (x, y) in zip(f, g)] + f_ntt = T.ntt(f) + g_ntt = T.ntt(g) + self.assertEqual( + T.ntt(λ_f_plus_μ_g), + [(λ*x+μ*y) % T.q for (x, y) in zip(f_ntt, g_ntt)] + ) diff --git a/assets/eip-NTT/pythonref/polyntt/tests/test_poly.py b/assets/eip-NTT/pythonref/polyntt/tests/test_poly.py new file mode 100644 index 00000000000000..e2a4bc378b27b3 --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/tests/test_poly.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +from random import randint +from polyntt.ntt_iterative import NTTIterative +from polyntt.poly import Poly +import unittest +from polyntt.params import PARAMS + + +class TestPoly(unittest.TestCase): + + def shortDescription(self): + return None # This prevents unittest from printing docstrings + + # def add_sub(self, q, n): + # with self.subTest(msg="Test of add_sub with q={} and n = {}".format(q, n)): + # f = Poly([randint(0, q-1) for _ in range(n)], q) + # g = Poly([randint(0, q-1) for _ in range(n)], q) + # f_plus_g = f+g + # self.assertEqual(f_plus_g - g, f) + + # def test_all(self, iterations=100): + # for (q, k) in PARAMS: + # n = 1 << (k-1) + # self.add_sub(q, n) + + def test_add_sub(self, iterations=100): + """Test if ntt and intt are indeed inverses of each other.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + for i in range(iterations): + f = Poly([randint(0, q-1) for _ in range(n)], q) + g = Poly([randint(0, q-1) for _ in range(n)], q) + f_plus_g = f+g + self.assertEqual(f_plus_g - g, f) + + def test_mod_q(self, iterations=100): + """ Test if the reduction mod q works.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + zero = Poly([0 for _ in range(n)], q) + for i in range(iterations): + f = Poly([q*randint(0, q-1) for _ in range(n)], q) + self.assertEqual(f, zero) + + def test_mul(self, iterations=10): + """Compare FFT multiplication with schoolbook multiplication.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + for i in range(iterations): + f = Poly([randint(0, q-1) for _ in range(n)], q) + g = Poly([randint(0, q-1) for _ in range(n)], q) + f_mul_g = f*g + self.assertEqual(f_mul_g, f.mul_schoolbook(g)) + + def test_div(self, iterations=10): + """Test the division.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + for i in range(iterations): + # random f + f = Poly([randint(0, q-1) for _ in range(n)], q) + # invertible random g + g = Poly(f.NTT.intt([randint(1, q-1) + for _ in range(n)]), q) + h = f/g + self.assertEqual(h * g, f) + + def test_inv(self, iterations=10): + """Test the division.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + T = NTTIterative(q) + one = Poly([1]+[0 for i in range(n-1)], q) + for i in range(iterations): + # invertible random f + f = Poly(T.intt([randint(1, q-1) + for _ in range(n)]), q) + inv_f = f.inverse() + self.assertEqual(inv_f * f, one) + + def test_mul_pwc(self, iterations=10): + """Test the multiplication modulo x^n+1.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + for i in range(1, iterations): + # random f,g + f = Poly([randint(0, q-1) for _ in range(n)], q) + g = Poly([randint(0, q-1) for _ in range(n)], q) + self.assertEqual(f.mul_pwc(g), f.mul_schoolbook_pwc(g)) + + def test_mul_pwc_one_table(self, iterations=100): + """Compare NTT with one and four tables.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + for i in range(iterations): + f = Poly([randint(0, q-1) for _ in range(n)], q) + g = Poly([randint(0, q-1) for _ in range(n)], q) + f_mul_g_1 = f.mul_pwc(g) + f_mul_g_2 = f.mul_pwc(g, NODE=True) + self.assertEqual(f_mul_g_1, f_mul_g_2) + + def test_mul_opt(self, iterations=100): + """Compare mul_opt with __mul__.""" + for (q, k) in PARAMS: + n = 1 << (k-1) + with self.subTest(q=q, k=k): + for i in range(iterations): + f = Poly([randint(0, q-1) for _ in range(n)], q) + g = Poly([randint(0, q-1) for _ in range(n)], q) + f_mul_g_1 = f*g + f_mul_g_2 = f.mul_opt(g.ntt()) + self.assertEqual(f_mul_g_1, f_mul_g_2) diff --git a/assets/eip-NTT/pythonref/polyntt/tests/test_utils.py b/assets/eip-NTT/pythonref/polyntt/tests/test_utils.py new file mode 100755 index 00000000000000..fba5b33b88b70f --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/tests/test_utils.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from random import randint +import unittest +from polyntt.ntt import batch_modular_inversion +from polyntt.utils import batch_modular_inversion + + +class TestUtils(unittest.TestCase): + def shortDescription(self): + return None # This prevents unittest from printing docstrings + + def test_batched_modular_multiplication(self, iterations=100): + """Test the batched modular multiplication.""" + q = 3329 + n = 256 + for i in range(iterations): + L = [randint(1, q-1) for i in range(n)] # non-zeros! + M = batch_modular_inversion(L, q) + for (l, m) in zip(L, M): + self.assertEqual((l*m) % q, 1) diff --git a/assets/eip-NTT/pythonref/polyntt/tests/test_vectors.py b/assets/eip-NTT/pythonref/polyntt/tests/test_vectors.py new file mode 100644 index 00000000000000..248bd5da1fd1e1 --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/tests/test_vectors.py @@ -0,0 +1,49 @@ +# This file import the json test vectors and verify the expected output. + +import json +from polyntt.ntt_iterative import NTTIterative +from polyntt.poly import Poly +from polyntt.params import PARAMS +import unittest +from polyntt.scripts.generate_test_vectors import decode + + +class TestVectors(unittest.TestCase): + def test_vectors(self): + """Run tests on the test vectors.""" + for (q, two_adicity) in PARAMS: + + # for two sizes of polynomials + for n in [1 << (two_adicity-2), 1 << (two_adicity-1)]: + + with open("../test_vectors/q{}_n{}.json".format(q, n), 'r') as file: + T = NTTIterative(q) + for test in json.load(file): + input = decode(test['Input'], q) + mid = len(input)//2 + in1, in2 = input[:mid], input[mid:] + + name = test['Name'].split('{')[1][:-1] + + expected = decode(test['Expected'], q) + gas = test['Gas'] + + if name == 'ntt': + mid = len(expected)//2 + ex1, ex2 = expected[:mid], expected[mid:] + self.assertEqual(T.ntt(in1), ex1) + self.assertEqual(T.ntt(in2), ex2) + if name == 'intt': + mid = len(expected)//2 + ex1, ex2 = expected[:mid], expected[mid:] + self.assertEqual(T.intt(in1), ex1) + self.assertEqual(T.intt(in2), ex2) + if name == 'vec_mul': + self.assertEqual(T.vec_mul(in1, in2), expected) + if name == 'pol_mul': + self.assertEqual(Poly(in1, q) * + Poly(in2, q), Poly(expected, q)) + if name == 'vec_add': + self.assertEqual(T.vec_add(in1, in2), expected) + if name == 'vec_sub': + self.assertEqual(T.vec_sub(in1, in2), expected) diff --git a/assets/eip-NTT/pythonref/polyntt/utils.py b/assets/eip-NTT/pythonref/polyntt/utils.py new file mode 100644 index 00000000000000..bb42bfbf6d1e0f --- /dev/null +++ b/assets/eip-NTT/pythonref/polyntt/utils.py @@ -0,0 +1,90 @@ + +def xgcd(a, b): + """ Returns gcd(a, b), and x, y such that ax + by = gcd(a, b) """ + x0, x1, y0, y1 = 1, 0, 0, 1 + while b: + q, a, b = a // b, b, a % b + x0, x1 = x1, x0 - q * x1 + y0, y1 = y1, y0 - q * y1 + return a, x0, y0 + + +def inv_mod(elt, q): + """ + Thomas Prest stores the inverses mod q, but in the long term, we will consider a larger q, + and thus we do not store the inverses mod q (it would require a too large storage). + """ + _, inv_elt, _ = xgcd(elt, q) + assert (inv_elt * elt) % q == 1 + return inv_elt + + +def batch_modular_inversion(elements, q): + """Compute batch inversion of a list of elements mod q.""" + n = len(elements) + if n == 0: + return [] + # Prefix products + prefix = [None] * n + prefix[0] = elements[0] + for i in range(1, n): + prefix[i] = (prefix[i - 1] * elements[i]) % q + # Iinverse of the total product + total_inv = inv_mod(prefix[-1], q) + # Individual inverses using the prefix products + inverses = [None] * n + inverses[-1] = total_inv + for i in range(n - 2, -1, -1): + inverses[i] = (inverses[i + 1] * elements[i + 1]) % q + # Final inverses + for i in range(1, n): + inverses[i] = (inverses[i] * prefix[i - 1]) % q + return inverses + + +def bit_reverse_order(a): + '''Reorders the given array in reverse-bit order.''' + num_bits = len(bin(len(a) - 1)) - 2 + result = [0] * len(a) + for i in range(len(a)): + rev_index = int(bin(i)[2:].zfill(num_bits)[::-1], 2) + result[rev_index] = a[i] + return result + + +def legendre_symbol(a, q): + """ Compute the Legendre symbol a|q using Euler's criterion. """ + return pow(a, (q - 1) // 2, q) + + +def tonelli_shanks(a, q): + """ Solve x^2 ≡ a (mod q) using the Tonelli-Shanks algorithm. """ + if legendre_symbol(a, q) != 1: + raise ValueError(f"No square root exists for {a} modulo {q}") + s, m = 0, q - 1 + while m % 2 == 0: + s += 1 + m //= 2 + z = 2 + while legendre_symbol(z, q) != q - 1: + z += 1 + c = pow(z, m, q) + t = pow(a, m, q) + r = pow(a, (m + 1) // 2, q) + while t != 1: + # Find the smallest i such that t^(2^i) ≡ 1 (mod q) + i = 0 + t2i = t + while t2i != 1: + t2i = pow(t2i, 2, q) + i += 1 + b = pow(c, 2**(s-i-1), q) + r = (r * b) % q + t = (t * b * b) % q + c = (b * b) % q + s = i + return r + + +def sqrt_mod(x, q): + return tonelli_shanks(x, q) diff --git a/assets/eip-NTT/pythonref/setup.py b/assets/eip-NTT/pythonref/setup.py new file mode 100644 index 00000000000000..e6ca2cbe2aae17 --- /dev/null +++ b/assets/eip-NTT/pythonref/setup.py @@ -0,0 +1,8 @@ +from setuptools import setup, find_packages + +setup( + name="polyntt", + version="0.1", + packages=find_packages(), + install_requires=[], +) diff --git a/assets/eip-NTT/solidity/src/ZKNOX_NTT.sol b/assets/eip-NTT/solidity/src/ZKNOX_NTT.sol new file mode 100644 index 00000000000000..64ec1a55a33e97 --- /dev/null +++ b/assets/eip-NTT/solidity/src/ZKNOX_NTT.sol @@ -0,0 +1,288 @@ +/************************************************************************************************************************************************************************/ +/*ZZZZZZZZZZZZZZZZZZZKKKKKKKKK KKKKKKKNNNNNNNN NNNNNNNN OOOOOOOOO XXXXXXX XXXXXXX ..../&@&#. .###%@@@#, .. +/*Z:::::::::::::::::ZK:::::::K K:::::KN:::::::N N::::::N OO:::::::::OO X:::::X X:::::X ...(@@* .... . &#//%@@&,. +/*Z:::::::::::::::::ZK:::::::K K:::::KN::::::::N N::::::N OO:::::::::::::OO X:::::X X:::::X ..*@@......... .@#%%(%&@&.. +/*Z:::ZZZZZZZZ:::::Z K:::::::K K::::::KN:::::::::N N::::::NO:::::::OOO:::::::OX::::::X X::::::X .*@( ........ . .&@@@@. .@%%%%%#&@@. +/*ZZZZZ Z:::::Z KK::::::K K:::::KKKN::::::::::N N::::::NO::::::O O::::::OXXX:::::X X::::::XX ...&@ ......... . &. .@ /@%%%%%%&@@# +/* Z:::::Z K:::::K K:::::K N:::::::::::N N::::::NO:::::O O:::::O X:::::X X:::::X ..@( .......... . &. ,& /@%%%%&&&&@@@. +/* Z:::::Z K::::::K:::::K N:::::::N::::N N::::::NO:::::O O:::::O X:::::X:::::X ..&% ........... .@%(#@# ,@%%%%&&&&&@@@%. +/* Z:::::Z K:::::::::::K N::::::N N::::N N::::::NO:::::O O:::::O X:::::::::X ..,@ ............ *@%%%&%&&&&&&@@@. +/* Z:::::Z K:::::::::::K N::::::N N::::N:::::::NO:::::O O:::::O X:::::::::X ..(@ ............. ,#@&&&&&&&&&&&&@@@@* +/* Z:::::Z K::::::K:::::K N::::::N N:::::::::::NO:::::O O:::::O X:::::X:::::X .*@.............. . ..,(%&@@&&&&&&&&&&&&&&&&@@@@, +/* Z:::::Z K:::::K K:::::K N::::::N N::::::::::NO:::::O O:::::O X:::::X X:::::X ...&#............. *@@&&&&&&&&&&&&&&&&&&&&@@&@@@@& +/*ZZZ:::::Z ZZZZZKK::::::K K:::::KKKN::::::N N:::::::::NO::::::O O::::::OXXX:::::X X::::::XX ...@/.......... *@@@@. ,@@. &@&&&&&&@@@@@@@@@@@. +/*Z::::::ZZZZZZZZ:::ZK:::::::K K::::::KN::::::N N::::::::NO:::::::OOO:::::::OX::::::X X::::::X ....&#..........@@@, *@@&&&@% .@@@@@@@@@@@@@@@& +/*Z:::::::::::::::::ZK:::::::K K:::::KN::::::N N:::::::N OO:::::::::::::OO X:::::X X:::::X ....*@.,......,@@@...@@@@@@&..%@@@@@@@@@@@@@/ +/*Z:::::::::::::::::ZK:::::::K K:::::KN::::::N N::::::N OO:::::::::OO X:::::X X:::::X ...*@,,.....%@@@,.........%@@@@@@@@@@@@( +/*ZZZZZZZZZZZZZZZZZZZKKKKKKKKK KKKKKKKNNNNNNNN NNNNNNN OOOOOOOOO XXXXXXX XXXXXXX ...&@,....*@@@@@ ..,@@@@@@@@@@@@@&. +/* ....,(&@@&..,,,/@&#*. . +/* ......(&.,.,,/&@,. +/* .....,%*.,*@% +/* .#@@@&(&@*,,*@@%,.. +/* .##,,,**$.,,*@@@@@%. +/* *(%%&&@(,,**@@@@@& +/* . . .#@((@@(*,** +/* . (*. . +/* .*/ +///* Copyright (C) 2025 - Renaud Dubois, Simon Masson - This file is part of ZKNOX project +///* License: This software is licensed under MIT License +///* This Code may be reused including this header, license and copyright notice. +///* See LICENSE file at the root folder of the project. +///* FILE: ZKNOX_NTT.sol +///* Description: Compute Negative Wrap Convolution NTT as specified in EIP-NTT +/************************************************************************************************************************************************************************/ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +contract ZKNOX_NTT{ + +/************************************************************************************************************************************************************************/ +/* COMMON */ +/************************************************************************************************************************************************************************/ + +//Vectorized modular multiplication +//Multiply chunk wise vectors of n chunks modulo q +function ZKNOX_VECMULMOD( + uint[] memory a, + uint[] memory b, + uint q + ) public pure returns (uint[] memory) { + + assert(a.length == b.length); + uint[] memory res = new uint[](a.length); + for (uint i = 0; i < a.length; i++) { + res[i] = mulmod(a[i], b[i], q); + } + return res; + } + + +//Vectorized modular multiplication +//Multiply chunk wise vectors of n chunks modulo q +function ZKNOX_VECADDMOD( + uint[] memory a, + uint[] memory b, + uint q + ) public pure returns (uint[] memory) { + + assert(a.length == b.length); + uint[] memory res = new uint[](a.length); + for (uint i = 0; i < a.length; i++) { + res[i] = addmod(a[i], b[i], q); + } + return res; + } + +/************************************************************************************************************************************************************************/ +/* STATEFULL VERSION */ +/************************************************************************************************************************************************************************/ + /* STORAGE FOR THE STATEFUL VERSION */ + address public o_psirev; //external contract containing psi_rev + address public o_psi_inv_rev;//external contract containing psi_inv_rev + uint256 storage_q; + uint256 storage_nm1modq;//n^-1 mod 12289 + uint256 is_immutable;//"antifuse" variable + + constructor(address Apsi_rev, address Apsi_inrev, uint256 q, uint256 nm1modq) { + storage_q = q;//prime field modulus + storage_nm1modq=nm1modq;//n^-1 mod 12289, used in inverse NTT + + o_psirev = Apsi_rev; + o_psi_inv_rev = Apsi_inrev; + is_immutable = 1; + } + + function update(address Apsi_rev, address Apsi_inrev, uint256 q, uint256 nm1modq) public{ + + if(is_immutable>0){ + storage_q = q;//prime field modulus + storage_nm1modq=nm1modq;//n^-1 mod 12289, used in inverse NTT + + o_psirev = Apsi_rev; + o_psi_inv_rev = Apsi_inrev; + } + } + + //by calling this function, the contract storage variables cannot be modified (precomputed values) + function make_immutable() public{ + is_immutable=1; + } + +// NTT_FW as specified by EIP, statefull version +//address apsirev: address of the contract storing the powers of psi +function ZKNOX_NTTFW(uint[] memory a, address apsirev) public view returns (uint[] memory){ + uint n=a.length; + uint t=n; + uint m=1; + uint q=storage_q; + + uint[1] memory S; + + + assembly{ + for {} gt(n,m) { } {//while(m 1) + let j1:=0 + let h:=shr(1,m)//uint h = m>>1; + for {let i:=0} gt(h,i) { i := add(i, 1) } { //while(m>1; + for(uint i=0;i 1){ + uint j1 = 0; + uint h = m>>1; + for(uint i=0;i>1; + + }//end while + + t=storage_nm1modq;//sparing one variable for stack + for(m=0;m Date: Tue, 18 Feb 2025 09:02:46 +0100 Subject: [PATCH 02/21] update mail, link and PR number --- EIPS/{eip-ntt.md => eip-9374.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename EIPS/{eip-ntt.md => eip-9374.md} (98%) diff --git a/EIPS/eip-ntt.md b/EIPS/eip-9374.md similarity index 98% rename from EIPS/eip-ntt.md rename to EIPS/eip-9374.md index 97d7bffc47dcf7..6ae82afb75afdd 100644 --- a/EIPS/eip-ntt.md +++ b/EIPS/eip-9374.md @@ -1,7 +1,7 @@ --- title: description: -author: +author: discussions-to: status: Draft type: @@ -234,8 +234,8 @@ There is no edge cases in the considered operations. There are two fully spec compatible implementations on the day of writing: -- a python reference code provided in the [assets]() of this EIP -- a solidity reference code provided in the [assets]() of this EIP +- a python reference code provided in the assets of this EIP +- a solidity reference code provided in the assets of this EIP Both codes have been validated over a large base of reference vectors, and implementing both FALCON and DILITHIUM algorithms as demonstration of the usefulness of the precompile. ## Test Cases -There is no edge cases in the considered operations. +There are no edge cases in the considered operations. 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 literal 0 HcmV?d00001 diff --git a/assets/eip-9374/image.png b/assets/eip-9374/image.png new file mode 100644 index 0000000000000000000000000000000000000000..4b60dbf0ddef3d99b1f5f88c9844dcb0a54d4d37 GIT binary patch literal 111544 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%0Aqc48JiiKe=?esf3a&!hHnAud!Z*KP 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 z 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 Date: Tue, 18 Feb 2025 15:00:01 +0100 Subject: [PATCH 08/21] removal of TODOs useless sections --- EIPS/eip-9374.md | 63 +----------------------------------------------- 1 file changed, 1 insertion(+), 62 deletions(-) diff --git a/EIPS/eip-9374.md b/EIPS/eip-9374.md index ab54cb374f39c1..307742dcf66063 100644 --- a/EIPS/eip-9374.md +++ b/EIPS/eip-9374.md @@ -6,21 +6,10 @@ author: Renaud Dubois (rdubois-crypto) , Simon Masso discussions-to: https://ethresear.ch/t/ntt-as-postquantum-and-starks-settlements-helper-precompile/21775 status: Draft type: Standards Track -category: Core # Only required for Standards Track. Otherwise, remove this field. +category: Core created: 2025-02-12 --- - ## Abstract @@ -30,15 +19,6 @@ This proposal creates a precompiled contract that performs NTT and Inverse NTT t With the release of Willow cheap, the concern for quantum threat against Ethereum accelerated. Today ECDSA is the EOA signature algorithms, which is prone to quantum computing. Efficient replacement algorithms use polynomial multiplication as the core operation. Once NTT and Inverse NTT are available, the remaining of the verification algorithm is trivial. Choosing to integrate NTT and InvNTT instead of a specific algorithm provides agility, as DILITHIUM or FALCON or any equivalent can be implemented with a modest cost from those operators. NTT is also of interest to speed-up STARK verifiers. This single operator would thus benefit to both the Ethereum scaling and Post Quantum threat mitigation. - ## Specification @@ -187,39 +167,14 @@ Adopting the hash function as a separate EIP would enable a gas verification cos This is in line with the ratio looking at SUPERCOP implementations. - ## Backwards Compatibility There are no backward compatibility questions. - ## Test Cases There are no edge cases in the considered operations. - ## Reference Implementation @@ -229,25 +184,9 @@ There are two fully spec compatible implementations on the day of writing: - a solidity reference code provided in the assets of this EIP Both codes have been validated over a large base of reference vectors, and implementing both FALCON and DILITHIUM algorithms as demonstration of the usefulness of the precompile. - ## Security Considerations - - Needs discussion. ## Copyright From 096f81a434171ecbf8a11e8609d78e035bfa1a2e Mon Sep 17 00:00:00 2001 From: renaud dubois Date: Tue, 18 Feb 2025 15:20:49 +0100 Subject: [PATCH 09/21] update with magician thread --- EIPS/eip-9374.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/EIPS/eip-9374.md b/EIPS/eip-9374.md index 307742dcf66063..26a134c4152c42 100644 --- a/EIPS/eip-9374.md +++ b/EIPS/eip-9374.md @@ -2,8 +2,8 @@ eip: 9374 title: Precompile for NTT operations description: Proposal to add a precompiled contract that performs number theoretical transformation (NTT) and inverse (InvNTT). -author: Renaud Dubois (rdubois-crypto) , Simon Masson -discussions-to: https://ethresear.ch/t/ntt-as-postquantum-and-starks-settlements-helper-precompile/21775 +author: Renaud Dubois (@rdubois-crypto), Simon Masson (@simonmasson) +discussions-to: https://ethereum-magicians.org/t/eip-9374-precompile-for-ntt-operations/22895 status: Draft type: Standards Track category: Core @@ -34,8 +34,11 @@ With the release of Willow cheap, the concern for quantum threat against Ethereu We introduce *four* separate precompiles to perform the following operations: - NTT_FW - to perform the forward NTT transformation (Negative wrap convolution) with a gas cost of `600` gas, + - NTT_INV - to perform the inverse NTT transformation (Negative wrap convolution) with a gas cost of `600` gas, + - NTT_VECMULMOD - to perform vectorized modular multiplication with a gas cost formula defined in the corresponding section, + - NTT_VECADDMOD - to perform vectorized modular addition with a gas cost formula defined in the corresponding section. @@ -43,9 +46,13 @@ We introduce *four* separate precompiles to perform the following operations: The NTT_FW and NTT_INV are fully defined by the following set of parameters. Let $R$ be a cyclotomic ring of the form $R=\mathbb F_q[X]/(X^n+1)$. In these notations, + - $n$ is the degree and is a power of 2, + - $\mathbb F_q$ is the prime field where $q=1 \mod 2n$, + - $\omega$ is a $n$-th root of unity in $\mathbb F_q$, + - $\psi$ is a $2n$-th root of unity in $\mathbb F_q$. Any element $a \in R$ is a polynomial of degree at most $n-1$ with integer coefficients, written @@ -86,6 +93,7 @@ The Inverse NTT is described by the following algorithm. **Output:** $a \leftarrow \text{NTT\_INV}(a)$ in standard order. ```plaintext + t ← 1 for m = n to 1 by m/2 do j1 ← 0 @@ -139,12 +147,19 @@ $f\times g= \text{NTT\_INV}(\text{NTT\_VECMULMOD}( ### Fields of interest The implementation applies for many fields of interest for cryptography. In particular, the design applies for: + - FALCON: $q=3.2^{12}+1$ (one of the NIST winners for post-quantum signature scheme), + - DILITHIUM: $q=2^{23}-2^{13}+1$ (one of the NIST winners for post-quantum signature scheme), + - KYBER: $q=13.2^8+1$ (one of the NIST winners for post-quantum key encapsulation mechanism), -- Babybear: $q=15.2^{27}+1$ (Risc0) -- Goldilocks: $q=2^{64}-2^{32}+1$ (Polygon's Plonky2) -- M31: $q=2^{31}-1$ (Circle STARKS, STwo, Plonky3) + +- Babybear: $q=15.2^{27}+1$ (Risc0), + +- Goldilocks: $q=2^{64}-2^{32}+1$ (Polygon's Plonky2), + +- M31: $q=2^{31}-1$ (Circle STARKS, STwo, Plonky3), + - StarkCurve: $q=2^{251}+17.2^{192}+1$ @@ -180,8 +195,11 @@ There are no edge cases in the considered operations. There are two fully spec compatible implementations on the day of writing: + - a python reference code provided in the assets of this EIP + - a solidity reference code provided in the assets of this EIP + Both codes have been validated over a large base of reference vectors, and implementing both FALCON and DILITHIUM algorithms as demonstration of the usefulness of the precompile. From 59dc1ae660f4405271dfdc07cb26d434073aa79d Mon Sep 17 00:00:00 2001 From: renaud dubois Date: Tue, 18 Feb 2025 15:27:49 +0100 Subject: [PATCH 10/21] remove blank lines --- EIPS/eip-9374.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/EIPS/eip-9374.md b/EIPS/eip-9374.md index 26a134c4152c42..0a31785c02773e 100644 --- a/EIPS/eip-9374.md +++ b/EIPS/eip-9374.md @@ -60,6 +60,7 @@ as $a=\sum_{i=0}^{n-1} a_iX^i$ ### NTT_FW + The NTT transformation is described by the following algorithm. **Input:** A vector $a = (a[0], a[1], \dots, a[n-1]) \in \mathbb F_q^n$ in standard order, where $q$ is a prime such that $q \equiv 1 \mod 2n$ and $n$ is a power of two, and a precomputed table $\Psi_\text{rev} \in \mathbb{Z}_q^n$ storing powers of $\psi$ in bit-reversed order. @@ -86,6 +87,7 @@ return a ``` ### NTT_INV + The Inverse NTT is described by the following algorithm. **Input:** A vector $a = (a[0], a[1], \dots, a[n-1]) \in \mathbb F_q^n$ in bit-reversed order, where $q$ is a prime such that $q \equiv 1 \mod 2n$ and $n$ is a power of two, and a precomputed table $\Psi^{-1}_\text{rev} \in \mathbb F_q^n$ storing powers of $\psi^{-1}$ in bit-reversed order. @@ -146,6 +148,7 @@ $f\times g= \text{NTT\_INV}(\text{NTT\_VECMULMOD}( \text{NTT\_FW}(a), \text{NTT\_FW}(b)))$ is equal to the product of $f$ and $g$ in $R$. The algorithm has a complexity of $n \log_2n$ rather than $n^2$ with the classical schoolbook multiplication algorithm. ### Fields of interest + The implementation applies for many fields of interest for cryptography. In particular, the design applies for: - FALCON: $q=3.2^{12}+1$ (one of the NIST winners for post-quantum signature scheme), @@ -188,6 +191,7 @@ This is in line with the ratio looking at SUPERCOP implementations. There are no backward compatibility questions. ## Test Cases + There are no edge cases in the considered operations. From 3af803a4fd8b10fb0782f3969b1291c8de0d8d01 Mon Sep 17 00:00:00 2001 From: renaud dubois Date: Tue, 18 Feb 2025 16:37:08 +0100 Subject: [PATCH 11/21] remove blank line --- EIPS/eip-9374.md | 1 + 1 file changed, 1 insertion(+) diff --git a/EIPS/eip-9374.md b/EIPS/eip-9374.md index 0a31785c02773e..77a4e49c8cf208 100644 --- a/EIPS/eip-9374.md +++ b/EIPS/eip-9374.md @@ -188,6 +188,7 @@ This is in line with the ratio looking at SUPERCOP implementations. ## Backwards Compatibility + There are no backward compatibility questions. ## Test Cases From 09b3f35ccf4bd908f0b64980cdd5225c73d5a25a Mon Sep 17 00:00:00 2001 From: renaud dubois Date: Wed, 19 Feb 2025 10:32:17 +0100 Subject: [PATCH 12/21] update EIP number according to provided number --- EIPS/{eip-9374.md => eip-7885.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename EIPS/{eip-9374.md => eip-7885.md} (99%) diff --git a/EIPS/eip-9374.md b/EIPS/eip-7885.md similarity index 99% rename from EIPS/eip-9374.md rename to EIPS/eip-7885.md index 77a4e49c8cf208..d4cf1cd95a49ea 100644 --- a/EIPS/eip-9374.md +++ b/EIPS/eip-7885.md @@ -1,5 +1,5 @@ --- -eip: 9374 +eip: 7885 title: Precompile for NTT operations description: Proposal to add a precompiled contract that performs number theoretical transformation (NTT) and inverse (InvNTT). author: Renaud Dubois (@rdubois-crypto), Simon Masson (@simonmasson) From 40b359586966600814a989e000aed2e0c8a96d59 Mon Sep 17 00:00:00 2001 From: Renaud Dubois <103030189+rdubois-crypto@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:54:59 +0100 Subject: [PATCH 13/21] Update EIPS/eip-7885.md Co-authored-by: Marc --- EIPS/eip-7885.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7885.md b/EIPS/eip-7885.md index d4cf1cd95a49ea..3b5e6dffbf209f 100644 --- a/EIPS/eip-7885.md +++ b/EIPS/eip-7885.md @@ -13,7 +13,7 @@ created: 2025-02-12 ## Abstract -This proposal creates a precompiled contract that performs NTT and Inverse NTT transformations. This provides a way to have efficient and fast polynomial multiplication for Post Quantum and Starks applications. +This proposal creates a precompiled contract that performs NTT and Inverse NTT transformations. This provides a way to efficiently perform fast polynomial multiplication for post-quantum and STARK cryptography. ## Motivation From 3ebc1fc593081fcbbc031ec17d8d1bef8b01edb0 Mon Sep 17 00:00:00 2001 From: Renaud Dubois <103030189+rdubois-crypto@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:55:09 +0100 Subject: [PATCH 14/21] Update EIPS/eip-7885.md Co-authored-by: Marc --- EIPS/eip-7885.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7885.md b/EIPS/eip-7885.md index 3b5e6dffbf209f..f03fc804cbba7e 100644 --- a/EIPS/eip-7885.md +++ b/EIPS/eip-7885.md @@ -17,7 +17,7 @@ This proposal creates a precompiled contract that performs NTT and Inverse NTT t ## Motivation -With the release of Willow cheap, the concern for quantum threat against Ethereum accelerated. Today ECDSA is the EOA signature algorithms, which is prone to quantum computing. Efficient replacement algorithms use polynomial multiplication as the core operation. Once NTT and Inverse NTT are available, the remaining of the verification algorithm is trivial. Choosing to integrate NTT and InvNTT instead of a specific algorithm provides agility, as DILITHIUM or FALCON or any equivalent can be implemented with a modest cost from those operators. NTT is also of interest to speed-up STARK verifiers. This single operator would thus benefit to both the Ethereum scaling and Post Quantum threat mitigation. +With the recent advances in quantum computing, there are increased concerns for the quantum threat against Ethereum. Today ECDSA is the EOA signature algorithms, which is vulnerable to attacks by quantum computers. Efficient replacement algorithms use polynomial multiplication as the core operation. Once NTT and Inverse NTT are available, the remaining of the verification algorithm is trivial. Choosing to integrate NTT and InvNTT instead of a specific algorithm provides agility, as DILITHIUM or FALCON or any equivalent can be implemented with a modest cost from those operators. NTT is also of interest to speed-up STARK verifiers. This single operator would thus benefit to both the Ethereum scaling and post-quantum threat mitigation. ## Specification From 4127991e69121eb9c2b11e0dd5955c52555045c4 Mon Sep 17 00:00:00 2001 From: Renaud Dubois <103030189+rdubois-crypto@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:55:15 +0100 Subject: [PATCH 15/21] Update EIPS/eip-7885.md Co-authored-by: Marc --- EIPS/eip-7885.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7885.md b/EIPS/eip-7885.md index f03fc804cbba7e..5199690e1f2fc1 100644 --- a/EIPS/eip-7885.md +++ b/EIPS/eip-7885.md @@ -122,7 +122,7 @@ return a ### NTT_VECMULMOD -The NTT_VECMULMOD is similar to SIMD in the functioning, but operates with larger sizes in input and output. +The NTT_VECMULMOD is functions similarly to SIMD, but operates with larger input and output sizes. **Input:** Two vectors $a = (a[0], a[1], \dots, a[n-1]), b=(b[0], b[1], \dots, b[n-1]) \in \mathbb F_q^n$ where $n$ and $q$ are defined above. From c42f0716edb3f13aeb17e90252bca1df3fb7af4d Mon Sep 17 00:00:00 2001 From: Renaud Dubois <103030189+rdubois-crypto@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:55:24 +0100 Subject: [PATCH 16/21] Update EIPS/eip-7885.md Co-authored-by: Marc --- EIPS/eip-7885.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7885.md b/EIPS/eip-7885.md index 5199690e1f2fc1..4bdb30b25d13a5 100644 --- a/EIPS/eip-7885.md +++ b/EIPS/eip-7885.md @@ -44,7 +44,7 @@ We introduce *four* separate precompiles to perform the following operations: ### Field parameters -The NTT_FW and NTT_INV are fully defined by the following set of parameters. +The NTT_FW and NTT_INV are fully defined by the following set of parameters: Let $R$ be a cyclotomic ring of the form $R=\mathbb F_q[X]/(X^n+1)$. In these notations, - $n$ is the degree and is a power of 2, From da332ca920d952118bc107945e8f28ee88bcbdbc Mon Sep 17 00:00:00 2001 From: Renaud Dubois <103030189+rdubois-crypto@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:55:33 +0100 Subject: [PATCH 17/21] Update EIPS/eip-7885.md Co-authored-by: Marc --- EIPS/eip-7885.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7885.md b/EIPS/eip-7885.md index 4bdb30b25d13a5..bdbef3ce75e6da 100644 --- a/EIPS/eip-7885.md +++ b/EIPS/eip-7885.md @@ -128,7 +128,7 @@ The NTT_VECMULMOD is functions similarly to SIMD, but operates with larger input **Output:** The element-wise product $(a[0]\cdot b[0] \mod q, a[1]\cdot b[1]\mod q, \dots, a[n-1]\cdot b[n-1] \mod q)$. -**Gas cost:** Denotoing $k$ to be the smallest power of $2$ larger than $\log_2(q)$, the gas cost of this operation is $k\log_2(n) / 8$. +**Gas cost:** Denoting $k$ to be the smallest power of $2$ larger than $\log_2(q)$, the gas cost of this operation is $k\log_2(n) / 8$. ### NTT_VECADDMOD From 7da5b784c8ee87b4bbc6e4aae2a3c4e9266dcae4 Mon Sep 17 00:00:00 2001 From: Renaud Dubois <103030189+rdubois-crypto@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:55:43 +0100 Subject: [PATCH 18/21] Update EIPS/eip-7885.md Co-authored-by: Marc --- EIPS/eip-7885.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7885.md b/EIPS/eip-7885.md index bdbef3ce75e6da..3a07d95517ad52 100644 --- a/EIPS/eip-7885.md +++ b/EIPS/eip-7885.md @@ -179,7 +179,7 @@ To illustrate the interest of the precompile, the assets provide the measured ga Falcon cost has been measured over a full implementation and is compliant to the estimation. Dilithium cost is evaluated assuming -This demonstrates that using pure solidity enables cheap L2s to experiment with FALCON from now, but is to expensive for L1. +This demonstrates that using pure solidity enables L2s with low gas fees to experiment with FALCON in the short term, whereas it is too expensive to do so on L1. Adopting this EIP, the signature verification of Falcon can be reduced to **1500** gas, and a similar result is expected for Dilithium. Adopting the hash function as a separate EIP would enable a gas verification cost of 2000 gas. This is in line with the ratio looking at SUPERCOP implementations. From 517431d3db9017212f26d10e027e38031c0e190d Mon Sep 17 00:00:00 2001 From: Renaud Dubois <103030189+rdubois-crypto@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:55:53 +0100 Subject: [PATCH 19/21] Update EIPS/eip-7885.md Co-authored-by: Marc --- EIPS/eip-7885.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7885.md b/EIPS/eip-7885.md index 3a07d95517ad52..37ec88bb119550 100644 --- a/EIPS/eip-7885.md +++ b/EIPS/eip-7885.md @@ -155,7 +155,7 @@ The implementation applies for many fields of interest for cryptography. In part - DILITHIUM: $q=2^{23}-2^{13}+1$ (one of the NIST winners for post-quantum signature scheme), -- KYBER: $q=13.2^8+1$ (one of the NIST winners for post-quantum key encapsulation mechanism), +- KYBER: $q=13.2^8+1$ (one of the NIST winners for post-quantum key encapsulation mechanism), - Babybear: $q=15.2^{27}+1$ (Risc0), From 7636b4717dfec55ab13d07a6ca2f8e4489d7a33f Mon Sep 17 00:00:00 2001 From: Renaud Dubois <103030189+rdubois-crypto@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:56:00 +0100 Subject: [PATCH 20/21] Update EIPS/eip-7885.md Co-authored-by: Marc --- EIPS/eip-7885.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7885.md b/EIPS/eip-7885.md index 37ec88bb119550..1fa8f5bd8f1513 100644 --- a/EIPS/eip-7885.md +++ b/EIPS/eip-7885.md @@ -182,7 +182,7 @@ Falcon cost has been measured over a full implementation and is compliant to the This demonstrates that using pure solidity enables L2s with low gas fees to experiment with FALCON in the short term, whereas it is too expensive to do so on L1. Adopting this EIP, the signature verification of Falcon can be reduced to **1500** gas, and a similar result is expected for Dilithium. Adopting the hash function as a separate EIP would enable a gas verification cost of 2000 gas. -This is in line with the ratio looking at SUPERCOP implementations. +This is in line with the ratio looking at SUPERCOP implementations. From e3abf625fb3972977ed26d47cb64278f5c7798c0 Mon Sep 17 00:00:00 2001 From: Renaud Dubois <103030189+rdubois-crypto@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:56:10 +0100 Subject: [PATCH 21/21] Update assets/eip-9374/README.md Co-authored-by: Marc --- assets/eip-9374/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/eip-9374/README.md b/assets/eip-9374/README.md index 8567a414a20f04..44554d1b04e3b2 100644 --- a/assets/eip-9374/README.md +++ b/assets/eip-9374/README.md @@ -1,4 +1,4 @@ -# NTT-EIP as a building block for FALCON, DILITHIUM and Stark verifiers +# NTT-EIP as a building block for FALCON, DILITHIUM and STARK verifiers This repository contains the EIP for NTT transform, along with a python reference code, and a solidity implementation.