|
| 1 | +/* |
| 2 | + This library do operations on polinomials where their coefficients are in field F |
| 3 | +
|
| 4 | + The polynomial P(x) = p0 + p1 * x + p2 * x^2 + p3 * x^3, ... |
| 5 | + is represented by the array [ p0, p1, p2, p3, ... ] |
| 6 | + */ |
| 7 | + |
| 8 | +const bigInt = require("./bigInt"); |
| 9 | +const ZqField = require("./zqfield"); |
| 10 | + |
| 11 | +class PolFieldZq { |
| 12 | + constructor (q) { |
| 13 | + this.F = new ZqField(q); |
| 14 | + |
| 15 | + let rem = q.sub(bigInt(1)); |
| 16 | + let s = 0; |
| 17 | + while (!rem.isOdd()) { |
| 18 | + s ++; |
| 19 | + rem = rem.shiftRight(1); |
| 20 | + } |
| 21 | + |
| 22 | + this.w = new Array(s+1); |
| 23 | + this.wi = new Array(s+1); |
| 24 | + this.w[s] = this.F.exp(bigInt(5), rem); |
| 25 | + this.wi[s] = this.F.inverse(this.w[s]); |
| 26 | + |
| 27 | + let n=s-1; |
| 28 | + while (n>=0) { |
| 29 | + this.w[n] = this.F.square(this.w[n+1]); |
| 30 | + this.wi[n] = this.F.square(this.wi[n+1]); |
| 31 | + n--; |
| 32 | + } |
| 33 | + |
| 34 | + } |
| 35 | + |
| 36 | + add(a, b) { |
| 37 | + const m = Math.max(a.length, b.length); |
| 38 | + const res = new Array(m); |
| 39 | + for (let i=0; i<m; i++) { |
| 40 | + res[i] = this.F.add(a[i] || this.F.zero, b[i] || this.F.zero); |
| 41 | + } |
| 42 | + return this.reduce(res); |
| 43 | + } |
| 44 | + |
| 45 | + double(a) { |
| 46 | + return this.add(a,a); |
| 47 | + } |
| 48 | + |
| 49 | + sub(a, b) { |
| 50 | + const m = Math.max(a.length, b.length); |
| 51 | + const res = new Array(m); |
| 52 | + for (let i=0; i<m; i++) { |
| 53 | + res[i] = this.F.sub(a[i] || this.F.zero, b[i] || this.F.zero); |
| 54 | + } |
| 55 | + return this.reduce(res); |
| 56 | + } |
| 57 | + |
| 58 | + mulEscalar(a, b) { |
| 59 | + if (this.F.isZero(b)) return []; |
| 60 | + const res = new Array(a.length); |
| 61 | + for (let i=0; i<a.length; i++) { |
| 62 | + res[i] = this.F.mul(a[i], b); |
| 63 | + } |
| 64 | + return res; |
| 65 | + } |
| 66 | + |
| 67 | + mul(a, b) { |
| 68 | + if (a.length == 0) return []; |
| 69 | + if (b.length == 0) return []; |
| 70 | + if (a.length == 1) return this.mulEscalar(b, a[0]); |
| 71 | + if (b.length == 1) return this.mulEscalar(a, b[0]); |
| 72 | + |
| 73 | + const longestN = Math.max(a.length, b.length); |
| 74 | + const bitsResult = log2(longestN-1)+2; |
| 75 | + const m = 1 << bitsResult; |
| 76 | + const ea = this.extend(a,m); |
| 77 | + const eb = this.extend(b,m); |
| 78 | + |
| 79 | + const ta = this._fft(ea, bitsResult, 0, 1, false); |
| 80 | + const tb = this._fft(eb, bitsResult, 0, 1, false); |
| 81 | + |
| 82 | + const tres = new Array(m); |
| 83 | + |
| 84 | + for (let i=0; i<m; i++) { |
| 85 | + tres[i] = this.F.mul(ta[i], tb[i]); |
| 86 | + } |
| 87 | + |
| 88 | + const res = this._fft(tres, bitsResult, 0, 1, true); |
| 89 | + |
| 90 | + const twoinvm = this.F.inverse(bigInt(m)); |
| 91 | + const resn = new Array(m); |
| 92 | + for (let i=0; i<m; i++) { |
| 93 | + resn[i] = this.F.mul(res[(m-i)%m], twoinvm); |
| 94 | + } |
| 95 | + |
| 96 | + return this.reduce(this.affine(resn)); |
| 97 | + } |
| 98 | + |
| 99 | + square(a) { |
| 100 | + return this.mul(a,a); |
| 101 | + } |
| 102 | + |
| 103 | + scaleX(p, n) { |
| 104 | + if (n==0) { |
| 105 | + return p; |
| 106 | + } else if (n>0) { |
| 107 | + const z = new Array(n).fill(this.F.zero); |
| 108 | + return z.concat(p); |
| 109 | + } else { |
| 110 | + return p.slice(-n); |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + div(a, b) { |
| 115 | + throw new Error("Not Implementted"); |
| 116 | + } |
| 117 | + |
| 118 | + eval(p, x) { |
| 119 | + let v = this.F.zero; |
| 120 | + let ix = this.F.one; |
| 121 | + for (let i=0; i<p.length; i++) { |
| 122 | + v = this.F.add(v, this.F.mul(p[i], ix)); |
| 123 | + ix = this.F.mul(ix, x); |
| 124 | + } |
| 125 | + return v; |
| 126 | + } |
| 127 | + |
| 128 | + lagrange(points) { |
| 129 | + throw new Error("Not Implementted"); |
| 130 | + } |
| 131 | + |
| 132 | + _fft(pall, bits, offset, step) { |
| 133 | + |
| 134 | + const n = 1 << bits; |
| 135 | + if (n==1) { |
| 136 | + return [ pall[offset] ]; |
| 137 | + } |
| 138 | + |
| 139 | + const ndiv2 = n >> 1; |
| 140 | + const p1 = this._fft(pall, bits-1, offset, step*2); |
| 141 | + const p2 = this._fft(pall, bits-1, offset+step, step*2); |
| 142 | + |
| 143 | + const out = new Array(n); |
| 144 | + |
| 145 | + let m= bigInt(1); |
| 146 | + for (let i=0; i<ndiv2; i++) { |
| 147 | + out[i] = this.F.add(p1[i], this.F.mul(m, p2[i])); |
| 148 | + out[i+ndiv2] = this.F.sub(p1[i], this.F.mul(m, p2[i])); |
| 149 | + m = this.F.mul(m, this.w[bits]); |
| 150 | + } |
| 151 | + |
| 152 | + return out; |
| 153 | + } |
| 154 | + |
| 155 | + extend(p, e) { |
| 156 | + if (e == p.length) return p; |
| 157 | + const z = new Array(e-p.length).fill(this.F.zero); |
| 158 | + |
| 159 | + return p.concat(z); |
| 160 | + } |
| 161 | + |
| 162 | + reduce(p) { |
| 163 | + if (p.length == 0) return p; |
| 164 | + if (! this.F.isZero(p[p.length-1]) ) return p; |
| 165 | + let i=p.length-1; |
| 166 | + while( i>0 && this.F.isZero(p[i]) ) i--; |
| 167 | + return p.slice(0, i+1); |
| 168 | + } |
| 169 | + |
| 170 | + affine(p) { |
| 171 | + for (let i=0; i<p.length; i++) { |
| 172 | + p[i] = this.F.affine(p[i]); |
| 173 | + } |
| 174 | + return p; |
| 175 | + } |
| 176 | + |
| 177 | + equals(a, b) { |
| 178 | + const pa = this.reduce(this.affine(a)); |
| 179 | + const pb = this.reduce(this.affine(b)); |
| 180 | + |
| 181 | + if (pa.length != pb.length) return false; |
| 182 | + for (let i=0; i<pb.length; i++) { |
| 183 | + if (!this.F.equals(pa[i], pb[i])) return false; |
| 184 | + } |
| 185 | + |
| 186 | + return true; |
| 187 | + } |
| 188 | + |
| 189 | + _next2Power(v) { |
| 190 | + v--; |
| 191 | + v |= v >> 1; |
| 192 | + v |= v >> 2; |
| 193 | + v |= v >> 4; |
| 194 | + v |= v >> 8; |
| 195 | + v |= v >> 16; |
| 196 | + v++; |
| 197 | + return v; |
| 198 | + } |
| 199 | + |
| 200 | + toString(p) { |
| 201 | + const ap = this.affine(p); |
| 202 | + let S = ""; |
| 203 | + for (let i=ap.length-1; i>=0; i--) { |
| 204 | + if (!this.F.isZero(p[i])) { |
| 205 | + if (S!="") S += " + "; |
| 206 | + S = S + p[i].toString(10); |
| 207 | + if (i>0) { |
| 208 | + S = S + "x"; |
| 209 | + if (i>1) { |
| 210 | + S = S + "^" +i; |
| 211 | + } |
| 212 | + } |
| 213 | + } |
| 214 | + } |
| 215 | + return S; |
| 216 | + } |
| 217 | + |
| 218 | + |
| 219 | + _reciprocal(p, bits) { |
| 220 | + const k = 1 << bits; |
| 221 | + if (k==1) { |
| 222 | + return [ this.F.inverse(p[0]) ]; |
| 223 | + } |
| 224 | + const np = this.scaleX(p, -k/2); |
| 225 | + const q = this._reciprocal(np, bits-1); |
| 226 | + const a = this.scaleX(this.double(q), 3*k/2-2); |
| 227 | + const b = this.mul( this.square(q), p); |
| 228 | + |
| 229 | + return this.scaleX(this.sub(a,b), -(k-2)); |
| 230 | + } |
| 231 | + |
| 232 | + // divides x^m / v |
| 233 | + _div2(m, v) { |
| 234 | + const kbits = log2(v.length-1)+1; |
| 235 | + const k = 1 << kbits; |
| 236 | + |
| 237 | + const scaleV = k - v.length; |
| 238 | + |
| 239 | + // rec = x^(k - 2) / v* x^scaleV => |
| 240 | + // rec = x^(k-2-scaleV)/ v |
| 241 | + // |
| 242 | + // res = x^m/v = x^(m +(2k-2-scaleV) -(2k-2-scaleV)) /v => |
| 243 | + // res = rec * x^(m - (2k-2-scaleV)) => |
| 244 | + // res = rec * x^(m - 2k +2 + scaleV) |
| 245 | + |
| 246 | + const rec = this._reciprocal(this.scaleX(v, scaleV), kbits); |
| 247 | + const res = this.scaleX(rec, m - k*2 +2+scaleV); |
| 248 | + |
| 249 | + return res; |
| 250 | + |
| 251 | + } |
| 252 | + |
| 253 | +} |
| 254 | + |
| 255 | +function log2( V ) |
| 256 | +{ |
| 257 | + 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 ) ); |
| 258 | +} |
| 259 | + |
| 260 | +module.exports = PolFieldZq; |
0 commit comments