UNPKG

@kotevode/ffjavascript

Version:

Finite Field Library in Javascript

619 lines (494 loc) 16 kB
/* Copyright 2018 0kims association. This file is part of snarkjs. snarkjs is a free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. snarkjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with snarkjs. If not, see <https://www.gnu.org/licenses/>. */ /* This library does operations on polynomials with coefficients in a field F. A polynomial P(x) = p0 + p1 * x + p2 * x^2 + ... + pn * x^n is represented by the array [ p0, p1, p2, ... , pn ]. */ export default class PolField { constructor (F) { this.F = F; let rem = F.sqrt_t; let s = F.sqrt_s; const five = this.F.add(this.F.add(this.F.two, this.F.two), this.F.one); this.w = new Array(s+1); this.wi = new Array(s+1); this.w[s] = this.F.pow(five, rem); this.wi[s] = this.F.inv(this.w[s]); let n=s-1; while (n>=0) { this.w[n] = this.F.square(this.w[n+1]); this.wi[n] = this.F.square(this.wi[n+1]); n--; } this.roots = []; /* for (let i=0; i<16; i++) { let r = this.F.one; n = 1 << i; const rootsi = new Array(n); for (let j=0; j<n; j++) { rootsi[j] = r; r = this.F.mul(r, this.w[i]); } this.roots.push(rootsi); } */ this._setRoots(15); } _setRoots(n) { if (n > this.F.sqrt_s) n = this.s; for (let i=n; (i>=0) && (!this.roots[i]); i--) { let r = this.F.one; const nroots = 1 << i; const rootsi = new Array(nroots); for (let j=0; j<nroots; j++) { rootsi[j] = r; r = this.F.mul(r, this.w[i]); } this.roots[i] = rootsi; } } add(a, b) { const m = Math.max(a.length, b.length); const res = new Array(m); for (let i=0; i<m; i++) { res[i] = this.F.add(a[i] || this.F.zero, b[i] || this.F.zero); } return this.reduce(res); } double(a) { return this.add(a,a); } sub(a, b) { const m = Math.max(a.length, b.length); const res = new Array(m); for (let i=0; i<m; i++) { res[i] = this.F.sub(a[i] || this.F.zero, b[i] || this.F.zero); } return this.reduce(res); } mulScalar(p, b) { if (this.F.eq(b, this.F.zero)) return []; if (this.F.eq(b, this.F.one)) return p; const res = new Array(p.length); for (let i=0; i<p.length; i++) { res[i] = this.F.mul(p[i], b); } return res; } mul(a, b) { if (a.length == 0) return []; if (b.length == 0) return []; if (a.length == 1) return this.mulScalar(b, a[0]); if (b.length == 1) return this.mulScalar(a, b[0]); if (b.length > a.length) { [b, a] = [a, b]; } if ((b.length <= 2) || (b.length < log2(a.length))) { return this.mulNormal(a,b); } else { return this.mulFFT(a,b); } } mulNormal(a, b) { let res = []; for (let i=0; i<b.length; i++) { res = this.add(res, this.scaleX(this.mulScalar(a, b[i]), i) ); } return res; } mulFFT(a,b) { const longestN = Math.max(a.length, b.length); const bitsResult = log2(longestN-1)+2; this._setRoots(bitsResult); const m = 1 << bitsResult; const ea = this.extend(a,m); const eb = this.extend(b,m); const ta = __fft(this, ea, bitsResult, 0, 1, false); const tb = __fft(this, eb, bitsResult, 0, 1, false); const tres = new Array(m); for (let i=0; i<m; i++) { tres[i] = this.F.mul(ta[i], tb[i]); } const res = __fft(this, tres, bitsResult, 0, 1, true); const twoinvm = this.F.inv( this.F.mulScalar(this.F.one, m) ); const resn = new Array(m); for (let i=0; i<m; i++) { resn[i] = this.F.mul(res[(m-i)%m], twoinvm); } return this.reduce(resn); } square(a) { return this.mul(a,a); } scaleX(p, n) { if (n==0) { return p; } else if (n>0) { const z = new Array(n).fill(this.F.zero); return z.concat(p); } else { if (-n >= p.length) return []; return p.slice(-n); } } eval2(p, x) { let v = this.F.zero; let ix = this.F.one; for (let i=0; i<p.length; i++) { v = this.F.add(v, this.F.mul(p[i], ix)); ix = this.F.mul(ix, x); } return v; } evaluate(p,x) { const F = this.F; if (p.length == 0) return F.zero; const m = this._next2Power(p.length); const ep = this.extend(p, m); return _eval(ep, x, 0, 1, m); function _eval(p, x, offset, step, n) { if (n==1) return p[offset]; const newX = F.square(x); const res= F.add( _eval(p, newX, offset, step << 1, n >> 1), F.mul( x, _eval(p, newX, offset+step , step << 1, n >> 1))); return res; } } lagrange(points) { let roots = [this.F.one]; for (let i=0; i<points.length; i++) { roots = this.mul(roots, [this.F.neg(points[i][0]), this.F.one]); } let sum = []; for (let i=0; i<points.length; i++) { let mpol = this.ruffini(roots, points[i][0]); const factor = this.F.mul( this.F.inv(this.evaluate(mpol, points[i][0])), points[i][1]); mpol = this.mulScalar(mpol, factor); sum = this.add(sum, mpol); } return sum; } fft(p) { if (p.length <= 1) return p; const bits = log2(p.length-1)+1; this._setRoots(bits); const m = 1 << bits; const ep = this.extend(p, m); const res = __fft(this, ep, bits, 0, 1); return res; } fft2(p) { if (p.length <= 1) return p; const bits = log2(p.length-1)+1; this._setRoots(bits); const m = 1 << bits; const ep = this.extend(p, m); __bitReverse(ep, bits); const res = __fft2(this, ep, bits); return res; } ifft(p) { if (p.length <= 1) return p; const bits = log2(p.length-1)+1; this._setRoots(bits); const m = 1 << bits; const ep = this.extend(p, m); const res = __fft(this, ep, bits, 0, 1); const twoinvm = this.F.inv( this.F.mulScalar(this.F.one, m) ); const resn = new Array(m); for (let i=0; i<m; i++) { resn[i] = this.F.mul(res[(m-i)%m], twoinvm); } return resn; } ifft2(p) { if (p.length <= 1) return p; const bits = log2(p.length-1)+1; this._setRoots(bits); const m = 1 << bits; const ep = this.extend(p, m); __bitReverse(ep, bits); const res = __fft2(this, ep, bits, 0, 1); const twoinvm = this.F.inv( this.F.mulScalar(this.F.one, m) ); const resn = new Array(m); for (let i=0; i<m; i++) { resn[i] = this.F.mul(res[(m-i)%m], twoinvm); } return resn; } _fft(pall, bits, offset, step) { const n = 1 << bits; if (n==1) { return [ pall[offset] ]; } const ndiv2 = n >> 1; const p1 = this._fft(pall, bits-1, offset, step*2); const p2 = this._fft(pall, bits-1, offset+step, step*2); const out = new Array(n); let m= this.F.one; for (let i=0; i<ndiv2; i++) { out[i] = this.F.add(p1[i], this.F.mul(m, p2[i])); out[i+ndiv2] = this.F.sub(p1[i], this.F.mul(m, p2[i])); m = this.F.mul(m, this.w[bits]); } return out; } extend(p, e) { if (e == p.length) return p; const z = new Array(e-p.length).fill(this.F.zero); return p.concat(z); } reduce(p) { if (p.length == 0) return p; if (! this.F.eq(p[p.length-1], this.F.zero) ) return p; let i=p.length-1; while( i>0 && this.F.eq(p[i], this.F.zero) ) i--; return p.slice(0, i+1); } eq(a, b) { const pa = this.reduce(a); const pb = this.reduce(b); if (pa.length != pb.length) return false; for (let i=0; i<pb.length; i++) { if (!this.F.eq(pa[i], pb[i])) return false; } return true; } ruffini(p, r) { const res = new Array(p.length-1); res[res.length-1] = p[p.length-1]; for (let i = res.length-2; i>=0; i--) { res[i] = this.F.add(this.F.mul(res[i+1], r), p[i+1]); } return res; } _next2Power(v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } toString(p) { const ap = this.normalize(p); let S = ""; for (let i=ap.length-1; i>=0; i--) { if (!this.F.eq(p[i], this.F.zero)) { if (S!="") S += " + "; S = S + p[i].toString(10); if (i>0) { S = S + "x"; if (i>1) { S = S + "^" +i; } } } } return S; } normalize(p) { const res = new Array(p.length); for (let i=0; i<p.length; i++) { res[i] = this.F.normalize(p[i]); } return res; } _reciprocal(p, bits) { const k = 1 << bits; if (k==1) { return [ this.F.inv(p[0]) ]; } const np = this.scaleX(p, -k/2); const q = this._reciprocal(np, bits-1); const a = this.scaleX(this.double(q), 3*k/2-2); const b = this.mul( this.square(q), p); return this.scaleX(this.sub(a,b), -(k-2)); } // divides x^m / v _div2(m, v) { const kbits = log2(v.length-1)+1; const k = 1 << kbits; const scaleV = k - v.length; // rec = x^(k - 2) / v* x^scaleV => // rec = x^(k-2-scaleV)/ v // // res = x^m/v = x^(m + (2*k-2 - scaleV) - (2*k-2 - scaleV)) /v => // res = rec * x^(m - (2*k-2 - scaleV)) => // res = rec * x^(m - 2*k + 2 + scaleV) const rec = this._reciprocal(this.scaleX(v, scaleV), kbits); const res = this.scaleX(rec, m - 2*k + 2 + scaleV); return res; } div(_u, _v) { if (_u.length < _v.length) return []; const kbits = log2(_v.length-1)+1; const k = 1 << kbits; const u = this.scaleX(_u, k-_v.length); const v = this.scaleX(_v, k-_v.length); const n = v.length-1; let m = u.length-1; const s = this._reciprocal(v, kbits); let t; if (m>2*n) { t = this.sub(this.scaleX([this.F.one], 2*n), this.mul(s, v)); } let q = []; let rem = u; let us, ut; let finish = false; while (!finish) { us = this.mul(rem, s); q = this.add(q, this.scaleX(us, -2*n)); if ( m > 2*n ) { ut = this.mul(rem, t); rem = this.scaleX(ut, -2*n); m = rem.length-1; } else { finish = true; } } return q; } // returns the ith nth-root of one oneRoot(n, i) { let nbits = log2(n-1)+1; let res = this.F.one; let r = i; if(i>=n) { throw new Error("Given 'i' should be lower than 'n'"); } else if (1<<nbits !== n) { throw new Error(`Internal errlr: ${n} should equal ${1<<nbits}`); } while (r>0) { if (r & 1 == 1) { res = this.F.mul(res, this.w[nbits]); } r = r >> 1; nbits --; } return res; } computeVanishingPolinomial(bits, t) { const m = 1 << bits; return this.F.sub(this.F.pow(t, m), this.F.one); } evaluateLagrangePolynomials(bits, t) { const m= 1 << bits; const tm = this.F.pow(t, m); const u= new Array(m).fill(this.F.zero); this._setRoots(bits); const omega = this.w[bits]; if (this.F.eq(tm, this.F.one)) { for (let i = 0; i < m; i++) { if (this.F.eq(this.roots[bits][0],t)) { // i.e., t equals omega^i u[i] = this.F.one; return u; } } } const z = this.F.sub(tm, this.F.one); // let l = this.F.mul(z, this.F.pow(this.F.twoinv, m)); let l = this.F.mul(z, this.F.inv(this.F.e(m))); for (let i = 0; i < m; i++) { u[i] = this.F.mul(l, this.F.inv(this.F.sub(t,this.roots[bits][i]))); l = this.F.mul(l, omega); } return u; } log2(V) { return log2(V); } } function log2( V ) { return( ( ( V & 0xFFFF0000 ) !== 0 ? ( V &= 0xFFFF0000, 16 ) : 0 ) | ( ( V & 0xFF00FF00 ) !== 0 ? ( V &= 0xFF00FF00, 8 ) : 0 ) | ( ( V & 0xF0F0F0F0 ) !== 0 ? ( V &= 0xF0F0F0F0, 4 ) : 0 ) | ( ( V & 0xCCCCCCCC ) !== 0 ? ( V &= 0xCCCCCCCC, 2 ) : 0 ) | ( ( V & 0xAAAAAAAA ) !== 0 ) ); } function __fft(PF, pall, bits, offset, step) { const n = 1 << bits; if (n==1) { return [ pall[offset] ]; } else if (n==2) { return [ PF.F.add(pall[offset], pall[offset + step]), PF.F.sub(pall[offset], pall[offset + step])]; } const ndiv2 = n >> 1; const p1 = __fft(PF, pall, bits-1, offset, step*2); const p2 = __fft(PF, pall, bits-1, offset+step, step*2); const out = new Array(n); for (let i=0; i<ndiv2; i++) { out[i] = PF.F.add(p1[i], PF.F.mul(PF.roots[bits][i], p2[i])); out[i+ndiv2] = PF.F.sub(p1[i], PF.F.mul(PF.roots[bits][i], p2[i])); } return out; } function __fft2(PF, pall, bits) { const n = 1 << bits; if (n==1) { return [ pall[0] ]; } const ndiv2 = n >> 1; const p1 = __fft2(PF, pall.slice(0, ndiv2), bits-1); const p2 = __fft2(PF, pall.slice(ndiv2), bits-1); const out = new Array(n); for (let i=0; i<ndiv2; i++) { out[i] = PF.F.add(p1[i], PF.F.mul(PF.roots[bits][i], p2[i])); out[i+ndiv2] = PF.F.sub(p1[i], PF.F.mul(PF.roots[bits][i], p2[i])); } return out; } const _revTable = []; for (let i=0; i<256; i++) { _revTable[i] = _revSlow(i, 8); } function _revSlow(idx, bits) { let res =0; let a = idx; for (let i=0; i<bits; i++) { res <<= 1; res = res | (a &1); a >>=1; } return res; } function rev(idx, bits) { return ( _revTable[idx >>> 24] | (_revTable[(idx >>> 16) & 0xFF] << 8) | (_revTable[(idx >>> 8) & 0xFF] << 16) | (_revTable[idx & 0xFF] << 24) ) >>> (32-bits); } function __bitReverse(p, bits) { for (let k=0; k<p.length; k++) { const r = rev(k, bits); if (r>k) { const tmp= p[k]; p[k] = p[r]; p[r] = tmp; } } }