|
|
/* 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 ]. */
const bigInt = require("./bigint.js"); const assert = require("assert");
class PolField { constructor (F) { this.F = F;
const q = this.F.q; let rem = q.sub(bigInt(1)); let s = 0; while (!rem.isOdd()) { s ++; rem = rem.shr(1); }
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.exp(five, rem); this.wi[s] = this.F.inverse(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) { 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.isZero(b)) return []; if (this.F.equals(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 = []; b = this.affine(b); 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.inverse( 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(this.affine(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; }
eval(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.inverse(this.eval(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; }
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.inverse( 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.isZero(p[p.length-1]) ) return p; let i=p.length-1; while( i>0 && this.F.isZero(p[i]) ) i--; return p.slice(0, i+1); }
affine(p) { for (let i=0; i<p.length; i++) { p[i] = this.F.affine(p[i]); } return p; }
equals(a, b) { const pa = this.reduce(this.affine(a)); const pb = this.reduce(this.affine(b));
if (pa.length != pb.length) return false; for (let i=0; i<pb.length; i++) { if (!this.F.equals(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.affine(p); let S = ""; for (let i=ap.length-1; i>=0; i--) { if (!this.F.isZero(p[i])) { if (S!="") S += " + "; S = S + p[i].toString(10); if (i>0) { S = S + "x"; if (i>1) { S = S + "^" +i; } } } } return S; }
_reciprocal(p, bits) { const k = 1 << bits; if (k==1) { return [ this.F.inverse(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;
assert(i<n); assert(1<<nbits === n);
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.exp(t, bigInt(m)), this.F.one); }
evaluateLagrangePolynomials(bits, t) { const m= 1 << bits; const tm = this.F.exp(t, bigInt(m)); const u= new Array(m).fill(this.F.zero); this._setRoots(bits); const omega = this.w[bits];
if (this.F.equals(tm, this.F.one)) { for (let i = 0; i < m; i++) { if (this.F.equals(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.exp(this.F.twoinv, m));
let l = this.F.mul(z, this.F.inverse(bigInt(m))); for (let i = 0; i < m; i++) { u[i] = this.F.mul(l, this.F.inverse(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; }
module.exports = PolField;
|