├── .gitignore ├── LICENSE ├── README.md ├── abstract-algebra-charles-pinter-notes.pdf ├── abstract-algebra-charles-pinter-notes.tex ├── blind-sign-over-ec.sage ├── bls-sigs.sage ├── bls12-381.sage ├── ccs-plonk.sage ├── ccs-r1cs.sage ├── fft-notes.bib ├── fft-notes.pdf ├── fft-notes.tex ├── fft.sage ├── galois-theory-notes.pdf ├── galois-theory-notes.tex ├── ipa.sage ├── kzg.sage ├── notes_bls-sig.pdf ├── notes_bls-sig.tex ├── notes_caulk.pdf ├── notes_caulk.tex ├── notes_fri_stir.pdf ├── notes_fri_stir.tex ├── notes_halo.pdf ├── notes_halo.tex ├── notes_hypernova.pdf ├── notes_hypernova.tex ├── notes_nova.pdf ├── notes_nova.tex ├── notes_ntt.pdf ├── notes_ntt.tex ├── notes_reed-solomon.bib ├── notes_reed-solomon.pdf ├── notes_reed-solomon.tex ├── notes_sonic.pdf ├── notes_sonic.tex ├── notes_spartan.pdf ├── notes_spartan.tex ├── number-theory.sage ├── paper-notes.bib ├── powersoftau.sage ├── ring-signatures.sage ├── seminarexercises.pdf ├── seminarexercises.tex ├── sigma-or-notes.pdf ├── sigma-or-notes.tex ├── sigma.sage ├── slides_hypernova-part1-introduction.pdf ├── slides_hypernova-part1-introduction.tex ├── slides_hypernova-part2-multifolding-unfolded.pdf ├── slides_hypernova-part2-multifolding-unfolded.tex ├── typos.toml ├── weil-pairing.pdf └── weil-pairing.tex /.gitignore: -------------------------------------------------------------------------------- 1 | *.sage.py 2 | *.aux 3 | *.fdb_latexmk 4 | *.fls 5 | *.log 6 | *.out 7 | *.synctex.gz 8 | *.toc 9 | *.bbl 10 | *.blg 11 | *.nav 12 | *.snm 13 | *.vrb 14 | galois-theory-notes.bib 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # math/cryptography 2 | 3 | Notes, code and documents done while reading books and papers. 4 | 5 | ## mathematics 6 | 7 | - [Notes on "Abstract Algebra" book, by Charles C. Pinter](abstract-algebra-charles-pinter-notes.pdf) 8 | - [Notes on Weil pairing](weil-pairing.pdf) 9 | - [Notes on Galois Theory](galois-theory-notes.pdf) 10 | 11 | 12 | In-between math & crypto: 13 | 14 | - [Notes on the DFT & FFT](fft-notes.pdf) 15 | - [Notes on NTT](notes_ntt.pdf) 16 | - [Notes on Reed-Solomon codes](notes_reed-solomon.pdf) 17 | 18 | ## cryptography 19 | 20 | - [Notes on Caulk & Caulk+ papers](notes_caulk.pdf) 21 | - [Notes on the BLS signatures](notes_bls-sig.pdf) 22 | - [Notes on IPA from Halo paper](notes_halo.pdf) 23 | - [Notes on Sonic paper](notes_sonic.pdf) 24 | - [Notes on Sigma protocol and OR proofs](sigma-or-notes.pdf) 25 | - [Notes on FRI and STIR](notes_fri_stir.pdf) 26 | - [Notes on Spartan](notes_spartan.pdf) 27 | - [Notes on Nova](notes_nova.pdf) 28 | - [Notes on HyperNova](notes_hypernova.pdf) 29 | 30 | ## code 31 | Also some Sage implementations can be found in the `*.sage` files of this repo. 32 | Also some of the algorithms and schemes can be found implemented (mostly in Rust language) in various repositories of the github https://github.com/arnaucube . 33 | -------------------------------------------------------------------------------- /abstract-algebra-charles-pinter-notes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaucube/math/6593857e91fe437f72d5420ef23fec3ad29b0b40/abstract-algebra-charles-pinter-notes.pdf -------------------------------------------------------------------------------- /blind-sign-over-ec.sage: -------------------------------------------------------------------------------- 1 | # Implementation of: https://sci-hub.se/10.1109/iccke.2013.6682844 2 | # more details at: https://arnaucube.com/blog/blind-signatures-ec.html#the-scheme 3 | # A Go implementation of this schema can be found at: https://github.com/arnaucube/go-blindsecp256k1 4 | 5 | from hashlib import sha256 6 | 7 | def hash(m): 8 | h_output = sha256(str(m).encode('utf-8')) 9 | return int(h_output.hexdigest(), 16) 10 | 11 | 12 | 13 | class User: 14 | def __init__(self, F, G): 15 | self.F = F # Z_q 16 | self.G = G # elliptic curve generator 17 | 18 | def blind_msg(self, m, R_): 19 | self.a = self.F.random_element() 20 | self.b = self.F.random_element() 21 | self.R = self.a * R_ + self.b * self.G 22 | m_ = self.F(self.a)^(-1) * self.F(self.R.xy()[0]) * self.F(hash(m)) 23 | return m_ 24 | 25 | def unblind_sig(self, s_): 26 | s = self.a * s_ + self.b 27 | return (self.R, s) 28 | 29 | 30 | class Signer: 31 | def __init__(self, F, G): 32 | self.F = F # Z_q 33 | self.G = G # elliptic curve generator 34 | 35 | # gen Signer's key pair 36 | self.d = self.F.random_element() 37 | self.Q = self.G * self.d 38 | 39 | 40 | def new_request_params(self): 41 | self.k = self.F.random_element() 42 | R_ = self.G * self.k 43 | return R_ 44 | 45 | def blind_sign(self, m_): 46 | return self.d * m_ + self.k 47 | 48 | def verify(G, Q, sig, m): 49 | (R, s) = sig 50 | return s*G == R + (Fq(R.xy()[0]) * Fq(hash(m))) * Q 51 | 52 | 53 | 54 | # ethereum elliptic curve 55 | p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F # base field 56 | a = 0 57 | b = 7 58 | F = GF(p) # base field 59 | E = EllipticCurve(F, [a,b]) 60 | GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 61 | GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 62 | g = E(GX,GY) 63 | n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 64 | q = g.order() # order of Fp 65 | assert is_prime(p) 66 | assert is_prime(q) 67 | Fq = GF(q) # scalar field 68 | 69 | 70 | 71 | # protocol flow: 72 | 73 | user = User(Fq, g) 74 | signer = Signer(Fq, g) 75 | 76 | R_ = signer.new_request_params() 77 | 78 | m = 12345 # user's message 79 | m_ = user.blind_msg(m, R_) 80 | 81 | s_ = signer.blind_sign(m_) 82 | 83 | sig = user.unblind_sig(s_) 84 | 85 | v = verify(g, signer.Q, sig, m) 86 | print(v) 87 | assert v 88 | -------------------------------------------------------------------------------- /bls-sigs.sage: -------------------------------------------------------------------------------- 1 | # toy implementation of BLS signatures 2 | 3 | load("bls12-381.sage") 4 | from hashlib import sha256 5 | 6 | def hash(m): 7 | h_output = sha256(str(m).encode('utf-8')) 8 | return int(h_output.hexdigest(), 16) 9 | 10 | def hash_to_point(m): 11 | # WARNING this hash-to-point approach should not be used! 12 | h = hash(m) 13 | return e.G2 * h 14 | 15 | 16 | e = Pairing() 17 | 18 | class Signer: 19 | def __init__(self): 20 | self.sk = e.F1.random_element() 21 | self.pk = self.sk * e.G1 22 | 23 | def sign(self, m): 24 | H = hash_to_point(m) 25 | return self.sk * H 26 | 27 | def verify(pk, s, m): 28 | H = hash_to_point(m) 29 | return e.pair(e.G1, s) == e.pair(pk, H) 30 | 31 | def aggr(points): 32 | R = 0 33 | for i in range(len(points)): 34 | R = R + points[i] 35 | return R 36 | 37 | 38 | m = 1234 39 | 40 | # single signature & verification 41 | user0 = Signer() 42 | s = user0.sign(m) 43 | v = verify(user0.pk, s, m) 44 | assert v 45 | 46 | 47 | # BLS signature aggregation 48 | n = 10 49 | users = [None]*n 50 | pks = [None]*n 51 | sigs = [None]*n 52 | for i in range(n): 53 | users[i] = Signer() 54 | pks[i] = users[i].pk 55 | sigs[i] = users[i].sign(m) 56 | 57 | # aggregate sigs & pks 58 | s_aggr = aggr(sigs) 59 | pk_aggr = aggr(pks) 60 | 61 | # verify aggregated signature 62 | v = verify(pk_aggr, s_aggr, m) 63 | assert v 64 | -------------------------------------------------------------------------------- /bls12-381.sage: -------------------------------------------------------------------------------- 1 | # The code of this file has been adapted from: 2 | # https://github.com/osirislab/CSAW-CTF-2021-Finals/blob/main/crypto/aBoLiSh_taBLeS/chal.sage 3 | # 4 | # ## Example of usage: 5 | # load("bls12-381.sage") 6 | # e = Pairing() 7 | # assert e.pair(e.G1 * 3, e.G2 * 2) == e.pair(e.G1, e.G2)^6 8 | 9 | 10 | class Pairing(): 11 | def __init__(self): 12 | # BLS12-381 Parameters 13 | # https://github.com/zkcrypto/bls12_381 14 | self.p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab 15 | self.r = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 16 | self.h1 = 0x396c8c005555e1568c00aaab0000aaab 17 | self.h2 = 0x5d543a95414e7f1091d50792876a202cd91de4547085abaa68a205b2e5a7ddfa628f1cb4d9e82ef21537e293a6691ae1616ec6e786f0c70cf1c38e31c7238e5 18 | 19 | # Define base fields 20 | self.F1 = GF(self.p) 21 | F2. = GF(self.p^2, x, x^2 + 1) 22 | self.u = u 23 | self.F2 = F2 24 | F12. = GF(self.p^12, x, x^12 - 2*x^6 + 2) 25 | self.w = w 26 | self.F12 = F12 27 | 28 | # Define the Elliptic Curves 29 | self.E1 = EllipticCurve(self.F1, [0, 4]) 30 | self.E2 = EllipticCurve(F2, [0, 4*(1 + self.u)]) 31 | self.E12 = EllipticCurve(F12, [0, 4]) 32 | 33 | # Generator of order r in E1 / F1 34 | G1x = 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb 35 | G1y = 0x8b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1 36 | self.G1 = self.E1(G1x, G1y) 37 | 38 | # Generator of order r in E2 / F2 39 | G2x0 = 0x24aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8 40 | G2x1 = 0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e 41 | G2y0 = 0xce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801 42 | G2y1 = 0x606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be 43 | self.G2 = self.E2(G2x0 + self.u*G2x1, G2y0 + self.u*G2y1) 44 | 45 | 46 | def lift_E1_to_E12(self, P): 47 | """ 48 | Lift point on E/F_q to E/F_{q^12} using the natural lift 49 | """ 50 | assert P.curve() == self.E1, "Attempting to lift a point from the wrong curve." 51 | return self.E12(P) 52 | 53 | def lift_E2_to_E12(self, P): 54 | """ 55 | Lift point on E/F_{q^2} to E/F_{q_12} through the sextic twist 56 | """ 57 | assert P.curve() == self.E2, "Attempting to lift a point from the wrong curve." 58 | xs, ys = [c.polynomial().coefficients() for c in (self.h2*P).xy()] 59 | nx = self.F12(xs[0] - xs[1] + self.w ^ 6*xs[1]) 60 | ny = self.F12(ys[0] - ys[1] + self.w ^ 6*ys[1]) 61 | return self.E12(nx / (self.w ^ 2), ny / (self.w ^ 3)) 62 | 63 | def pair(self, A, B): 64 | A = self.lift_E1_to_E12(A) 65 | B = self.lift_E2_to_E12(B) 66 | return A.ate_pairing(B, self.r, 12, self.E12.trace_of_frobenius()) 67 | 68 | -------------------------------------------------------------------------------- /ccs-plonk.sage: -------------------------------------------------------------------------------- 1 | # Plonk-CCS (https://eprint.iacr.org/2023/552) Sage prototype 2 | 3 | # utils 4 | def matrix_vector_product(M, v): 5 | n = M.nrows() 6 | r = [F(0)] * n 7 | for i in range(0, n): 8 | for j in range(0, M.ncols()): 9 | r[i] += M[i][j] * v[j] 10 | return r 11 | def hadamard_product(a, b): 12 | n = len(a) 13 | r = [None] * n 14 | for i in range(0, n): 15 | r[i] = a[i] * b[i] 16 | return r 17 | def vec_add(a, b): 18 | n = len(a) 19 | r = [None] * n 20 | for i in range(0, n): 21 | r[i] = a[i] + b[i] 22 | return r 23 | def vec_elem_mul(a, s): 24 | r = [None] * len(a) 25 | for i in range(0, len(a)): 26 | r[i] = a[i] * s 27 | return r 28 | # end of utils 29 | 30 | 31 | # can use any finite field, using a small one for the example 32 | F = GF(101) 33 | # F = GF(21888242871839275222246405745257275088696311157297823662689037894645226208583) 34 | 35 | 36 | # The following CCS instance values have been provided by Carlos 37 | # (https://github.com/CPerezz) and Edu (https://github.com/ed255), 38 | # and this sage script was made to check the CCS relation. 39 | 40 | ## Checks performed by this Plonk/CCS instance: 41 | # - binary check for x0, x1 42 | # - 2*x2 + 2*x3 == x4 43 | M0 = matrix([ 44 | [F(0), 1, 0, 0, 0, 0, 0], 45 | [0, 0, 1, 0, 0, 0, 0], 46 | [0, 0, 0, 1, 0, 0, 0], 47 | [0, 0, 0, 0, 0, 0, 1], 48 | ]) 49 | M1 = matrix([ 50 | [F(0), 1, 0, 0, 0, 0, 0], 51 | [0, 0, 1, 0, 0, 0, 0], 52 | [0, 0, 0, 0, 1, 0, 0], 53 | [0, 0, 0, 0, 0, 0, 1], 54 | ]) 55 | M2 = matrix([ 56 | [F(0), 1, 0, 0, 0, 0, 0], 57 | [0, 0, 1, 0, 0, 0, 0], 58 | [0, 0, 0, 0, 0, 1, 0], 59 | [0, 0, 0, 0, 0, 0, 1], 60 | ]) 61 | M3 = matrix([ 62 | [F(1), 0, 0, 0, 0, 0, 0], 63 | [1, 0, 0, 0, 0, 0, 0], 64 | [0, 0, 0, 0, 0, 0, 0], 65 | [0, 0, 0, 0, 0, 0, 0], 66 | ]) 67 | M4 = matrix([ 68 | [F(0), 0, 0, 0, 0, 0, 0], 69 | [0, 0, 0, 0, 0, 0, 0], 70 | [2, 0, 0, 0, 0, 0, 0], 71 | [0, 0, 0, 0, 0, 0, 0], 72 | ]) 73 | M5 = matrix([ 74 | [F(0), 0, 0, 0, 0, 0, 0], 75 | [0, 0, 0, 0, 0, 0, 0], 76 | [2, 0, 0, 0, 0, 0, 0], 77 | [0, 0, 0, 0, 0, 0, 0], 78 | ]) 79 | M6 = matrix([ 80 | [F(-1), 0, 0, 0, 0, 0, 0], 81 | [-1, 0, 0, 0, 0, 0, 0], 82 | [-1, 0, 0, 0, 0, 0, 0], 83 | [0, 0, 0, 0, 0, 0, 0], 84 | ]) 85 | M7 = matrix([ 86 | [F(0), 0, 0, 0, 0, 0, 0], 87 | [0, 0, 0, 0, 0, 0, 0], 88 | [0, 0, 0, 0, 0, 0, 0], 89 | [0, 0, 0, 0, 0, 0, 0], 90 | ]) 91 | 92 | z = [F(1), 0, 1, 2, 3, 10, 42] 93 | 94 | print("z:", z) 95 | 96 | assert len(z) == M0.ncols() 97 | 98 | # CCS parameters 99 | n = M0.ncols() # == len(z) 100 | m = M0.nrows() 101 | t=8 102 | q=5 103 | d=3 104 | S = [[3,0,1], [4,0], [5,1], [6,2], [7]] 105 | c = [1, 1, 1, 1, 1] 106 | 107 | M = [M0,M1,M2,M3,M4,M5,M6,M7] 108 | 109 | print("CCS values:") 110 | print("n: %s, m: %s, t: %s, q: %s, d: %s" % (n, m, t, q, d)) 111 | print("M:", M) 112 | print("z:", z) 113 | print("S:", S) 114 | print("c:", c) 115 | 116 | # check CCS relation (this is agnostic to Plonk, for any CCS instance) 117 | r = [F(0)] * m 118 | for i in range(0, q): 119 | hadamard_output = [F(1)]*m 120 | for j in S[i]: 121 | hadamard_output = hadamard_product(hadamard_output, 122 | matrix_vector_product(M[j], z)) 123 | 124 | r = vec_add(r, vec_elem_mul(hadamard_output, c[i])) 125 | 126 | print("\nCCS relation check (∑ cᵢ ⋅ ◯ Mⱼ z == 0):", r == [0]*m) 127 | assert r == [0]*m 128 | -------------------------------------------------------------------------------- /ccs-r1cs.sage: -------------------------------------------------------------------------------- 1 | # R1CS-to-CCS (https://eprint.iacr.org/2023/552) Sage prototype 2 | 3 | # utils 4 | def matrix_vector_product(M, v): 5 | n = M.nrows() 6 | r = [F(0)] * n 7 | for i in range(0, n): 8 | for j in range(0, M.ncols()): 9 | r[i] += M[i][j] * v[j] 10 | return r 11 | def hadamard_product(a, b): 12 | n = len(a) 13 | r = [None] * n 14 | for i in range(0, n): 15 | r[i] = a[i] * b[i] 16 | return r 17 | def vec_add(a, b): 18 | n = len(a) 19 | r = [None] * n 20 | for i in range(0, n): 21 | r[i] = a[i] + b[i] 22 | return r 23 | def vec_elem_mul(a, s): 24 | r = [None] * len(a) 25 | for i in range(0, len(a)): 26 | r[i] = a[i] * s 27 | return r 28 | # end of utils 29 | 30 | 31 | # can use any finite field, using a small one for the example 32 | F = GF(101) 33 | 34 | # R1CS matrices for: x^3 + x + 5 = y (example from article 35 | # https://www.vitalik.ca/general/2016/12/10/qap.html ) 36 | A = matrix([ 37 | [F(0), 1, 0, 0, 0, 0], 38 | [0, 0, 0, 1, 0, 0], 39 | [0, 1, 0, 0, 1, 0], 40 | [5, 0, 0, 0, 0, 1], 41 | ]) 42 | B = matrix([ 43 | [F(0), 1, 0, 0, 0, 0], 44 | [0, 1, 0, 0, 0, 0], 45 | [1, 0, 0, 0, 0, 0], 46 | [1, 0, 0, 0, 0, 0], 47 | ]) 48 | C = matrix([ 49 | [F(0), 0, 0, 1, 0, 0], 50 | [0, 0, 0, 0, 1, 0], 51 | [0, 0, 0, 0, 0, 1], 52 | [0, 0, 1, 0, 0, 0], 53 | ]) 54 | 55 | print("R1CS matrices:") 56 | print("A:", A) 57 | print("B:", B) 58 | print("C:", C) 59 | 60 | 61 | z = [F(1), 3, 35, 9, 27, 30] 62 | print("z:", z) 63 | 64 | assert len(z) == A.ncols() 65 | 66 | n = A.ncols() # == len(z) 67 | m = A.nrows() 68 | # l = len(io) # not used for now 69 | 70 | # check R1CS relation 71 | Az = matrix_vector_product(A, z) 72 | Bz = matrix_vector_product(B, z) 73 | Cz = matrix_vector_product(C, z) 74 | print("\nR1CS relation check (Az ∘ Bz == Cz):", hadamard_product(Az, Bz) == Cz) 75 | assert hadamard_product(Az, Bz) == Cz 76 | 77 | 78 | # Translate R1CS into CCS: 79 | print("\ntranslate R1CS into CCS:") 80 | 81 | # fixed parameters (and n, m, l are direct from R1CS) 82 | t=3 83 | q=2 84 | d=2 85 | S1=[0,1] 86 | S2=[2] 87 | S = [S1, S2] 88 | c0=1 89 | c1=-1 90 | c = [c0, c1] 91 | 92 | M = [A, B, C] 93 | 94 | print("CCS values:") 95 | print("n: %s, m: %s, t: %s, q: %s, d: %s" % (n, m, t, q, d)) 96 | print("M:", M) 97 | print("z:", z) 98 | print("S:", S) 99 | print("c:", c) 100 | 101 | # check CCS relation (this is agnostic to R1CS, for any CCS instance) 102 | r = [F(0)] * m 103 | for i in range(0, q): 104 | hadamard_output = [F(1)]*m 105 | for j in S[i]: 106 | hadamard_output = hadamard_product(hadamard_output, 107 | matrix_vector_product(M[j], z)) 108 | 109 | r = vec_add(r, vec_elem_mul(hadamard_output, c[i])) 110 | 111 | print("\nCCS relation check (∑ cᵢ ⋅ ◯ Mⱼ z == 0):", r == [0]*m) 112 | assert r == [0]*m 113 | -------------------------------------------------------------------------------- /fft-notes.bib: -------------------------------------------------------------------------------- 1 | @misc{gstrang, 2 | title = {Linear Algebra and Its Applications, by Gilbert Strang (chapter 3.5)}, 3 | howpublished = "\url{https://archive.org/details/linearalgebrait00stra}", 4 | } 5 | @misc{tpornin, 6 | title = {{Thomas Pornin} mathoverflow answer}, 7 | howpublished = "\url{https://crypto.stackexchange.com/a/63616}", 8 | } 9 | @misc{rfateman, 10 | title = {notes by {Prof. R. Fateman}}, 11 | howpublished = "\url{https://www.csee.umbc.edu/~phatak/691a/fft-lnotes/fftnotes.pdf}", 12 | } 13 | @misc{fftrs, 14 | title = {fft-rs}, 15 | howpublished = "\url{https://github.com/arnaucube/fft-rs}", 16 | } 17 | @misc{fftsage, 18 | title = {fft-sage}, 19 | howpublished = "\url{https://github.com/arnaucube/math/blob/master/fft.sage}", 20 | } 21 | -------------------------------------------------------------------------------- /fft-notes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaucube/math/6593857e91fe437f72d5420ef23fec3ad29b0b40/fft-notes.pdf -------------------------------------------------------------------------------- /fft-notes.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage{amsfonts} 4 | \usepackage{amsthm} 5 | \usepackage{amsmath} 6 | \usepackage{amssymb} 7 | \usepackage{enumerate} 8 | \usepackage{hyperref} 9 | \hypersetup{ 10 | colorlinks, 11 | citecolor=black, 12 | filecolor=black, 13 | linkcolor=black, 14 | urlcolor=blue 15 | } 16 | 17 | \theoremstyle{definition} 18 | \newtheorem{definition}{Def}[section] 19 | \newtheorem{theorem}[definition]{Thm} 20 | \newtheorem{innersolution}{} 21 | \newenvironment{solution}[1] 22 | {\renewcommand\theinnersolution{#1}\innersolution} 23 | {\endinnersolution} 24 | 25 | 26 | \title{FFT: Fast Fourier Transform} 27 | \author{arnaucube} 28 | \date{August 2022} 29 | 30 | \begin{document} 31 | 32 | \maketitle 33 | 34 | \begin{abstract} 35 | Usually while reading papers and books I take handwritten notes, this document contains some of them re-written to $LaTeX$. 36 | 37 | The notes are not complete, don't include all the steps neither all the proofs. I use these notes to revisit the concepts after some time of reading the topic. 38 | 39 | This document are notes done while reading about the topic from \cite{gstrang}, \cite{tpornin}, \cite{rfateman}. 40 | \end{abstract} 41 | 42 | \tableofcontents 43 | 44 | \section{Discrete \& Fast Fourier Transform} 45 | 46 | \subsection{Discrete Fourier Transform (DFT)} 47 | 48 | Continuous: 49 | 50 | $$ 51 | x(f) = \int_{-\infty}^{\infty} x(t) e^{-2 \pi f t} dt 52 | $$ 53 | 54 | Discrete: 55 | The $k^{th}$ frequency, evaluating at $n$ of $N$ samples. 56 | $$ 57 | \hat{f_k} = \sum_{n=0}^{n-1} f_n e^{\frac{-j \pi kn}{N}} 58 | $$ 59 | 60 | where we can group under $b_n = \frac{\pi kn}{N}$. The previous expression can be expanded into: 61 | $$ 62 | x_k = x_0 e^{-b_0 j} + x_1 e^{-b_1 j} + ... + x_n e^{-b_n j} 63 | $$ 64 | 65 | By the \emph{Euler's formula} we have $e^{jx} = cos(x) + j\cdot sin(x)$, and using it in the previous $x_k$, we obtain 66 | 67 | $$ 68 | x_k = x_0 [cos(-b_0) + j \cdot sin(-b_0)] + \ldots 69 | $$ 70 | 71 | Using $\hat{f_k}$ we obtained 72 | $$ 73 | \{f_0, f_1, \ldots, f_N\} \xrightarrow{DFT} \{ \hat{f_0}, \hat{f_1}, \ldots, \hat{f_N} \} 74 | $$ 75 | 76 | To reverse the $\hat{f_k}$ back to $f_k$: 77 | $$ 78 | f_k = \left( \sum_{n=0}^{n-1} \hat{f_n} e^{\frac{-j \pi kn}{N}} \right) \cdot \frac{1}{N} 79 | $$ 80 | 81 | 82 | $$ 83 | DFT = 84 | \begin{pmatrix} 85 | \hat{f_0}\\ 86 | \hat{f_1}\\ 87 | \hat{f_2}\\ 88 | \vdots\\ 89 | \hat{f_n}\\ 90 | \end{pmatrix}= 91 | \begin{pmatrix} 92 | 1 & 1 & 1 & \ldots & 1 \\ 93 | 1 & w_n & w_n^2 & \ldots & w_n^{N-1} \\ 94 | 1 & w_n^2 & w_n^4 & \ldots & w_n^{2(N-1)} \\ 95 | \vdots & \vdots & \vdots & & \vdots\\ 96 | 1 & w_n^{n-1} & w_n^{2(n-1)} & \ldots & w_n^{(N-1)^2} \\ 97 | \end{pmatrix} 98 | \begin{pmatrix} 99 | f_0 \\ f_1 \\ f_2 \\ \vdots \\ f_n 100 | \end{pmatrix} 101 | $$ 102 | 103 | \subsection{Fast Fourier Transform (FFT)} 104 | While DFT is $O(n)$, FFT is $O(n \space log(n))$ 105 | 106 | Here you can find a simple implementation of the these concepts in Rust: \href{https://github.com/arnaucube/fft-rs}{arnaucube/fft-rs} \cite{fftrs} 107 | 108 | 109 | \section{FFT over finite fields, roots of unity, and polynomial multiplication} 110 | 111 | FFT is very useful when working with polynomials. [TODO poly multiplication] 112 | 113 | An implementation of the FFT over finite fields using the Vandermonde matrix approach can be found at \cite{fftsage}. 114 | 115 | \subsection{Intro} 116 | Let $A(x)$ be a polynomial of degree $n-1$, 117 | 118 | $$ 119 | A(x) = a_0 + a_1 \cdot x + a_2 \cdot x^2 + \cdots + a_{n-1} \cdot x^{n-1} = \sum_{i=0}^{n-1} a_i \cdot x^i 120 | $$ 121 | 122 | We can represent $A(x)$ in its evaluation form, 123 | 124 | $$ 125 | (x_0, A(x_0)), (x_1, A(x_1)), \cdots, (x_{n-1}, A(x_{n-1})) = (x_i, A(x_i)) 126 | $$ 127 | 128 | 129 | We can evaluate A(x) at n given points $(x_0, x_1, ..., x_{n-1}$): 130 | 131 | $$ 132 | \begin{pmatrix} 133 | A(x_0)\\ A(x_1)\\ A(x_2)\\ \vdots\\ A(x_{n-1}) 134 | \end{pmatrix}= 135 | \begin{pmatrix} 136 | x_0^0 & x_0^1 & x_0^2 & \ldots & x_0^{n-1} \\ 137 | x_1^0 & x_1^1 & x_1^2 & \ldots & x_1^{n-1} \\ 138 | x_2^0 & x_2^1 & x_2^2 & \ldots & x_2^{n-1} \\ 139 | \vdots & \vdots & \vdots & & \vdots\\ 140 | x_{n-1}^0 & x_{n-1}^1 & x_{n-1}^2 & \ldots & x_{n-1}^{n-1} \\ 141 | \end{pmatrix} 142 | \begin{pmatrix} 143 | a_0 \\ a_1 \\ a_2 \\ \vdots \\ a_{n-1} 144 | \end{pmatrix} 145 | $$ 146 | 147 | This is known by the Vandermonde matrix. 148 | 149 | But this will not be too efficient. Instead of random $x_i$ values, we use \emph{roots of unity}, where $\omega_n^n = 1$. We denote $\omega$ as a primitive $n^{th}$ root of unity: 150 | 151 | $$ 152 | \begin{pmatrix} 153 | A(1)\\ A(\omega)\\ A(\omega^2)\\ \vdots\\ A(\omega^{n-1}) 154 | \end{pmatrix}= 155 | \begin{pmatrix} 156 | 1 & 1 & 1 & \ldots & 1 \\ 157 | 1 & \omega & \omega^2 & \ldots & \omega^{n-1} \\ 158 | 1 & \omega^2 & \omega^4 & \ldots & \omega^{2(n-1)} \\ 159 | \vdots & \vdots & \vdots & & \vdots\\ 160 | 1 & \omega^{n-1} & \omega^{2(n-1)} & \ldots & \omega^{(n-1)^2} \\ 161 | \end{pmatrix} 162 | \begin{pmatrix} 163 | a_0 \\ a_1 \\ a_2 \\ \vdots \\ a_{n-1} 164 | \end{pmatrix} 165 | $$ 166 | 167 | Which we can see as 168 | 169 | $$ 170 | \hat{A} = F_n \cdot A 171 | $$ 172 | 173 | This matches our system of equations: 174 | 175 | \begin{itemize} 176 | \item at $x=0$, $a_0 + a_1 + \cdots + a_{n-1} = A_0 = A(1)$ 177 | \item at $x=1$, $a_0 \cdot 1 + a_1 \cdot \omega + a_2 \cdot \omega^2 + \cdots + a_{n-1} \cdot \omega^{n-1} = A_1 = A(\omega)$ 178 | \item at $x=2$, $a_0 \cdot 1 + a_1 \cdot \omega^2 + a_2 \cdot \omega^4 + \cdots + a_{n-1} \cdot \omega^{2(n-1)} = A_2 = A(\omega^2)$ 179 | \item $\cdots$ 180 | \item at $x=n-1$, $a_0 \cdot 1 + a_1 \cdot \omega^{n-1} + a_2 \cdot \omega^{2(n-1)} + \cdots + a_{n-1} \cdot \omega^{(n-1)(n-1)} = A_2 = A(\omega^{n-1})$ 181 | \end{itemize} 182 | 183 | We denote the $F_n$ as the Fourier matrix, with $j$ rows and $k$ columns, where each entry can be expressed as $F_{jk} = \omega^{jk}$. 184 | 185 | To find the $a_i$ values, we use the inverted $F_n = F_n^{-1}$ 186 | 187 | \subsection{Roots of unity} 188 | todo 189 | 190 | \subsection{FFT over finite fields} 191 | todo 192 | 193 | \subsection{Polynomial multiplication with FFT} 194 | todo 195 | 196 | 197 | \bibliography{fft-notes.bib} 198 | \bibliographystyle{unsrt} 199 | 200 | \end{document} 201 | -------------------------------------------------------------------------------- /fft.sage: -------------------------------------------------------------------------------- 1 | # Primitive Root of Unity 2 | def get_primitive_root_of_unity(F, n): 3 | # using the method described by Thomas Pornin in 4 | # https://crypto.stackexchange.com/a/63616 5 | q = F.order() 6 | for k in range(q): 7 | if k==0: 8 | continue 9 | g = F(k) 10 | # g = F.random_element() 11 | if g==0: 12 | continue 13 | w = g ^ ((q-1)/n) 14 | if w^(n/2) != 1: 15 | return g, w 16 | 17 | # Roots of Unity 18 | def get_nth_roots_of_unity(n, primitive_w): 19 | w = [0]*n 20 | for i in range(n): 21 | w[i] = primitive_w^i 22 | return w 23 | 24 | 25 | # fft (Fast Fourier Transform) returns: 26 | # - nth roots of unity 27 | # - Vandermonde matrix for the nth roots of unity 28 | # - Inverse Vandermonde matrix 29 | def fft(F, n): 30 | g, primitive_w = get_primitive_root_of_unity(F, n) 31 | 32 | w = get_nth_roots_of_unity(n, primitive_w) 33 | 34 | ft = matrix(F, n) 35 | for j in range(n): 36 | row = [] 37 | for k in range(n): 38 | row.append(primitive_w^(j*k)) 39 | ft.set_row(j, row) 40 | ft_inv = ft^-1 41 | return w, ft, ft_inv 42 | 43 | 44 | # Fast polynomial multiplication using FFT 45 | def poly_mul(fa, fb, F, n): 46 | w, ft, ft_inv = fft(F, n) 47 | 48 | # compute evaluation points from polynomials fa & fb at the roots of unity 49 | a_evals = [] 50 | b_evals = [] 51 | for i in range(n): 52 | a_evals.append(fa(w[i])) 53 | b_evals.append(fb(w[i])) 54 | 55 | # multiply elements in a_evals by b_evals 56 | c_evals = map(operator.mul, a_evals, b_evals) 57 | c_evals = vector(c_evals) 58 | 59 | # using FFT, convert the c_evals into fc(x) 60 | fc_coef = c_evals*ft_inv 61 | fc2=P(fc_coef.list()) 62 | return fc2, c_evals 63 | 64 | 65 | # Tests 66 | 67 | ##### 68 | # Roots of Unity test: 69 | q = 17 70 | F = GF(q) 71 | n = 4 72 | g, primitive_w = get_primitive_root_of_unity(F, n) 73 | print("generator:", g) 74 | print("primitive_w:", primitive_w) 75 | 76 | w = get_nth_roots_of_unity(n, primitive_w) 77 | print(f"{n}th roots of unity: {w}") 78 | assert w == [1, 13, 16, 4] 79 | 80 | 81 | ##### 82 | # FFT test: 83 | 84 | def isprime(num): 85 | for n in range(2,int(num^1/2)+1): 86 | if num%n==0: 87 | return False 88 | return True 89 | 90 | # list valid values for q 91 | for i in range(20): 92 | if isprime(8*i+1): 93 | print("q =", 8*i+1) 94 | 95 | q = 41 96 | F = GF(q) 97 | n = 4 98 | # q needs to be a prime, s.t. q-1 is divisible by n 99 | assert (q-1)%n==0 100 | print("q =", q, "n = ", n) 101 | 102 | # ft: Vandermonde matrix for the nth roots of unity 103 | w, ft, ft_inv = fft(F, n) 104 | print("nth roots of unity:", w) 105 | print("Vandermonde matrix:") 106 | print(ft) 107 | 108 | fa_eval = vector([3,4,5,9]) 109 | print("fa_eval:", fa_eval) 110 | 111 | # interpolate f_a(x) 112 | fa_coef = ft_inv * fa_eval 113 | print("fa_coef:", fa_coef) 114 | 115 | P. = PolynomialRing(F) 116 | fa = P(list(fa_coef)) 117 | print("f_a(x):", fa) 118 | 119 | # check that evaluating fa(x) at the roots of unity returns the expected values of fa_eval 120 | for i in range(len(fa_eval)): 121 | assert fa(w[i]) == fa_eval[i] 122 | 123 | # go from coefficient form to evaluation form 124 | fa_eval2 = ft * fa_coef 125 | print("fa_eval'", fa_eval) 126 | assert fa_eval2 == fa_eval 127 | 128 | 129 | # Fast polynomial multiplication using FFT 130 | print("\n---------") 131 | print("---Fast polynomial multiplication using FFT") 132 | 133 | n = 8 134 | # q needs to be a prime, s.t. q-1 is divisible by n 135 | assert (q-1)%n==0 136 | print("q =", q, "n = ", n) 137 | 138 | fa=P([1,2,3,4]) 139 | fb=P([1,2,3,4]) 140 | fc_expected = fa*fb 141 | print("fc expected result:", fc_expected) # expected result 142 | print("fc expected coef", fc_expected.coefficients()) 143 | 144 | fc, c_evals = poly_mul(fa, fb, F, n) 145 | print("c_evals=(a_evals*b_evals)=", c_evals) 146 | print("fc:", fc) 147 | assert fc_expected == fc 148 | -------------------------------------------------------------------------------- /galois-theory-notes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaucube/math/6593857e91fe437f72d5420ef23fec3ad29b0b40/galois-theory-notes.pdf -------------------------------------------------------------------------------- /ipa.sage: -------------------------------------------------------------------------------- 1 | # This file contains two Inner Product Argument implementations: 2 | # - Bulletproofs version: https://eprint.iacr.org/2017/1066.pdf 3 | # - Halo version: https://eprint.iacr.org/2019/1021.pdf 4 | 5 | 6 | 7 | # IPA_bulletproofs implements the IPA version from the Bulletproofs paper: https://eprint.iacr.org/2017/1066.pdf 8 | # https://doc-internal.dalek.rs/bulletproofs/notes/inner_product_proof/index.html 9 | class IPA_bulletproofs: 10 | def __init__(self, F, E, g, d): 11 | self.g = g 12 | self.F = F 13 | self.E = E 14 | self.d = d 15 | # TODO: 16 | # Setup: 17 | self.h = E.random_element() # TMP 18 | self.gs = random_values(E, d) 19 | self.hs = random_values(E, d) 20 | 21 | # a: aᵢ ∈ 𝔽 coefficients of p(X) 22 | # r: blinding factor 23 | def commit(self, a, b): 24 | P = inner_product_point(a, self.gs) + inner_product_point(b, self.hs) 25 | return P 26 | 27 | def evaluate(self, a, x_powers): 28 | return inner_product_field(a, x_powers) 29 | 30 | def ipa(self, a_, b_, u, U): 31 | G = self.gs 32 | H = self.hs 33 | a = a_ 34 | b = b_ 35 | 36 | k = int(math.log(self.d, 2)) 37 | L = [None] * k 38 | R = [None] * k 39 | 40 | for j in reversed(range(0, k)): 41 | m = len(a)/2 42 | a_lo = a[:m] 43 | a_hi = a[m:] 44 | b_lo = b[:m] 45 | b_hi = b[m:] 46 | H_lo = H[:m] 47 | H_hi = H[m:] 48 | G_lo = G[:m] 49 | G_hi = G[m:] 50 | 51 | # Lⱼ = + [lⱼ] H + [] U 52 | L[j] = inner_product_point(a_lo, G_hi) + inner_product_point(b_hi, H_lo) + int(inner_product_field(a_lo, b_hi)) * U 53 | # Rⱼ = + [rⱼ] H + [] U 54 | R[j] = inner_product_point(a_hi, G_lo) + inner_product_point(b_lo, H_hi) + int(inner_product_field(a_hi, b_lo)) * U 55 | 56 | # use the random challenge uⱼ ∈ 𝕀 generated by the verifier 57 | u_ = u[j] # uⱼ 58 | u_inv = u[j]^(-1) # uⱼ⁻¹ 59 | 60 | a = vec_add(vec_scalar_mul_field(a_lo, u_), vec_scalar_mul_field(a_hi, u_inv)) 61 | b = vec_add(vec_scalar_mul_field(b_lo, u_inv), vec_scalar_mul_field(b_hi, u_)) 62 | G = vec_add(vec_scalar_mul_point(G_lo, u_inv), vec_scalar_mul_point(G_hi, u_)) 63 | H = vec_add(vec_scalar_mul_point(H_lo, u_), vec_scalar_mul_point(H_hi, u_inv)) 64 | 65 | assert len(a)==1 66 | assert len(b)==1 67 | assert len(G)==1 68 | assert len(H)==1 69 | # a, b, G have length=1 70 | # L, R are the "cross-terms" of the inner product 71 | return a[0], b[0], G[0], H[0], L, R 72 | 73 | def verify(self, P, a, v, x_powers, u, U, L, R, b_ipa, G_ipa, H_ipa): 74 | b = b_ipa 75 | G = G_ipa 76 | H = H_ipa 77 | 78 | # Q_0 = P' ⋅ ∑ ( [uⱼ²] Lⱼ + [uⱼ⁻²] Rⱼ) 79 | C = P 80 | for j in range(len(L)): 81 | u_ = u[j] # uⱼ 82 | u_inv = u[j]^(-1) # uⱼ⁻² 83 | 84 | # ∑ ( [uⱼ²] Lⱼ + [uⱼ⁻²] Rⱼ) 85 | C = C + int(u_^2) * L[j] + int(u_inv^2) * R[j] 86 | 87 | D = int(a) * G + int(b) * H + int(a * b)*U 88 | 89 | return C == D 90 | 91 | # IPA_halo implements the modified IPA from the Halo paper: https://eprint.iacr.org/2019/1021.pdf 92 | class IPA_halo: 93 | def __init__(self, F, E, g, d): 94 | self.g = g 95 | self.F = F 96 | self.E = E 97 | self.d = d 98 | 99 | self.h = E.random_element() # TMP 100 | self.gs = random_values(E, d) 101 | self.hs = random_values(E, d) 102 | # print(" h=", self.h) 103 | # print(" G=", self.gs) 104 | # print(" H=", self.hs) 105 | 106 | def commit(self, a, r): 107 | P = inner_product_point(a, self.gs) + r * self.h 108 | return P 109 | 110 | def evaluate(self, a, x_powers): 111 | return inner_product_field(a, x_powers) 112 | 113 | def ipa(self, a_, x_powers, u, U): # prove 114 | print(" method ipa():") 115 | G = self.gs 116 | a = a_ 117 | b = x_powers 118 | 119 | k = int(math.log(self.d, 2)) 120 | l = [None] * k 121 | r = [None] * k 122 | L = [None] * k 123 | R = [None] * k 124 | 125 | for j in reversed(range(0, k)): 126 | print(" j =", j) 127 | print(" len(a) = n =", len(a)) 128 | print(" m = n/2 =", len(a)/2) 129 | m = len(a)/2 130 | a_lo = a[:m] 131 | a_hi = a[m:] 132 | b_lo = b[:m] 133 | b_hi = b[m:] 134 | G_lo = G[:m] 135 | G_hi = G[m:] 136 | 137 | print(" Split into a_lo,hi b_lo,hi, G_lo,hi:") 138 | print(" a", a) 139 | print(" a_lo", a_lo) 140 | print(" a_hi", a_hi) 141 | print(" b", b) 142 | print(" b_lo", b_lo) 143 | print(" b_hi", b_hi) 144 | print(" G", G) 145 | print(" G_lo", G_lo) 146 | print(" G_hi", G_hi) 147 | 148 | l[j] = self.F.random_element() # random blinding factor 149 | r[j] = self.F.random_element() # random blinding factor 150 | print(" random blinding factors:") 151 | print(" l[j]", l[j]) 152 | print(" r[j]", r[j]) 153 | 154 | # Lⱼ = + [lⱼ] H + [] U 155 | L[j] = inner_product_point(a_lo, G_hi) + int(l[j]) * self.h + int(inner_product_field(a_lo, b_hi)) * U 156 | # Rⱼ = + [rⱼ] H + [] U 157 | R[j] = inner_product_point(a_hi, G_lo) + int(r[j]) * self.h + int(inner_product_field(a_hi, b_lo)) * U 158 | 159 | print(" Compute Lⱼ = + [lⱼ] H + [] U") 160 | print(" L[j]", L[j]) 161 | print(" Compute Rⱼ = + [rⱼ] H + [] U") 162 | print(" R[j]", R[j]) 163 | 164 | # use the random challenge uⱼ ∈ 𝕀 generated by the verifier 165 | u_ = u[j] # uⱼ 166 | u_inv = self.F(u[j])^(-1) # uⱼ⁻¹ 167 | print(" u_j", u_) 168 | print(" u_j^-1", u_inv) 169 | 170 | a = vec_add(vec_scalar_mul_field(a_lo, u_), vec_scalar_mul_field(a_hi, u_inv)) 171 | b = vec_add(vec_scalar_mul_field(b_lo, u_inv), vec_scalar_mul_field(b_hi, u_)) 172 | G = vec_add(vec_scalar_mul_point(G_lo, u_inv), vec_scalar_mul_point(G_hi, u_)) 173 | print(" new a, b, G") 174 | print(" a =", a) 175 | print(" b =", b) 176 | print(" G =", G) 177 | 178 | assert len(a)==1 179 | assert len(b)==1 180 | assert len(G)==1 181 | # a, b, G have length=1 182 | # l, r are random blinding factors 183 | # L, R are the "cross-terms" of the inner product 184 | return a[0], l, r, L, R 185 | 186 | def verify(self, P, a, v, x_powers, r, u, U, lj, rj, L, R): 187 | print("method verify()") 188 | 189 | # compute P' = P + [v] U 190 | P = P + int(v) * U 191 | 192 | s = build_s_from_us(u, self.d) 193 | b = inner_product_field(s, x_powers) 194 | G = inner_product_point(s, self.gs) 195 | 196 | # synthetic blinding factor 197 | # r' = r + ∑ ( lⱼ uⱼ² + rⱼ uⱼ⁻²) 198 | print(" synthetic blinding factor r' = r + ∑ ( lⱼ uⱼ² + rⱼ uⱼ⁻²)") 199 | r_ = r 200 | print(" r_ =", r_) 201 | # Q_0 = P' ⋅ ∑ ( [uⱼ²] Lⱼ + [uⱼ⁻²] Rⱼ) 202 | print(" Q_0 = P' ⋅ ∑ ( [uⱼ²] Lⱼ + [uⱼ⁻²] Rⱼ)") 203 | Q_0 = P 204 | print(" Q_0 =", Q_0) 205 | for j in range(len(u)): 206 | print(" j =", j) 207 | u_ = u[j] # uⱼ 208 | u_inv = u[j]^(-1) # uⱼ⁻² 209 | 210 | # ∑ ( [uⱼ²] Lⱼ + [uⱼ⁻²] Rⱼ) 211 | Q_0 = Q_0 + int(u[j]^2) * L[j] + int(u_inv^2) * R[j] 212 | print(" Q_0 =", Q_0) 213 | 214 | r_ = r_ + lj[j] * (u_^2) + rj[j] * (u_inv^2) 215 | print(" r_ =", r_) 216 | 217 | Q_1 = int(a) * G + int(r_) * self.h + int(a * b)*U 218 | print(" Q_1", Q_1) 219 | # Q_1_ = int(a) * (G + int(b)*U) + int(r_) * self.h 220 | 221 | return Q_0 == Q_1 222 | 223 | 224 | # s = ( 225 | # u₁⁻¹ u₂⁻¹ … uₖ⁻¹, 226 | # u₁ u₂⁻¹ … uₖ⁻¹, 227 | # u₁⁻¹ u₂ … uₖ⁻¹, 228 | # u₁ u₂ … uₖ⁻¹, 229 | # ⋮ ⋮ ⋮ 230 | # u₁ u₂ … uₖ 231 | # ) 232 | def build_s_from_us(u, d): 233 | k = int(math.log(d, 2)) 234 | s = [1]*d 235 | t = d 236 | for j in reversed(range(k)): 237 | t = t/2 238 | c = 0 239 | for i in range(d): 240 | if c=t*2: 246 | c=0 247 | 248 | return s 249 | 250 | 251 | 252 | 253 | def powers_of(g, d): 254 | r = [None] * d 255 | for i in range(d): 256 | r[i] = g^i 257 | return r 258 | 259 | 260 | def multiples_of(g, d): 261 | r = [None] * d 262 | for i in range(d): 263 | r[i] = g*i 264 | return r 265 | 266 | def random_values(G, d): 267 | r = [None] * d 268 | for i in range(d): 269 | r[i] = G.random_element() 270 | return r 271 | 272 | def inner_product_field(a, b): 273 | assert len(a) == len(b) 274 | c = 0 275 | for i in range(len(a)): 276 | c = c + a[i] * b[i] 277 | 278 | return c 279 | 280 | def inner_product_point(a, b): 281 | assert len(a) == len(b) 282 | c = 0 283 | for i in range(len(a)): 284 | c = c + int(a[i]) * b[i] 285 | 286 | return c 287 | 288 | def vec_add(a, b): 289 | assert len(a) == len(b) 290 | return [x + y for x, y in zip(a, b)] 291 | 292 | def vec_mul(a, b): 293 | assert len(a) == len(b) 294 | return [x * y for x, y in zip(a, b)] 295 | 296 | def vec_scalar_mul_field(a, n): 297 | r = [None]*len(a) 298 | for i in range(len(a)): 299 | r[i] = a[i]*n 300 | return r 301 | 302 | def vec_scalar_mul_point(a, n): 303 | r = [None]*len(a) 304 | for i in range(len(a)): 305 | r[i] = a[i]*int(n) 306 | return r 307 | 308 | 309 | # Tests 310 | import unittest, operator 311 | 312 | # Ethereum elliptic curve 313 | p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F 314 | a = 0 315 | b = 7 316 | Fp = GF(p) 317 | E = EllipticCurve(Fp, [a,b]) 318 | GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 319 | GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 320 | g = E(GX,GY) 321 | n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 322 | h = 1 323 | q = g.order() 324 | Fq = GF(q) 325 | 326 | # simpler curve values 327 | # p = 19 328 | # Fp = GF(p) 329 | # E = EllipticCurve(Fp,[0,3]) 330 | # g = E(1, 2) 331 | # q = g.order() 332 | # Fq = GF(q) 333 | 334 | print(E) 335 | print(Fq) 336 | assert is_prime(p) 337 | assert is_prime(q) 338 | assert g * q == 0 339 | 340 | class TestUtils(unittest.TestCase): 341 | def test_vecs(self): 342 | a = [1, 2, 3, 4, 5] 343 | b = [1, 2, 3, 4, 5] 344 | 345 | c = vec_scalar_mul_field(a, 10) 346 | assert c == [10, 20, 30, 40, 50] 347 | 348 | c = inner_product_field(a, b) 349 | assert c == 55 350 | 351 | # check that with b = (1, x, x^2, ..., x^{d-1}) is the same 352 | # than evaluating p(x) with coefficients a_i, at x 353 | a = [Fq(1), Fq(2), Fq(3), Fq(4), Fq(5), Fq(6), Fq(7), Fq(8)] 354 | z = Fq(3) 355 | b = powers_of(z, 8) 356 | c = inner_product_field(a, b) 357 | 358 | x = PolynomialRing(Fq, 'x').gen() 359 | px = 1 + 2*x + 3*x^2 + 4*x^3 + 5*x^4 + 6*x^5 + 7*x^6 + 8*x^7 360 | assert c == px(x=z) 361 | 362 | 363 | class TestIPA_bulletproofs(unittest.TestCase): 364 | def test_inner_product_argument(self): 365 | d = 8 366 | ipa = IPA_bulletproofs(Fq, E, g, d) 367 | 368 | # prover 369 | # p(x) = 1 + 2x + 3x² + 4x³ + 5x⁴ + 6x⁵ + 7x⁶ + 8x⁷ 370 | a = [ipa.F(1), ipa.F(2), ipa.F(3), ipa.F(4), ipa.F(5), ipa.F(6), ipa.F(7), ipa.F(8)] 371 | x = ipa.F(3) 372 | b = powers_of(x, ipa.d) # = b 373 | 374 | # prover 375 | P = ipa.commit(a, b) 376 | print("commit", P) 377 | v = ipa.evaluate(a, b) 378 | print("v", v) 379 | 380 | # verifier generate random challenges {uᵢ} ∈ 𝕀 and U ∈ 𝔾 381 | U = ipa.E.random_element() 382 | k = int(math.log(d, 2)) 383 | u = [None] * k 384 | for j in reversed(range(0, k)): 385 | u[j] = ipa.F.random_element() 386 | while (u[j] == 0): # prevent u[j] from being 0 387 | u[j] = ipa.F.random_element() 388 | 389 | P = P + int(inner_product_field(a, b)) * U 390 | 391 | # prover 392 | a_ipa, b_ipa, G_ipa, H_ipa, L, R = ipa.ipa(a, b, u, U) 393 | 394 | # verifier 395 | print("P", P) 396 | print("a_ipa", a_ipa) 397 | verif = ipa.verify(P, a_ipa, v, b, u, U, L, R, b_ipa, G_ipa, H_ipa) 398 | print("Verification:", verif) 399 | assert verif == True 400 | 401 | 402 | class TestIPA_halo(unittest.TestCase): 403 | def test_homomorphic_property(self): 404 | ipa = IPA_halo(Fq, E, g, 5) 405 | 406 | a = [1, 2, 3, 4, 5] 407 | b = [1, 2, 3, 4, 5] 408 | c = vec_add(a, b) 409 | assert c == [2,4,6,8,10] 410 | 411 | r = int(ipa.F.random_element()) 412 | s = int(ipa.F.random_element()) 413 | vc_a = ipa.commit(a, r) 414 | vc_b = ipa.commit(b, s) 415 | 416 | # com(a, r) + com(b, s) == com(a+b, r+s) 417 | expected_vc_c = ipa.commit(vec_add(a, b), r+s) 418 | vc_c = vc_a + vc_b 419 | assert vc_c == expected_vc_c 420 | 421 | def test_inner_product_argument(self): 422 | d = 8 423 | ipa = IPA_halo(Fq, E, g, d) 424 | 425 | # prover 426 | # p(x) = 1 + 2x + 3x² + 4x³ + 5x⁴ + 6x⁵ + 7x⁶ + 8x⁷ 427 | a = [ipa.F(1), ipa.F(2), ipa.F(3), ipa.F(4), ipa.F(5), ipa.F(6), ipa.F(7), ipa.F(8)] 428 | x = ipa.F(3) 429 | x_powers = powers_of(x, ipa.d) # = b 430 | 431 | # blinding factor 432 | r = int(ipa.F.random_element()) 433 | 434 | # prover 435 | P = ipa.commit(a, r) 436 | print("commit", P) 437 | v = ipa.evaluate(a, x_powers) 438 | print("v", v) 439 | 440 | # verifier generate random challenges {uᵢ} ∈ 𝕀 and U ∈ 𝔾 441 | # This might be obtained from the hash of the transcript 442 | # (Fiat-Shamir heuristic for non-interactive version) 443 | U = ipa.E.random_element() 444 | k = int(math.log(ipa.d, 2)) 445 | u = [None] * k 446 | for j in reversed(range(0, k)): 447 | u[j] = ipa.F.random_element() 448 | while (u[j] == 0): # prevent u[j] from being 0 449 | u[j] = ipa.F.random_element() 450 | 451 | # prover 452 | a_ipa, lj, rj, L, R = ipa.ipa(a, x_powers, u, U) 453 | 454 | # verifier 455 | print("P", P) 456 | print("a_ipa", a_ipa) 457 | print("\n Verify:") 458 | verif = ipa.verify(P, a_ipa, v, x_powers, r, u, U, lj, rj, L, R) 459 | assert verif == True 460 | 461 | 462 | if __name__ == '__main__': 463 | unittest.main() 464 | -------------------------------------------------------------------------------- /kzg.sage: -------------------------------------------------------------------------------- 1 | # toy implementation of BLS signatures in Sage 2 | # 3 | # Scheme overview: https://arnaucube.com/blog/kzg-commitments.html 4 | # Go implementation: https://github.com/arnaucube/kzg-commitments-study 5 | 6 | load("bls12-381.sage") 7 | 8 | e = Pairing() 9 | 10 | def new_ts(l): 11 | Fr = GF(e.r) 12 | s = Fr.random_element() 13 | print("s", s) 14 | tauG1 = [None] * l 15 | tauG2 = [None] * l 16 | for i in range(0, l): # TODO probably duplicate G1 & G2 instead of first powering s^i and then * G_j 17 | sPow = Integer(s)^i 18 | tauG1[i] = sPow * e.G1 19 | tauG2[i] = sPow * e.G2 20 | 21 | return (tauG1, tauG2) 22 | 23 | def commit(taus, p): 24 | return evaluate_at_tau(p, taus) 25 | 26 | # evaluates p at tau 27 | def evaluate_at_tau(p, taus): 28 | e = 0 29 | for i in range(0, len(p.list())): 30 | e = e + p[i] * taus[i] 31 | return e 32 | 33 | def evaluation_proof(tau, p, z, y): 34 | # (p - y) 35 | n = p - y 36 | # (t - z) 37 | d = (t-z) 38 | # q, rem = n / d 39 | q = n / d 40 | print("q", q) 41 | q = q.numerator() 42 | den = q.denominator() 43 | print("q", q) 44 | print("den", den) 45 | # check that den = 1 46 | assert(den==1) # rem=0 47 | # proof: e = [q(t)]₁ 48 | return evaluate_at_tau(q, tau) 49 | 50 | def verify(tau, c, proof, z, y): 51 | # [t]₂ - [z]₂ 52 | sz = tau[1] - z*e.G2 53 | 54 | # c - [y]₁ 55 | cy = c - y*e.G1 56 | 57 | print("proof", proof) 58 | print("sz", sz) 59 | print("cy", cy) 60 | lhs = e.pair(proof, sz) 61 | rhs = e.pair(cy, e.G2) 62 | print("lhs", lhs) 63 | print("rhs", rhs) 64 | return lhs == rhs 65 | 66 | 67 | (tauG1, tauG2) = new_ts(5) 68 | 69 | R. = PolynomialRing(e.F1) 70 | p = t^3 + t + 5 71 | 72 | c = commit(tauG1, p) 73 | 74 | z = 3 75 | y = p(z) # = 35 76 | 77 | proof = evaluation_proof(tauG1, p, z, y) 78 | print("proof", proof) 79 | 80 | v = verify(tauG2, c, proof, z, y) 81 | print(v) 82 | assert(v) 83 | -------------------------------------------------------------------------------- /notes_bls-sig.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaucube/math/6593857e91fe437f72d5420ef23fec3ad29b0b40/notes_bls-sig.pdf -------------------------------------------------------------------------------- /notes_bls-sig.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage{amsfonts} 4 | \usepackage{amsthm} 5 | \usepackage{amsmath} 6 | \usepackage{enumerate} 7 | \usepackage{hyperref} 8 | \hypersetup{ 9 | colorlinks, 10 | citecolor=black, 11 | filecolor=black, 12 | linkcolor=black, 13 | urlcolor=blue 14 | } 15 | \usepackage{xcolor} 16 | 17 | % prevent warnings of underfull \hbox: 18 | \usepackage{etoolbox} 19 | \apptocmd{\sloppy}{\hbadness 4000\relax}{}{} 20 | 21 | \theoremstyle{definition} 22 | \newtheorem{definition}{Def}[section] 23 | \newtheorem{theorem}[definition]{Thm} 24 | 25 | 26 | \title{Notes on BLS Signatures} 27 | \author{arnaucube} 28 | \date{July 2022} 29 | 30 | \begin{document} 31 | 32 | \maketitle 33 | 34 | \begin{abstract} 35 | Notes taken while reading about BLS signatures \cite{bls-sig-eth2}. Usually while reading papers I take handwritten notes, this document contains some of them re-written to $LaTeX$. 36 | 37 | The notes are not complete, don't include all the steps neither all the proofs. 38 | \end{abstract} 39 | 40 | % \tableofcontents 41 | 42 | \section{BLS signatures} 43 | 44 | \paragraph{Key generation} 45 | $sk \in \mathbb{Z}_q$, $pk = [sk] \cdot g_1$, where $g_1 \in G_1$, and is the generator. 46 | 47 | \paragraph{Signature} 48 | $$\sigma = [sk] \cdot H(m)$$ 49 | where $H$ is a function that maps to a point in $G_2$. So $H(m), \sigma \in G_2$. 50 | 51 | \paragraph{Verification} 52 | $$e(g_1, \sigma) == e(pk, H(m))$$ 53 | 54 | Unfold: 55 | $$e(pk, H(m)) = e([sk] \cdot g_1, H(m) = e(g_1, H(m))^{sk} = e(g_1, [sk] \cdot H(m)) = e(g_1, \sigma))$$ 56 | 57 | \paragraph{Aggregation} 58 | Signatures aggregation: 59 | $$\sigma_{aggr} = \sigma_1 + \sigma_2 + \ldots + \sigma_n$$ 60 | where $\sigma_{aggr} \in G_2$, and an aggregated signatures is indistinguishable from a non-aggregated signature. 61 | 62 | \vspace{0.5cm} 63 | Public keys aggregation: 64 | $$pk_{aggr} = pk_1 + pk_2 + \ldots + pk_n$$ 65 | where $pk_{aggr} \in G_1$, and an aggregated public keys is indistinguishable from a non-aggregated public key. 66 | 67 | 68 | \paragraph{Verification of aggregated signatures} 69 | Identical to verification of a normal signature as long as we use the same corresponding aggregated public key: 70 | $$e(g_1, \sigma_{aggr})==e(pk_{aggr}, H(m))$$ 71 | 72 | Unfold: 73 | $$e(pk_{aggr}, H(m))= e(pk_1 + pk_2 + \ldots + pk_n, H(m)) =$$ 74 | $$=e([sk_1] \cdot g_1 + [sk_2] \cdot g_1 + \ldots + [sk_n] \cdot g_1, H(m))=$$ 75 | $$=e([sk_1 + sk_2 + \ldots + sk_n] \cdot g_1, H(m))=$$ 76 | $$=[sk_1 + sk_2 + \ldots + sk_n]~\cdot~e(g_1, H(m))=$$ 77 | $$=e(g_1, [sk_1 + sk_2 + \ldots + sk_n] \cdot H(m))=$$ 78 | $$=e(g_1, [sk_1] \cdot H(m) + [sk_2] \cdot H(m) + \ldots + [sk_n] \cdot H(m))=$$ 79 | $$=e(g_1, \sigma_1 + \sigma_2 + \ldots + \sigma_n)= e(g_1, \sigma_{aggr})$$ 80 | 81 | 82 | Note: in the current notes $pk \in G_1$ and $\sigma, H(m) \in G_2$, but we could use $\sigma, H(m) \in G_1$ and $pk \in G_2$. 83 | 84 | \bibliography{paper-notes.bib} 85 | \bibliographystyle{unsrt} 86 | 87 | \end{document} 88 | -------------------------------------------------------------------------------- /notes_caulk.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaucube/math/6593857e91fe437f72d5420ef23fec3ad29b0b40/notes_caulk.pdf -------------------------------------------------------------------------------- /notes_fri_stir.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaucube/math/6593857e91fe437f72d5420ef23fec3ad29b0b40/notes_fri_stir.pdf -------------------------------------------------------------------------------- /notes_fri_stir.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage{amsfonts} 4 | \usepackage{amsthm} 5 | \usepackage{amsmath} 6 | \usepackage{mathtools} 7 | \usepackage{enumerate} 8 | \usepackage{hyperref} 9 | \usepackage{xcolor} 10 | % \usepackage{pgf-umlsd} % diagrams 11 | 12 | 13 | % prevent warnings of underfull \hbox: 14 | \usepackage{etoolbox} 15 | \apptocmd{\sloppy}{\hbadness 4000\relax}{}{} 16 | 17 | \theoremstyle{definition} 18 | \newtheorem{definition}{Def}[section] 19 | \newtheorem{theorem}[definition]{Thm} 20 | 21 | % custom lemma environment to set custom numbers 22 | \newtheorem{innerlemma}{Lemma} 23 | \newenvironment{lemma}[1] 24 | {\renewcommand\theinnerlemma{#1}\innerlemma} 25 | {\endinnerlemma} 26 | 27 | 28 | \title{Notes on FRI and STIR} 29 | \author{arnaucube} 30 | \date{February 2023} 31 | 32 | \begin{document} 33 | 34 | \maketitle 35 | 36 | \begin{abstract} 37 | Notes taken from \href{https://sites.google.com/site/vincenzoiovinoit/}{Vincenzo Iovino} \cite{vincenzoiovino} explanations about FRI \cite{fri}, \cite{cryptoeprint:2022/1216}, \cite{cryptoeprint:2019/1020}. 38 | 39 | These notes are for self-consumption, are not complete, don't include all the steps neither all the proofs. 40 | 41 | An implementation of FRI can be found at\\ \href{https://github.com/arnaucube/fri-commitment}{https://github.com/arnaucube/fri-commitment} \cite{fri-impl}. 42 | 43 | Update(2024-03-22): notes on STIR \cite{cryptoeprint:2024/390} from explanations by \href{https://hecmas.github.io/}{Héctor Masip Ardevol} \cite{hectormasipardevol}. 44 | \end{abstract} 45 | 46 | \tableofcontents 47 | 48 | \section{Preliminaries} 49 | \subsection{General degree d test} 50 | 51 | Query at points $\{ x_i \}_0^{d+1},~z$ (with rand $z \overset{R}{\in} \mathbb{F}$). 52 | Interpolate $p(x)$ at $\{f(x_i)\}_0^{d+1}$ to reconstruct the unique polynomial $p$ of degree $d$ such that $p(x_i)=f(x_i)~\forall i=1, \ldots, d+1$. 53 | 54 | V checks $p(z)=f(z)$, if the check passes, then V is convinced with high probability. 55 | 56 | This needs $d+2$ queries, is linear, $\mathcal{O}(n)$. With FRI we will have the test in $\mathcal{O}(\log{}d)$. 57 | 58 | \section{FRI protocol} 59 | Allows to test if a function $f$ is a poly of degree $\leq d$ in $\mathcal{O}(\log{}d)$. 60 | 61 | Note: "P \emph{sends} $f(x)$ to V", "\emph{sends}", in the ideal IOP model means that all the table of $f(x)$ is sent, in practice is sent a commitment to $f(x)$. 62 | 63 | \subsection{Intuition} 64 | V wants to check that two functions $g,~h$ are both polynomials of degree $\leq d$. 65 | 66 | Consider the following protocol: 67 | 68 | \begin{enumerate} 69 | \item V sends $\alpha \in \mathbb{F}$ to P. 70 | \item P sends $f(x)=g(x) + \alpha h(x)$ to V. 71 | \item V queries $f(r), ~g(r), ~h(r)$ for rand $r \in \mathbb{F}$. 72 | \item V checks $f(r)=g(r) + \alpha h(r)$. (Schwartz-Zippel lema). 73 | If holds, V can be certain that $f(x)=g(x)+ \alpha h(x)$. 74 | \item P proves that $deg(f) \leq d$. 75 | \item If V is convinced that $deg(f) \leq d$, V believes that both $g, h$ have $deg \leq d$. 76 | \end{enumerate} 77 | 78 | %/// TODO tabulate this next lines 79 | With high probablility, $\alpha$ will not cancel the coeffs with $deg \geq d+1$. % TODO check which is the name of this theorem or why this is true 80 | 81 | Let $g(x)=a \cdot x^{d+1}, ~~ h(x)=b \cdot x^{d+1}$, and set $f(x) = g(x) + \alpha h(x)$. 82 | Imagine that P can chose $\alpha$ such that $a x^{d+1} + \alpha \cdot b x^{d+1} = 0$, then, in $f(x)$ the coefficients of degree $d+1$ would cancel. 83 | %/// 84 | 85 | \quad 86 | 87 | Here, P proves $g,~h$ both have $deg \leq d$, but instead of doing $2 \cdot (d+2)$ queries ($d+2$ for $g$, and $d+2$ for $h$), it is done in $d+2$ queries (for $f$). 88 | So we halved the number of queries. 89 | 90 | 91 | \subsection{FRI-LDT}\label{sec:fri-ldt} 92 | FRI low degree testing.\\ 93 | Both P and V have oracle access to function $f$. 94 | 95 | V wants to test if $f$ is polynomial with $deg(f) \leq d$. 96 | 97 | Let $f_0(x)=f(x)$. 98 | 99 | Each polynomial $f(x)$ of degree that is a power of $2$, can be written as 100 | $$f(x) = f^L(x^2) + x f^R(x^2)$$ 101 | for some polynomials $f^L,~f^R$ of degree $\frac{deg(f)}{2}$, each one containing the even and odd degree coefficients as follows: 102 | 103 | % $f^L(x)$ is built from the even degree coefficients divided by $x$, and $f^R(x)$ from the odd degree coefficients divided by $x$. 104 | 105 | $$f^L(x)= \sum_0^{\frac{d+1}{2}-1} c_{2i} x^i ,~~ f^R(x)= \sum_0^{\frac{d+1}{2}-1} c_{2i+1} x^i$$ 106 | 107 | eg. for $f(x)=x^4+x^3+x^2+x+1$, 108 | \begin{align*} 109 | \begin{rcases} 110 | f^L(x)=x^2+x+1\\ 111 | f^R(x)=x+1 112 | \end{rcases} 113 | ~f(x) = f^L(x^2) &+ x \cdot f^R(x^2)\\ 114 | = (x^2)^2 + (x^2) + 1 &+ x \cdot ((x^2) + 1)\\ 115 | = x^4 + x^2 + 1 &+ x^3 + x 116 | \end{align*} 117 | 118 | % \begin{enumerate} 119 | % \item V sends to P some $\alpha_0 \in \mathbb{F}$. 120 | % Let 121 | % \begin{equation}\tag{$A_0$} 122 | % f_0(x) = f_0^L(x^2) + x f_0^R(x^2) 123 | % \end{equation} 124 | % \item P sends 125 | % \begin{equation}\tag{$B_0$} 126 | % f_1(x) = f_0^L(x) + \alpha_0 f_0^R(x) 127 | % \end{equation} 128 | % to V. 129 | % 130 | % (remember that "sends" in IOP model is that P commits to it) 131 | % \item V sends to P some $\alpha_1 \in \mathbb{F}$. 132 | % Let 133 | % \begin{equation}\tag{$A_1$} 134 | % f_1(x) = f_1^L(x^2) + x f_1^R(x^2) 135 | % \end{equation} 136 | % \item P sends 137 | % \begin{equation}\tag{$B_1$} 138 | % f_2(x) = f_1^L(x) + \alpha_1 f_1^R(x) 139 | % \end{equation} 140 | % to V. 141 | % \item Keep repeating the process, eg. let 142 | % \begin{equation}\tag{$A_2$} 143 | % f_2(x) = f_2^L(x^2) + x f_2^R(x^2) 144 | % \end{equation} 145 | % until $f_i^L,~ f_i^R$ are constant (degree 0 polynomials). 146 | % \item Once $f_i^L,~ f_i^R$ are constant, P sends them to V. 147 | % \end{enumerate} 148 | % 149 | % Notice that at each step, $deg(f_i)$ halves. 150 | 151 | 152 | \vspace{30px} 153 | 154 | \paragraph{Proof generation} 155 | 156 | \emph{(Commitment phase)} 157 | P starts from $f(x)$, and for $i=0$ sets $f_0(x)=f(x)$. 158 | \begin{enumerate} 159 | \item $\forall~i \in \{0, log(d)\}$, with $d = deg~f(x)$,\\ 160 | P computes $f_i^L(x),~ f_i^R(x)$ for which 161 | \begin{equation}\tag{eq. $A_i$} 162 | f_i(x) = f_i^L(x^2) + x f_i^R(x^2) 163 | \end{equation} 164 | holds. 165 | \item V sends challenge $\alpha_i \in \mathbb{F}$ 166 | \item P commits to the random linear combination $f_{i+1}$, for 167 | \begin{equation}\tag{eq. $B_i$} 168 | f_{i+1}(x) = f_i^L(x) + \alpha_i f_i^R(x) 169 | \end{equation} 170 | \item P sets $f_i(x) := f_{i+1}(x)$ and starts again the iteration. 171 | \end{enumerate} 172 | 173 | Note on step 3: when we say "commits", this means that the prover P evaluates $f_{i+1}(x)$ at the $(\rho^{-1} \cdot d)$-sized evaluation 174 | domain $D$ (ie. $f_{i+1}(x) |_D$), and constructs a merkle tree with the 175 | evaluations as leaves. 176 | 177 | \vspace{0.5cm} 178 | Notice that at each step, $deg(f_i)$ halves with respect to $deg(f_{i-1}$). 179 | 180 | This is done until the last step, where $f_i^L(x),~ f_i^R(x)$ are constant (degree 0 polynomials). For which P does not commit but gives their values directly to V. 181 | 182 | \emph{(Query phase)} 183 | P would receive a challenge $z \in D$ set by V (where $D$ is the evaluation 184 | domain, $D \subset \mathbb{F}$), and P would open the commitments at $\{z^{2^i}, -z^{2^i}\}$ for each step $i$. 185 | (Recall, "opening" means that would provide a proof (MerkleProof) of it). 186 | 187 | \paragraph{Data sent from P to V} 188 | \begin{itemize} 189 | \item[] Commitments: $\{Comm(f_i)\}_0^{log(d)}$\\ 190 | {\scriptsize eg. $\{Comm(f_0),~ Comm(f_1),~ Comm(f_2),~ ...,~ Comm(f_{log(d)})\}$ } 191 | \item[] Openings: $\{ f_i(z^{2^i}),~f_i(-(z^{2^i})) \}_0^{log(d)}$\\ 192 | for a challenge $z \in D$ set by V\\ 193 | {\scriptsize eg. $f_0(z),~ f_0(-z),~ f_1(z^2),~ f_1(-z^2),~ f_2(z^4),~ f_2(-z^4),~ f_3(z^8),~ f_3(-z^8),~ \ldots$} 194 | \item[] Constant values of last iteration: $\{f_k^L,~f_k^R\}$, for $k=log(d)$ 195 | \end{itemize} 196 | 197 | % \begin{figure}[htp] 198 | % \centering 199 | % \begin{footnotesize} 200 | % \begin{sequencediagram} 201 | % \newinst[0]{p}{Prover} 202 | % \newinst[5]{v}{Verifier} 203 | % 204 | % \mess{p}{$\{Comm(f_i)\}_0^{log(d)},~ \{f_i(z^{2^i}),~f_i(-(z^{2^i})) \}_0^{log(d)},~ \{f_k^L,~ f_k^R\}$}{v} 205 | % 206 | % \end{sequencediagram} 207 | % \end{footnotesize} 208 | % \caption[FRI-LDT]{sketch of the FRI-LDT flow} 209 | % \label{fig:fri-ldt} 210 | % \end{figure} 211 | 212 | 213 | \paragraph{Verification} 214 | 215 | V receives: 216 | \begin{align*} 217 | \text{Commitments:}~ &Comm(f_i),~ \forall i \in \{0, log(d)\}\\ 218 | \text{Openings:}~ &\{o_i, o_i'\}=\{ f_i(z^{2^i}),~f_i(-(z^{2^i})) \},~ \forall i \in \{0, log(d)\}\\ 219 | \text{Constant vals:}~ &\{f_k^L,~f_k^R\} 220 | \end{align*} 221 | 222 | \vspace{20px} 223 | 224 | For all $i \in \{0, log(d)\}$, V knows the openings at $z^{2^i}$ and $-(z^{2^i})$ for\\ 225 | $Comm(f_i(x))$, which are $o_i=f_i(z^{2^i})$ and $o_i'=f_i(-(z^{2^i}))$ respectively. 226 | 227 | V, from (eq. $A_i$), knows that 228 | $$f_i(x)=f_i^L(x^2) + x f_i^R(x^2)$$ 229 | should hold, thus 230 | $$f_i(z)=f_i^L(z^2) + z f_i^R(z^2)$$ 231 | where $f_i(z)$ is known, but $f_i^L(z^2),~f_i^R(z^2)$ are unknown. 232 | But, V also knows the value for $f_i(-z)$, which can be represented as 233 | $$f_i(-z)=f_i^L(z^2) - z f_i^R(z^2)$$ 234 | (note that when replacing $x$ by $-z$, it loses the negative in the power, not in the linear combination). 235 | 236 | Thus, we have the system of independent linear equations 237 | \begin{align*} % TODO add braces on left 238 | f_i(z)&=f_i^L(z^2) + z f_i^R(z^2)\\ 239 | f_i(-z)&=f_i^L(z^2) - z f_i^R(z^2) 240 | \end{align*} 241 | for which V will find the value of $f_i^L(z^{2^i}),~f_i^R(z^{2^i})$. 242 | Equivalently it can be represented by 243 | $$ 244 | \begin{pmatrix} 245 | 1 & z\\ 246 | 1 & -z 247 | \end{pmatrix} 248 | \begin{pmatrix} 249 | f_i^L(z^2)\\ 250 | f_i^R(z^2) 251 | \end{pmatrix} 252 | = 253 | \begin{pmatrix} 254 | f_i(z)\\ 255 | f_i(-z) 256 | \end{pmatrix} 257 | $$ 258 | where V will find the values of $f_i^L(z^{2^i}),~f_i^R(z^{2^i})$ being 259 | \begin{align*} 260 | f_i^L(z^{2^i})=\frac{f_i(z) + f_i(-z)}{2}\\ 261 | f_i^R(z^{2^i})=\frac{f_i(z) - f_i(-z)}{2z}\\ 262 | \end{align*} 263 | 264 | Once, V has computed $f_i^L(z^{2^i}),~f_i^R(z^{2^i})$, can use them to compute the linear combination of 265 | $$ 266 | f_{i+1}(z^{2^i}) = f_i^L(z^{2^i}) + \alpha_i f_i^R(z^{2^i}) 267 | $$ 268 | obtaining then $f_{i+1}(z^{2^i})$. This comes from (eq. $B_i$). 269 | 270 | Now, V checks that the obtained $f_{i+1}(z^{2^i})$ is equal to the received opening $o_{i+1}=f_{i+1}(z^{2^i})$ from the commitment done by P. 271 | V checks also the commitment of $Comm(f_{i+1}(x))$ for the opening $o_{i+1}=f_{i+1}(z^{2^i})$.\\ 272 | If the checks pass, V is convinced that $f_1(x)$ was committed honestly. 273 | 274 | Now, sets $i := i+1$ and starts a new iteration. 275 | 276 | For the last iteration, V checks that the obtained $f_i^L(z^{2^i}),~f_i^R(z^{2^i})$ are equal to the constant values $\{f_k^L,~f_k^R\}$ received from P. 277 | 278 | \vspace{10px} 279 | It needs $log(d)$ iterations, and the number of queries (commitments + openings sent and verified) needed is $2 \cdot log(d)$. 280 | 281 | \subsection{Parameters} 282 | 283 | P commits to $f_i$ restricted to a subfield $F_0 \subset \mathbb{F}$. 284 | Let $0<\rho<1$ be the \emph{rate} of the code, such that 285 | $$|F_0| = \rho^{-1} \cdot d$$ 286 | 287 | \begin{theorem} 288 | For $\delta \in (0, 1-\sqrt{\rho})$, we have that if V accepts, then w.v.h.p. (with very high probability) $\Delta(f_0,~ p^d) \leq \delta$. 289 | \end{theorem} 290 | 291 | \section{FRI as polynomial commitment scheme} 292 | This section overviews the trick from \cite{cryptoeprint:2019/1020} to convert FRI into a polynomial commitment. 293 | 294 | Want to check that the evaluation of $f(x)$ at $r$ is $f(r)$, for $r \notin D, r 295 | \in^R \mathbb{F}$; which is equivalent to proving that $\exists ~Q \in \mathbb{F}[x]$ with $deg(Q)=d-1$, such that 296 | 297 | $$ 298 | f(x)-f(r) = Q(x) \cdot (x-r) 299 | $$ 300 | 301 | note that $f(x)-f(r)$ evaluated at $r$ is $0$, so $(x-r) | (f(x)-f(r))$, in other words 302 | $(f(x)-f(r))$ is a multiple of $(x-r)$ for a polynomial $Q(x)$. 303 | 304 | Let us define $g(x) = \frac{f(x)-f(r)}{x-r}$. 305 | 306 | Prover uses FRI-LDT \ref{sec:fri-ldt} to commit to $g(x)$, and then prove w.v.h.p that $deg(g) \leq d-1$ ($\Longleftrightarrow \Delta(g,~ p^{d-1} \leq \delta$). 307 | 308 | Prover was already proving that $deg(f) \leq d$. 309 | 310 | Now, the missing thing to prove is that $g(x)$ has the right shape. We can relate $g$ to $f$ as follows: 311 | V does the normal FRI-LDT, but in addition, at the first iteration: 312 | V has $f(z)$ and $g(z)$ openings, so can verify 313 | $$g(z) = (f(z)-f(r))\cdot (z-r)^{-1}$$ 314 | 315 | 316 | \section{STIR (main idea)} 317 | \emph{Update from 2024-03-22, notes from Héctor Masip Ardevol (\href{https://hecmas.github.io/}{https://hecmas.github.io}) explanations.} 318 | 319 | \vspace{0.3cm} 320 | Let $p \in \mathbb{F}[x]^{,>=angle 60] (mess from) -- (mess to) node[midway, above] 29 | {#3}; 30 | 31 | \if R#5 32 | \node (#3 from) at (mess from) {\llap{#6~}}; 33 | \node (#3 to) at (mess to) {\rlap{~#7}}; 34 | \else\if L#5 35 | \node (#3 from) at (mess from) {\rlap{~#6}}; 36 | \node (#3 to) at (mess to) {\llap{#7~}}; 37 | \else 38 | \node (#3 from) at (mess from) {#6}; 39 | \node (#3 to) at (mess to) {#7}; 40 | \fi 41 | \fi 42 | } 43 | 44 | % prevent warnings of underfull \hbox: 45 | \usepackage{etoolbox} 46 | \apptocmd{\sloppy}{\hbadness 4000\relax}{}{} 47 | 48 | \theoremstyle{definition} 49 | \newtheorem{definition}{Def}[section] 50 | \newtheorem{theorem}[definition]{Thm} 51 | 52 | 53 | \title{Notes on Halo} 54 | \author{arnaucube} 55 | \date{July 2022} 56 | 57 | \begin{document} 58 | 59 | \maketitle 60 | 61 | \begin{abstract} 62 | Notes taken while reading Halo paper \cite{cryptoeprint:2019/1021}. Usually while reading papers I take handwritten notes, this document contains some of them re-written to $LaTeX$. 63 | 64 | The notes are not complete, don't include all the steps neither all the proofs. 65 | \end{abstract} 66 | 67 | \tableofcontents 68 | 69 | \section{modified IPA (from Halo paper)} 70 | Notes taken while reading about the modified Inner Product Argument (IPA) from the Halo paper \cite{cryptoeprint:2019/1021}. 71 | 72 | \paragraph{Objective:} 73 | Prover wants to prove that the polynomial $p(X)$ from the commitment $P$ evaluates to $v$ at $x$, and that $deg(p(X)) \leq d-1$. 74 | 75 | \subsection{Notation} 76 | \begin{description} 77 | \item[Scalar mul] $[a]G$, where $a$ is a scalar and $G \in \mathbb{G}$ 78 | \item[Inner product] $<\overrightarrow{a}, \overrightarrow{b}> = a_0 b_0 + a_1 b_1 + \ldots + a_{n-1} b_{n-1}$ 79 | \item[Multiscalar mul] $<\overrightarrow{a}, \overrightarrow{G}> = [a_0] G_0 + [a_1] G_1 + \ldots + [a_{n-1}] G_{n-1}$ 80 | \end{description} 81 | 82 | 83 | \subsection{Transparent setup} 84 | $\overrightarrow{G} \in^r \mathbb{G}^d$, $H \in^r \mathbb{G}$ 85 | 86 | Prover wants to commit to $p(x)=a_0$ 87 | \subsection{Protocol} 88 | Prover: 89 | $$P=<\overrightarrow{a}, \overrightarrow{G}> + [r]H$$ 90 | $$v=<\overrightarrow{a}, \{1, x, x^2, \ldots, x^{d-1} \} >$$ 91 | 92 | where $\{1, x, x^2, \ldots, x^{d-1} \} = \overrightarrow{b}$. 93 | 94 | We can see that computing $v$ is the equivalent to evaluating $p(X)$ at $x$ ($p(x)=v$). 95 | 96 | We will prove: 97 | \begin{enumerate}[i.] 98 | \item polynomial $p(X) = \sum a_i X^i$\\ 99 | $p(x) = v$ (that $p(X)$ evaluates $x$ to $v$). 100 | \item $deg(p(X)) \leq d-1$ 101 | \end{enumerate} 102 | 103 | 104 | Both parties know $P$, point $x$ and claimed evaluation $v$. For $U \in^r \mathbb{G}$. 105 | 106 | Prover computes $P'$: 107 | 108 | $$P' = P + [v] U = <\overrightarrow{a}, G> + [r]H + [v] U$$ 109 | 110 | Now, for $k$ rounds ($d=2^k$, from $j=k$ to $j=1$): 111 | \begin{itemize} 112 | \item Prover sets random blinding factors: $l_j, r_j \in \mathbb{F}_p$ 113 | \item Prover computes 114 | $$L_j = < \overrightarrow{a}_{lo}, \overrightarrow{G}_{hi}> + [l_j] H + [< \overrightarrow{a}_{lo}, \overrightarrow{b}_{hi}>] U$$ 115 | $$R_j = < \overrightarrow{a}_{hi}, \overrightarrow{G}_{lo}> + [r_j] H + [< \overrightarrow{a}_{hi}, \overrightarrow{b}_{lo}>] U$$ 116 | \item Verifier sends random challenge $u_j \in \mathbb{I}$ 117 | \item Prover computes the halved vectors for next round: 118 | $$\overrightarrow{a} \leftarrow \overrightarrow{a}_{hi} \cdot u_j^{-1} + \overrightarrow{a}_{lo} \cdot u_j$$ 119 | $$\overrightarrow{b} \leftarrow \overrightarrow{b}_{lo} \cdot u_j^{-1} + \overrightarrow{b}_{hi} \cdot u_j$$ 120 | $$\overrightarrow{G} \leftarrow \overrightarrow{G}_{lo} \cdot u_j^{-1} + \overrightarrow{G}_{hi} \cdot u_j$$ 121 | \end{itemize} 122 | 123 | After final round, $\overrightarrow{a}, \overrightarrow{b}, \overrightarrow{G}$ are each of length 1. 124 | 125 | Verifier can compute 126 | $$G = \overrightarrow{G}_0 = < \overrightarrow{s}, \overrightarrow{G} >$$ 127 | and $$b = \overrightarrow{b}_0 = < \overrightarrow{s}, \overrightarrow{b} >$$ 128 | where $\overrightarrow{s}$ is the binary counting structure: 129 | 130 | \begin{align*} 131 | &s = (u_1^{-1} ~ u_2^{-1} \cdots ~u_k^{-1},\\ 132 | &~~~~~~u_1 ~~~ u_2^{-1} ~\cdots ~u_k^{-1},\\ 133 | &~~~~~~u_1^{-1} ~~ u_2 ~~\cdots ~u_k^{-1},\\ 134 | &~~~~~~~~~~~~~~\vdots\\ 135 | &~~~~~~u_1 ~~~~ u_2 ~~\cdots ~u_k) 136 | \end{align*} 137 | 138 | 139 | And verifier checks: 140 | $$[a]G + [r'] H + [ab] U == P' + \sum_{j=1}^k ( [u_j^2] L_j + [u_j^{-2}] R_j)$$ 141 | 142 | where the synthetic blinding factor $r'$ is $r' = r + \sum_{j=1}^k (l_j u_j^2 + r_j u_j^{-2})$. 143 | 144 | \vspace{1cm} 145 | 146 | Unfold: 147 | 148 | $$ 149 | \textcolor{brown}{[a]G} + \textcolor{cyan}{[r'] H} + \textcolor{magenta}{[ab] U} 150 | == 151 | \textcolor{blue}{P'} + \sum_{j=1}^k ( \textcolor{violet}{[u_j^2] L_j} + \textcolor{orange}{[u_j^{-2}] R_j}) 152 | $$ 153 | 154 | \begin{align*} 155 | &Left~side = \textcolor{brown}{[a]G} + \textcolor{cyan}{[r'] H} + \textcolor{magenta}{[ab] U}\\ 156 | & = \textcolor{brown}{< \overrightarrow{a}, \overrightarrow{G} >}\\ 157 | &+ \textcolor{cyan}{[r + \sum_{j=1}^k (l_j \cdot u_j^2 + r_j u_j^{-2})] \cdot H}\\ 158 | &+ \textcolor{magenta}{< \overrightarrow{a}, \overrightarrow{b} > U} 159 | \end{align*} 160 | 161 | 162 | \begin{align*} 163 | &Right~side = \textcolor{blue}{P'} + \sum_{j=1}^k ( \textcolor{violet}{[u_j^2] L_j} + \textcolor{orange}{[u_j^{-2}] R_j})\\ 164 | &= \textcolor{blue}{< \overrightarrow{a}, \overrightarrow{G}> + [r] H + [v] U}\\ 165 | &+ \sum_{j=1}^k ( 166 | \textcolor{violet}{[u_j^2] \cdot <\overrightarrow{a}_{lo}, \overrightarrow{G}_{hi}> + [l_j] H + [<\overrightarrow{a}_{lo}, \overrightarrow{b}_{hi}>] U}\\ 167 | &\textcolor{orange}{+ [u_j^{-2}] \cdot <\overrightarrow{a}_{hi}, \overrightarrow{G}_{lo}> + [r_j] H + [<\overrightarrow{a}_{hi}, \overrightarrow{b}_{lo}>] U} 168 | ) 169 | \end{align*} 170 | 171 | 172 | \vspace{1.5cm} 173 | The following diagram ilustrates the main steps in the scheme: 174 | 175 | \begin{center} 176 | \begin{sequencediagram} 177 | \newinst[1]{p}{Prover} 178 | \newinst[3]{v}{Verifier} 179 | 180 | \bloodymess[1]{p}{P}{v}{R}{knows $p(X)\in \mathbb{F[X]}$, commits to $p(X)$, $P$}{rand $x \in \mathbb{F},~U\in \mathbb{G},~\overrightarrow{u} \in \mathbb{F}^d$} 181 | \bloodymess[1]{v}{$x, U, u$}{p}{R}{}{} 182 | \bloodymess[1]{p}{$proof, a, L_j, R_j, v$}{v}{R}{gen proof}{$verify(proof, P, a, x, L_j, R_j)$} 183 | 184 | % \begin{callself}{p}{knows $p(X) \in \mathbb{F}[X]$}{} 185 | % \end{callself} 186 | % \begin{callself}{p}{commit to $p(X),~P$}{} 187 | % \end{callself} 188 | % 189 | % \mess[0]{p}{$P$}{v} 190 | % \begin{callself}{v}{rand $x \in \mathbb{F},~U\in \mathbb{G},~\overrightarrow{u} \in \mathbb{F}^d$}{} 191 | % \end{callself} 192 | % 193 | % \mess[0]{v}{$x,U,u$}{p} 194 | 195 | % \node[anchor=west] (p2) at (mess to) {gen proof2} 196 | 197 | % \begin{callself}{p}{gen proof $\pi$}{} 198 | % \end{callself} 199 | % 200 | % \mess[0]{p}{$a, L_j, R_j, v$}{v} 201 | % 202 | % \begin{callself}{v}{$verify(P, a, x, v, L_j, R_k$)}{} 203 | % \end{callself} 204 | \end{sequencediagram} 205 | \end{center} 206 | 207 | \section{Amortization Strategy} 208 | TODO 209 | 210 | \bibliography{paper-notes.bib} 211 | \bibliographystyle{unsrt} 212 | 213 | \end{document} 214 | -------------------------------------------------------------------------------- /notes_hypernova.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaucube/math/6593857e91fe437f72d5420ef23fec3ad29b0b40/notes_hypernova.pdf -------------------------------------------------------------------------------- /notes_nova.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaucube/math/6593857e91fe437f72d5420ef23fec3ad29b0b40/notes_nova.pdf -------------------------------------------------------------------------------- /notes_nova.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage{amsfonts} 4 | \usepackage{amsthm} 5 | \usepackage{amsmath} 6 | \usepackage{mathtools} 7 | \usepackage{enumerate} 8 | \usepackage{hyperref} 9 | \usepackage{xcolor} 10 | 11 | \usepackage{pgf-umlsd} % diagrams 12 | % message between threads 13 | % Example: 14 | % \bloodymess[delay]{sender}{message content}{receiver}{DIR}{start note}{end note} 15 | \newcommand{\bloodymess}[7][0]{ 16 | \stepcounter{seqlevel} 17 | \path 18 | (#2)+(0,-\theseqlevel*\unitfactor-0.7*\unitfactor) node (mess from) {}; 19 | \addtocounter{seqlevel}{#1} 20 | \path 21 | (#4)+(0,-\theseqlevel*\unitfactor-0.7*\unitfactor) node (mess to) {}; 22 | \draw[->,>=angle 60] (mess from) -- (mess to) node[midway, above] 23 | {#3}; 24 | 25 | \if R#5 26 | \node (\detokenize{#3} from) at (mess from) {\llap{#6~}}; 27 | \node (\detokenize{#3} to) at (mess to) {\rlap{~#7}}; 28 | \else\if L#5 29 | \node (\detokenize{#3} from) at (mess from) {\rlap{~#6}}; 30 | \node (\detokenize{#3} to) at (mess to) {\llap{#7~}}; 31 | \else 32 | \node (\detokenize{#3} from) at (mess from) {#6}; 33 | \node (\detokenize{#3} to) at (mess to) {#7}; 34 | \fi 35 | \fi 36 | } 37 | 38 | % prevent warnings of underfull \hbox: 39 | \usepackage{etoolbox} 40 | \apptocmd{\sloppy}{\hbadness 4000\relax}{}{} 41 | 42 | \theoremstyle{definition} 43 | \newtheorem{definition}{Def}[section] 44 | \newtheorem{theorem}[definition]{Thm} 45 | 46 | % custom lemma environment to set custom numbers 47 | \newtheorem{innerlemma}{Lemma} 48 | \newenvironment{lemma}[1] 49 | {\renewcommand\theinnerlemma{#1}\innerlemma} 50 | {\endinnerlemma} 51 | 52 | 53 | \title{Notes on Nova} 54 | \author{arnaucube} 55 | \date{March 2023} 56 | 57 | \begin{document} 58 | 59 | \maketitle 60 | 61 | \begin{abstract} 62 | Notes taken while reading Nova \cite{cryptoeprint:2021/370} paper. 63 | 64 | Usually while reading papers I take handwritten notes, this document contains some of them re-written to $LaTeX$. 65 | 66 | The notes are not complete, don't include all the steps neither all the proofs. 67 | 68 | Thanks to \href{https://twitter.com/levs57}{Levs57}, \href{https://twitter.com/nibnalin}{Nalin Bhardwaj} and \href{https://twitter.com/cperezz19}{Carlos Pérez} for clarifications on the Nova paper. 69 | \end{abstract} 70 | 71 | \tableofcontents 72 | 73 | \section{NIFS} 74 | 75 | \subsection{R1CS modification} 76 | 77 | \paragraph{R1CS} 78 | R1CS instance: $(A, B, C, io, m, n)$, where $io$ denotes the public input and output, $A, B, C \in \mathbb{F}^{m \times n}$, with $m \geq |io|+1$. 79 | R1CS is satisfied by a witness $w \in \mathbb{F}^{m-|io|-1}$ such that 80 | $$Az \circ Bz = Cz$$ 81 | where $z=(io, 1, w)$. 82 | 83 | \vspace{0.5cm} 84 | 85 | \textbf{Want}: merge 2 instances of R1CS with the same matrices into a single one. Each instance has $z_i = (W_i,~ x_i)$ (public witness, private values resp.). 86 | 87 | \paragraph{traditional R1CS} 88 | Merged instance with $z=z_1 + r z_2$, for rand $r$. But, since R1CS is not linear $\longrightarrow$ can not apply. 89 | 90 | eg. 91 | \begin{align*} 92 | Az \circ Bz &= A(z_1 + r z_2) \circ B (z_1 + r z_2)\\ 93 | &= A z_1 \circ B z_1 + r(A z_1 \circ B z_2 + A z_2 \circ B z_1) + r^2 (A z_2 \circ B z_2)\\ 94 | &\neq Cz 95 | \end{align*} 96 | 97 | $\longrightarrow$ introduce error vector $E \in \mathbb{F}^m$, which absorbs the cross-temrs generated by folding. 98 | 99 | $\longrightarrow$ introduce scalar $u$, which absorbs an extra factor of $r$ in $C z_1 + r^2 C z_2$ and in $z=(W, x, 1+r\cdot 1)$. 100 | 101 | \paragraph{Relaxed R1CS} 102 | \begin{align*} 103 | &u=u_1+r u_2\\ 104 | &E=E_1 + r (A z_1 \circ B z_2 + A z_2 \circ B z_1 - u_1 C z_2 - u_2 C z_1) + r^2 E_2\\ 105 | &Az \circ Bz = uCz + E,~~ with~ z=(W,~x,~u) 106 | \end{align*} 107 | where R1CS set $E=0,~u=1$. 108 | 109 | \begin{align*} 110 | Az \circ Bz &= A z_1 \circ B z_1 + r(A z_1 \circ B z_2 + A z_2 \circ B z_1) + r^2 (A z_2 \circ B z_2)\\ 111 | &= (u_1 C z_1 + E_1) + r (A z_1 \circ B z_2 + A z_2 \circ B z_1) + r^2 (u_2 C z_2 + E_2)\\ 112 | &= u_1 C z_1 + \underbrace{E_1 + r(A z_1 \circ B z_2 + A z_2 \circ B z_1) + r^2 E_2}_\text{E} + r^2 u_2 C z_2\\ 113 | &= u_1 C z_1 + r^2 u_2 C z_2 + E\\ 114 | &= (u_1 + r u_2) \cdot C \cdot (z_1 + r z_2) + E\\ 115 | &= uCz + E 116 | \end{align*} 117 | 118 | For R1CS matrices $(A,~B,~C)$, the folded witness $W$ is a satisfying witness for the folded instance $(E,~u,~x)$. 119 | 120 | 121 | 122 | \vspace{20px} 123 | Problem: not non-trivial, and not zero-knowledge. Solution: use polynomial commitment with hiding, binding, succintness and additively homomorphic properties. 124 | 125 | \paragraph{Committed Relaxed R1CS} 126 | Instance for a Committed Relaxed R1CS\\ 127 | $(\overline{E}, u, \overline{W}, x)$, satisfied by a witness $(E, r_E, W, r_W)$ such that 128 | \begin{align*} 129 | &\overline{E} = Com(E, r_E)\\ 130 | &\overline{W} = Com(E, r_W)\\ 131 | &Az \circ Bz = uCz+E,~~ where~z=(W, x, u) 132 | \end{align*} 133 | 134 | 135 | \subsection{Folding scheme for committed relaxed R1CS} 136 | 137 | V and P take two \emph{committed relaxed R1CS} instances 138 | \begin{align*} 139 | \varphi_1&=(\overline{E}_1, u_1, \overline{W}_1, x_1)\\ 140 | \varphi_2&=(\overline{E}_2, u_2, \overline{W}_2, x_2) 141 | \end{align*} 142 | 143 | P additionally takes witnesses to both instances 144 | \begin{align*} 145 | (E_1, r_{E_1}, W_1, r_{W_1})\\ 146 | (E_2, r_{E_2}, W_2, r_{W_2}) 147 | \end{align*} 148 | 149 | Let $Z_1 = (W_1, x_1, u_1)$ and $Z_2 = (W_2, x_2, u_2)$. 150 | 151 | % \paragraph{Protocol} 152 | \begin{enumerate} 153 | \item P send $\overline{T} = Com(T, r_T)$,\\ 154 | where $T=A z_1 \circ B z_1 + A z_2 \circ B z_2 - u_1 C z_1 - u_2 C z_2$\\ 155 | and rand $r_T \in \mathbb{F}$ 156 | \item V sample random challenge $r \in \mathbb{F}$ 157 | \item V, P output the folded instance $\varphi = (\overline{E}, u, \overline{W}, x)$ 158 | \begin{align*} 159 | &\overline{E}=\overline{E}_1 + r \overline{T} + r^2 \overline{E}_2\\ 160 | &u = u_1 + r u_2\\ 161 | &\overline{W} = \overline{W}_1 + r \overline{W}_2\\ 162 | &x = x_1 + r x_2 163 | \end{align*} 164 | \item P outputs the folded witness $(E, r_E, W, r_W)$ 165 | \begin{align*} 166 | &E = E_1 + r T + r^2 E_2\\ 167 | &r_E = r_{E_1} + r \cdot r_T + r^2 r_{E_2}\\ 168 | &W=W_1 + r W_2\\ 169 | &r_W = r_{W_1} + r \cdot r_{W_2} 170 | \end{align*} 171 | \end{enumerate} 172 | 173 | P will prove that knows the valid witness $(E, r_E, W, r_W)$ for the committed relaxed R1CS without revealing its value. 174 | 175 | \begin{center} 176 | \begin{sequencediagram} 177 | \newinst[1]{p}{Prover} 178 | \newinst[3]{v}{Verifier} 179 | 180 | \bloodymess[1]{p}{$\overline{T}$}{v}{R}{ 181 | \shortstack{ 182 | $T=A z_1 \circ B z_1 + A z_2 \circ B z_2 - u_1 C z_2 - u_2 C z_2$\\ 183 | $\overline{T}=Commit(T, r_T)$ 184 | } 185 | }{ 186 | \shortstack{ 187 | $r \in^R \mathbb{F}_p$\\ 188 | $\overline{E} = \overline{E}_1 + r \overline{T} + r^2 \overline{E}_2$\\ 189 | $u= u_1 + r u_2$\\ 190 | $\overline{W} = \overline{W}_1 + r \overline{W}_2$\\ 191 | $\overline{x} = \overline{x}_1 + r \overline{x}_2$\\ 192 | $\varphi=(\overline{E}, u, \overline{W}, x)$ 193 | } 194 | } 195 | \bloodymess[1]{v}{$r$}{p}{L}{}{ 196 | \shortstack{ 197 | $E = E_1 + r T + r^2 E_2$\\ 198 | $u= u_1 + r u_2$\\ 199 | $W = W_1 + r W_2$\\ 200 | $r_{W} = r_{W_1} + r r_{W_2}$\\ 201 | $(E, r_E, W, r_W)$ 202 | } 203 | } 204 | \end{sequencediagram} 205 | \end{center} 206 | 207 | 208 | The previous protocol achieves non-interactivity via Fiat-Shamir transform, obtaining a \emph{Non-Interactive Folding Scheme for Committed Relaxed R1CS}. 209 | 210 | Note: the paper later uses $\mathsf{u}_i,~ \mathsf{U}_i$ for the two inputted $\varphi_1,~ \varphi_2$, and later $\mathsf{u}_{i+1}$ for the outputted $\varphi$. Also, the paper later uses $\mathsf{w},~ \mathsf{W}$ to refer to the witnesses of two folded instances (eg. $\mathsf{w}=(E, r_E, W, r_W)$). 211 | 212 | 213 | \subsection{NIFS} 214 | 215 | \underline{fold witness, $(pk, (u_1, w_1), (u_2, w_2))$}: 216 | \begin{enumerate} 217 | \item $T=A z_1 \circ B z_1 + A z_2 \circ B z_2 - u_1 C z_2 - u_2 C z_2$ 218 | \item $\overline{T}=Commit(T, r_T)$ 219 | % \item output the folded instance $\varphi = (\overline{E}, u, \overline{W}, x)$ 220 | % \begin{align*} 221 | % &\overline{E}=\overline{E}_1 + r \overline{T} + r^2 \overline{E}_2\\ 222 | % &u = u_1 + r u_2\\ 223 | % &\overline{W} = \overline{W}_1 + r \overline{W}_2\\ 224 | % &x = x_1 + r x_2 225 | % \end{align*} 226 | \item output the folded witness $(E, r_E, W, r_W)$ 227 | \begin{align*} 228 | &E = E_1 + r T + r^2 E_2\\ 229 | &r_E = r_{E_1} + r \cdot r_T + r^2 r_{E_2}\\ 230 | &W=W_1 + r W_2\\ 231 | &r_W = r_{W_1} + r \cdot r_{W_2} 232 | \end{align*} 233 | \end{enumerate} 234 | 235 | \underline{fold instances $(\varphi_1, \varphi_2) \rightarrow \varphi$, $(vk, u_1, u_2, \overline{E}_1, \overline{E}_2, \overline{W}_1, \overline{W}_2, \overline{T})$}:\\ 236 | V compute folded instance $\varphi = (\overline{E}, u, \overline{W}, x)$ 237 | \begin{align*} 238 | &\overline{E}=\overline{E}_1 + r \overline{T} + r^2 \overline{E}_2\\ 239 | &u = u_1 + r u_2\\ 240 | &\overline{W} = \overline{W}_1 + r \overline{W}_2\\ 241 | &x = x_1 + r x_2 242 | \end{align*} 243 | 244 | \section{Nova} 245 | IVC (Incremental Verifiable Computation) scheme for a non-interactive folding scheme. 246 | 247 | \subsection{IVC proofs} 248 | 249 | Allows prover to show $z_n = F^{(n)}(z_0)$, for some count $n$, initial input $z_0$, and output $z_n$.\\ 250 | $F$: program function (polynomial-time computable)\\ 251 | $F'$: augmented function, invokes $F$ and additionally performs fold-related stuff. 252 | 253 | \vspace{0.5cm} 254 | Two committed relaxed R1CS instances:\\ 255 | $\mathsf{U}_i$: represents the correct execution of invocations $1, \ldots, i-1$ of $F'$\\ 256 | $\mathsf{u}_i$: represents the correct execution of invocations $i$ of $F'$ 257 | 258 | \paragraph{Simplified version of $F'$ for intuition} 259 | \vspace{0.5cm} 260 | $F'$ performs two tasks: 261 | \begin{enumerate}[i.] 262 | \item execute a step of the incremental computation: 263 | instance $\mathsf{u}_i$ contains $z_i$, used to output $z_{i+1}=F(z_i)$ 264 | \item invokes the verifier of the non-interactive folding scheme to fold the task of checking $\mathsf{u}_i$ and $\mathsf{U}_i$ into the task of checking a single instance $\mathsf{U}_{i+1}$ 265 | \end{enumerate} 266 | 267 | \vspace{0.5cm} 268 | $F'$ proves that: 269 | \begin{enumerate} 270 | \item $\exists ( (i, z_0, z_i, \mathsf{u}_i, \mathsf{U}_i), \mathsf{U}_{i+1}, \overline{T})$ such that 271 | \begin{enumerate}[i.] 272 | \item $\mathsf{u}_i.x = H(vk, i, z_0, z_i, \mathsf{U}_i)$ 273 | \item $h_{i+1} = H(vk, i+1, z_0, F(z_i), \mathsf{U}_{i+1})$ 274 | \item $\mathsf{U}_{i+1} = NIFS.V(vk, \mathsf{U}_i, \mathsf{u}_i, \overline{T})$ 275 | \end{enumerate} 276 | \item $F'$ outputs $h_{i+1}$ 277 | \end{enumerate} 278 | 279 | 280 | $F'$ is described as follows:\\ 281 | \underline{$F'(vk, \mathsf{U}_i, \mathsf{u}_i, (i, z_0, z_i), w_i, \overline{T}) \rightarrow x$}:\\ 282 | if $i=0$, output $H(vk, 1, z_0, F(z_0, w_i), \mathsf{u}_{\bot})$\\ 283 | otherwise 284 | \begin{enumerate} 285 | \item check $\mathsf{u}_i.x = H(vk, i, z_0, z_i, \mathsf{U}_i)$ 286 | \item check $(\mathsf{u}_i.\overline{E}, \mathsf{u}_i.u) = (\mathsf{u}_{\bot}.\overline{E}, 1)$ 287 | \item compute $\mathsf{U}_{i+1} \leftarrow NIFS.V(vk, U, u, \overline{T})$ 288 | \item output $H(vk, i+1, z_0, F(z_i, w_i), \mathsf{U}_{i+1})$ 289 | \end{enumerate} 290 | 291 | % TODO add diagram 292 | 293 | \paragraph{IVC Proof} 294 | iteration $i+1$: prover runs $F'$ and computes $\mathsf{u}_{i+1},~ \mathsf{U}_{i+1}$, with corresponding witnesses $\mathsf{w}_{i+1},~ \mathsf{W}_{i+1}$. 295 | $(\mathsf{u}_{i+1},~ \mathsf{U}_{i+1})$ attest correctness of $i+1$ invocations of $F'$, the IVC proof is $\pi_{i+1} = ( (\mathsf{U}_{i+1}, \mathsf{W}_{i+1}), (\mathsf{u}_{i+1}, \mathsf{w}_{i+1}))$. 296 | 297 | 298 | \vspace{0.5cm} 299 | 300 | \underline{$P(pk, (i, z_0, z_i), \mathsf{w}_i, \pi_i) \rightarrow \pi_{i+1}$}:\\ 301 | Parse $\pi_i = ( (\mathsf{U}_i, \mathsf{W}_i), (\mathsf{u}_i, \mathsf{w}_i))$, then 302 | \begin{enumerate} 303 | \item if $i=0$: $(\mathsf{U}_{i+1}, \mathsf{W}_{i+1}, \overline{T}) \leftarrow (\mathsf{u}_{\perp}, \mathsf{w}_{\perp}, \mathsf{u}_{\perp}.{\overline{E}})$\\ 304 | otherwise: $(\mathsf{U}_{i+1}, \mathsf{W}_{i+1}, \overline{T}) \leftarrow NIFS.P(pk, (\mathsf{U}_i, \mathsf{W}_i), (\mathsf{u}_i, \mathsf{w}_i))$ 305 | \item compute $(\mathsf{u}_{i+1}, \mathsf{w}_{i+1}) \leftarrow trace(F', (vk, \mathsf{U}_i, \mathsf{u}_i, (i, z_0, z_i), \mathsf{w}_i, \overline{T}))$ 306 | \item output $\pi_{i+1} \leftarrow ((\mathsf{U}_{i+1}, \mathsf{W}_{i+1}), (\mathsf{u}_{i+1}, \mathsf{w}_{i+1}))$ 307 | \end{enumerate} 308 | 309 | \underline{$V(vk, (i, z_0, z_i), \pi_i) \rightarrow \{0,1\}$}: 310 | if $i=0$: check that $z_i=z_0$\\ 311 | otherwise, parse $\pi_i = ( (\mathsf{U}_i, \mathsf{W}_i), (\mathsf{u}_i, \mathsf{w}_i))$, then 312 | \begin{enumerate} 313 | \item check $\mathsf{u}_i.x = H(vk, i, z_0, z_i, \mathsf{U}_i)$ 314 | \item check $(\mathsf{u}_i.{\overline{E}}, \mathsf{u}_i.u) = (\mathsf{u}_{\perp}.{\overline{E}}, 1)$ 315 | \item check that $\mathsf{W}_i,~ \mathsf{w}_i$ are satisfying witnesses to $\mathsf{U}_i,~ \mathsf{u}_i$ respectively 316 | \end{enumerate} 317 | 318 | \vspace{0.5cm} 319 | 320 | \paragraph{A zkSNARK of a Valid IVC Proof} prover and verifier:\\ 321 | \underline{$P(pk, (i, z_0, z_i), \Pi) \rightarrow \pi$}:\\ 322 | if $i=0$, output $\perp$, otherwise:\\ 323 | parse $\Pi$ as $((\mathsf{U}, \mathsf{W}), (\mathsf{u}, \mathsf{w}))$ 324 | \begin{enumerate} 325 | \item compute $(\mathsf{U}', \mathsf{W}', \overline{T}) \leftarrow NIFS.P(pk_{NIFS}, (\mathsf{U,~W}), (\mathsf{u,~w}))$ 326 | \item compute $\pi_{\mathsf{u}'} \leftarrow zkSNARK.P(pk_{zkSNARK}, \mathsf{U}', \mathsf{W}')$ 327 | \item output $(\mathsf{U,~ u}, \overline{T}, \pi_{\mathsf{u}'})$ 328 | \end{enumerate} 329 | 330 | \underline{$V(vk, (i, z_0, z_i), \pi) \rightarrow \{0,1\}$}:\\ 331 | if $i=0$: check that $z_i=z_0$\\ 332 | parse $\pi$ as $(\mathsf{U}, \mathsf{u}, \overline{T}, \pi_{\mathsf{u}'})$ 333 | \begin{enumerate} 334 | \item check $\mathsf{u}.x = H(vk_{NIFS}, i, z_0, z_i, \mathsf{U})$ 335 | \item check $(\mathsf{u}.{\overline{E}}, \mathsf{u}.u) = (\mathsf{u}_{\perp}.{\overline{E}}, 1)$ 336 | \item compute $\mathsf{U}' \leftarrow NIFS.V(vk_{NIFS}, \mathsf{U}, \mathsf{u}, \overline{T})$ 337 | \item check $zkSNARK.V(vk_{zkSNARK}, \mathsf{U}', \pi_{\mathsf{u}'})=1$ 338 | \end{enumerate} 339 | 340 | 341 | \bibliography{paper-notes.bib} 342 | \bibliographystyle{unsrt} 343 | 344 | \end{document} 345 | -------------------------------------------------------------------------------- /notes_ntt.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaucube/math/6593857e91fe437f72d5420ef23fec3ad29b0b40/notes_ntt.pdf -------------------------------------------------------------------------------- /notes_ntt.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage{amsfonts} 4 | \usepackage{amsthm} 5 | \usepackage{amsmath} 6 | \usepackage{enumerate} 7 | 8 | \usepackage{hyperref} 9 | \hypersetup{ 10 | colorlinks, 11 | citecolor=black, 12 | filecolor=black, 13 | linkcolor=black, 14 | urlcolor=blue 15 | } 16 | 17 | 18 | \newcommand{\Zq}{\mathbb{Z}_q} 19 | \newcommand{\Rq}{\mathbb{Z}_q[X]/(X^n+1)} 20 | 21 | 22 | \title{NTT for Negacyclic Polynomial Multiplication} 23 | \author{arnaucube} 24 | \date{January 2025} 25 | 26 | \begin{document} 27 | 28 | \maketitle 29 | 30 | \begin{abstract} 31 | Notes taken while studying the NTT, mostly from \cite{10177902}. 32 | 33 | Usually while reading books and papers I take handwritten notes in a notebook, this document contains some of them re-written to $LaTeX$. 34 | 35 | The notes are not complete, don't include all the steps neither all the proofs. 36 | 37 | Update: an implementation of the NTT can be found at\\ 38 | \href{https://github.com/arnaucube/fhe-study/blob/main/arith/src/ntt.rs}{https://github.com/arnaucube/fhe-study/blob/main/arith/src/ntt.rs}. 39 | \end{abstract} 40 | 41 | \tableofcontents 42 | 43 | \section{Main idea} 44 | For doing multiplications in the \emph{negacyclic polynomial ring} ($\Rq$), rather than doing it in a naive way, it is more 45 | efficient to do it through the NTT. 46 | 47 | This is, let $a(X), b(X) \in \Rq$, and suppose we want to 48 | obtain $a(X) \cdot b(X)$. First apply the NTT to the two ring 49 | elements that we want to multiply, 50 | $$\hat{a}(X) = NTT(a(X)),~~ \hat{b}(X)=NTT(b(X))$$ 51 | then multiply the result element-wise, 52 | % $$\hat{c}(X) = \sum \hat{a}_i \cdot \hat{b}_i$$ 53 | $$c= \hat{a} \circ \hat{b}$$ 54 | where $\circ$ means the element-wise vector multiplication in $\Zq$. 55 | 56 | Then apply the NTT$^{-1}$ to the result, obtaining the actual value of 57 | multiplying $a(X) \cdot b(X)$. 58 | 59 | \section{Cyclotomic vs Negacyclic} 60 | 61 | \subsection{Cyclotomic: \texorpdfstring{$\mathbb{Z}_q[X]/(X^n-1)$}{Zq[X]/(X**n-1)}} 62 | In the cyclotomic case, the primitive n-th root of unity in $Z_q$ is $w^n \equiv 1 \pmod q$ (and 63 | $w^k \not\equiv 1 \pmod q ~~ for k