Polynomial division done

This commit is contained in:
Jordi Baylina 2018-08-18 14:11:51 +02:00
parent 8bc1bb610b
commit 0270ceada6
No known key found for this signature in database
GPG Key ID: 7480C80C1BE43112
7 changed files with 699 additions and 22 deletions

66
aux/calculate2roots.js Normal file

@ -0,0 +1,66 @@
const bigInt = require("../src/bigint.js");
const ZqField = require("../src/zqfield.js");
const r = bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
const s = 28;
const nqr_to_t = bigInt("19103219067921713944291392827692070036145651957329286315305642004821462161904");
const t_minus_1_over_2 = bigInt("40770029410420498293352137776570907027550720424234931066070132305055");
const root_unity = bigInt("19103219067921713944291392827692070036145651957329286315305642004821462161904");
const t = bigInt("81540058820840996586704275553141814055101440848469862132140264610111");
const F = new ZqField(r);
function sqrt(a) {
let v = s;
let z = nqr_to_t;
let w = F.exp(a, t_minus_1_over_2);
let x = F.mul(a, w);
let b = F.mul(x, w);
// compute square root with Tonelli--Shanks
// (does not terminate if not a square!)
while (!F.equals(b, F.one))
{
let m = 0;
let b2m = b;
while (!F.equals(b2m, F.one))
{
/* invariant: b2m = b^(2^m) after entering this loop */
b2m = F.square(b2m);
m += 1;
}
let j = v-m-1;
w = z;
while (j > 0)
{
w = F.square(w);
--j;
} // w = z^2^(v-m-1)
z = F.square(w);
b = F.mul(b, z);
x = F.mul(x, w);
v = m;
}
return x;
}
const p_minus1= F.sub(r,bigInt(1));
const gen = bigInt(bigInt(5));
const twoto28= F.exp(bigInt(2), bigInt(28));
const rem = F.div(p_minus1, twoto28);
const w28 = F.exp(gen, rem);
const one = F.exp(w28, twoto28);
console.log(F.toString(w28));
console.log(w28.toString(10));
console.log(F.toString(one));

@ -0,0 +1,260 @@
/*
This library do operations on polinomials where their coefficients are in field F
The polynomial P(x) = p0 + p1 * x + p2 * x^2 + p3 * x^3, ...
is represented by the array [ p0, p1, p2, p3, ... ]
*/
const bigInt = require("./bigInt");
const ZqField = require("./zqfield");
class PolFieldZq {
constructor (q) {
this.F = new ZqField(q);
let rem = q.sub(bigInt(1));
let s = 0;
while (!rem.isOdd()) {
s ++;
rem = rem.shiftRight(1);
}
this.w = new Array(s+1);
this.wi = new Array(s+1);
this.w[s] = this.F.exp(bigInt(5), 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--;
}
}
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);
}
mulEscalar(a, b) {
if (this.F.isZero(b)) return [];
const res = new Array(a.length);
for (let i=0; i<a.length; i++) {
res[i] = this.F.mul(a[i], b);
}
return res;
}
mul(a, b) {
if (a.length == 0) return [];
if (b.length == 0) return [];
if (a.length == 1) return this.mulEscalar(b, a[0]);
if (b.length == 1) return this.mulEscalar(a, b[0]);
const longestN = Math.max(a.length, b.length);
const bitsResult = log2(longestN-1)+2;
const m = 1 << bitsResult;
const ea = this.extend(a,m);
const eb = this.extend(b,m);
const ta = this._fft(ea, bitsResult, 0, 1, false);
const tb = this._fft(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 = this._fft(tres, bitsResult, 0, 1, true);
const twoinvm = this.F.inverse(bigInt(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 {
return p.slice(-n);
}
}
div(a, b) {
throw new Error("Not Implementted");
}
eval(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;
}
lagrange(points) {
throw new Error("Not Implementted");
}
_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= bigInt(1);
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;
}
_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 +(2k-2-scaleV) -(2k-2-scaleV)) /v =>
// res = rec * x^(m - (2k-2-scaleV)) =>
// res = rec * x^(m - 2k +2 + scaleV)
const rec = this._reciprocal(this.scaleX(v, scaleV), kbits);
const res = this.scaleX(rec, m - k*2 +2+scaleV);
return res;
}
}
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 ) );
}
module.exports = PolFieldZq;

@ -3,8 +3,6 @@ const bigInt = require("big-integer");
let wBigInt;
console.log("XXX");
if (typeof(BigInt) != "undefined") {
wBigInt = BigInt;
wBigInt.one = wBigInt(1);

@ -5,46 +5,291 @@
is represented by the array [ p0, p1, p2, p3, ... ]
*/
class PolField {
constructor (F) {
this.F = F;
}
const bigInt = require("./bigInt");
const ZqField = require("./zqfield");
class PolFieldZq {
constructor (q) {
this.F = new ZqField(q);
let rem = q.sub(bigInt(1));
let s = 0;
while (!rem.isOdd()) {
s ++;
rem = rem.shiftRight(1);
}
this.w = new Array(s+1);
this.wi = new Array(s+1);
this.w[s] = this.F.exp(bigInt(5), 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--;
}
_reduce(a) {
let i = a.length-1;
while ((i>=0) && (this.F.isZero(a[i])) ) i--;
return (i < a.length-1) ? a.slice(0, i+1) : a;
}
add(a, b) {
const maxGrade = Math.max(a.length, b.length);
const res = new Array(maxGrade);
for (let i=0; i<maxGrade; i++) {
res[i] = this.F.add(a[i], b[i]);
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);
return this.reduce(res);
}
double(a) {
return this.add(a,a);
}
sub(a, b) {
throw new Error("Not Implementted");
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);
}
mulEscalar(a, b) {
if (this.F.isZero(b)) return [];
const res = new Array(a.length);
for (let i=0; i<a.length; i++) {
res[i] = this.F.mul(a[i], b);
}
return res;
}
mul(a, b) {
throw new Error("Not Implementted");
if (a.length == 0) return [];
if (b.length == 0) return [];
if (a.length == 1) return this.mulEscalar(b, a[0]);
if (b.length == 1) return this.mulEscalar(a, b[0]);
const longestN = Math.max(a.length, b.length);
const bitsResult = log2(longestN-1)+2;
const m = 1 << bitsResult;
const ea = this.extend(a,m);
const eb = this.extend(b,m);
const ta = this._fft(ea, bitsResult, 0, 1, false);
const tb = this._fft(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 = this._fft(tres, bitsResult, 0, 1, true);
const twoinvm = this.F.inverse(bigInt(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));
}
div(a, b) {
throw new Error("Not Implementted");
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);
}
}
eval(p, x) {
throw new Error("Not Implementted");
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;
}
lagrange(points) {
throw new Error("Not Implementted");
}
_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= bigInt(1);
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;
}
_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([bigInt(1)], 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;
}
}
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 ) );
}
module.exports = PolField;
module.exports = PolFieldZq;

@ -16,6 +16,8 @@ class ZqField {
this.equals = bigInt.genEquals(q);
this.affine = bigInt.genAffine(q);
this.isZero = bigInt.genIsZero(q);
this.two = this.add(this.one, this.one);
this.twoinv = this.inverse(this.two);
}
copy(a) {

@ -154,7 +154,6 @@ describe("Pairing", () => {
const g1b = bn128.G1.mulEscalar(bn128.G1.g, 30);
const g2b = bn128.G2.mulEscalar(bn128.G2.g, 25);
const pre1a = bn128.precomputeG1(g1a);
const pre2a = bn128.precomputeG2(g2a);
const pre1b = bn128.precomputeG1(g1b);

107
test/pols.js Normal file

@ -0,0 +1,107 @@
const chai = require("chai");
const bigInt = require("../src/bigint.js");
const PolField = require("../src/polfield.js");
const assert = chai.assert;
const r = bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
describe("Polinomial field", () => {
it("Should compute a multiplication", () => {
const PF = new PolField(r);
const a = [bigInt(1), bigInt(2), bigInt(3)];
const b = [bigInt(1), bigInt(2), bigInt(3)];
const res = PF.mul(a,b);
assert(PF.equals(res, [bigInt(1), bigInt(4), bigInt(10), bigInt(12), bigInt(9)]));
});
it("Should compute a multiplication 2", () => {
const PF = new PolField(r);
const a = [bigInt(5), bigInt(1)];
const b = [bigInt(-5), bigInt(1)];
const res = PF.mul(a,b);
assert(PF.equals(res, [bigInt(-25), bigInt(0), bigInt(1)]));
});
it("Should compute an addition", () => {
const PF = new PolField(r);
const a = [bigInt(5), bigInt(1)];
const b = [bigInt(-5), bigInt(1)];
const res = PF.add(a,b);
assert(PF.equals(res, [bigInt(0), bigInt(2)]));
});
it("Should compute a substraction", () => {
const PF = new PolField(r);
const a = [bigInt(5), bigInt(3), bigInt(4)];
const b = [bigInt(5), bigInt(1)];
const res = PF.sub(a,b);
assert(PF.equals(res, [bigInt(0), bigInt(2), bigInt(4)]));
});
it("Should compute reciprocal", () => {
const PF = new PolField(r);
const a = [bigInt(4), bigInt(1), bigInt(-3), bigInt(-1), bigInt(2),bigInt(1), bigInt(-1), bigInt(1)];
const res = PF._reciprocal(a, 3, 0);
assert(PF.equals(res, [bigInt(12), bigInt(15), bigInt(3), bigInt(-4), bigInt(-3), bigInt(0), bigInt(1), bigInt(1)]));
});
it("Should div2", () => {
const PF = new PolField(r);
// x^6
const a = [bigInt(0), bigInt(0), bigInt(0), bigInt(0), bigInt(0),bigInt(0), bigInt(1)];
// x^5
const b = [bigInt(0), bigInt(0), bigInt(0), bigInt(0), bigInt(0), bigInt(1)];
const res = PF._div2(6, b);
assert(PF.equals(res, [bigInt(0), bigInt(1)]));
const res2 = PF.div(a,b);
assert(PF.equals(res2, [bigInt(0), bigInt(1)]));
});
it("Should div", () => {
const PF = new PolField(r);
const a = [bigInt(1), bigInt(2), bigInt(3), bigInt(4), bigInt(5),bigInt(6), bigInt(7)];
const b = [bigInt(8), bigInt(9), bigInt(10), bigInt(11), bigInt(12), bigInt(13)];
const c = PF.mul(a,b);
const d = PF.div(c,b);
assert(PF.equals(a, d));
});
it("Should div big/small", () => {
const PF = new PolField(r);
const a = [bigInt(1), bigInt(2), bigInt(3), bigInt(4), bigInt(5),bigInt(6), bigInt(7)];
const b = [bigInt(8), bigInt(9)];
const c = PF.mul(a,b);
const d = PF.div(c,b);
assert(PF.equals(a, d));
});
it("Should div random big", () => {
const PF = new PolField(r);
const a = [];
const b = [];
for (let i=0; i<1000; i++) a.push(bigInt(Math.floor(Math.random()*100000) -500000));
for (let i=0; i<300; i++) b.push(bigInt(Math.floor(Math.random()*100000) -500000));
const c = PF.mul(a,b);
const d = PF.div(c,b);
assert(PF.equals(a, d));
}).timeout(10000000);
});