link with wasmsnark

This commit is contained in:
Jordi Baylina
2020-05-26 21:39:19 +02:00
parent efa1935ca1
commit 58cfcaad16
10 changed files with 809 additions and 88 deletions

View File

@@ -21,6 +21,7 @@ const Scalar = require("./scalar");
const F1Field = require("./f1field");
const F2Field = require("./f2field");
const F3Field = require("./f3field");
const PolField = require("./polfield");
const EC = require("./ec.js");
const buildEngine = require("./engine");
const bn128_wasm = require("wasmsnark").bn128_wasm;
@@ -69,7 +70,7 @@ class BN128 {
this.F6 = new F3Field(this.F2, this.nonResidueF6);
this.F12 = new F2Field(this.F6, this.nonResidueF6);
this.Fr = new F1Field(this.r);
this.PFr = new PolField(this.Fr);
const self = this;
@@ -85,11 +86,27 @@ class BN128 {
this.G2.batchLEMtoU = this.batchLEMtoUG2.bind(this);
this.G1.batchLEMtoC = this.batchLEMtoCG1.bind(this);
this.G2.batchLEMtoC = this.batchLEMtoCG2.bind(this);
this.G1.batchUtoLEM = this.batchUtoLEMG1.bind(this);
this.G2.batchUtoLEM = this.batchUtoLEMG2.bind(this);
this.G1.batchCtoLEM = this.batchCtoLEMG1.bind(this);
this.G2.batchCtoLEM = this.batchCtoLEMG2.bind(this);
this.G1.ifft = this.ifftG1.bind(this);
this.G1.fft = this.fftG1.bind(this);
this.G2.ifft = this.ifftG2.bind(this);
this.G2.fft = this.fftG2.bind(this);
this.Fr.batchToMontgomery = this.batchToMontgomeryFr.bind(this);
this.Fr.batchFromMontgomery = this.batchFromMontgomeryFr.bind(this);
this.Fr.fft = this.fftFr.bind(this);
this.Fr.ifft = this.ifftFr.bind(this);
this.G1.multiExpAffine = this.multiExpAffineG1.bind(this);
this.G2.multiExpAffine = this.multiExpAffineG2.bind(this);
}
async loadEngine() {
if (!engine) {
engine = await buildEngine(this, bn128_wasm, true);
engine = await buildEngine(this, bn128_wasm /* , true */ ); // Set single Trherad tot true to debug
}
}
@@ -107,27 +124,121 @@ class BN128 {
async batchLEMtoUG1(buff) {
await this.loadEngine();
const res = await engine.batchConvert("G1", "LEM", "U", buff );
// const res = await engine.batchConvert("G1", "LEM", "U", buff );
const res = await engine.batchConvert("g1m_batchLEMtoU", buff, this.F1.n8*2, this.F1.n8*2);
return res;
}
async batchLEMtoUG2(buff) {
await this.loadEngine();
const res = await engine.batchConvert("G2", "LEM", "U",buff);
// const res = await engine.batchConvert("G2", "LEM", "U",buff);
const res = await engine.batchConvert("g2m_batchLEMtoU", buff, this.F2.n8*2, this.F2.n8*2);
return res;
}
async batchLEMtoCG1(buff) {
await this.loadEngine();
const res = await engine.batchConvert("G1", "LEM", "C", buff);
// const res = await engine.batchConvert("G1", "LEM", "C", buff);
const res = await engine.batchConvert("g1m_batchLEMtoC", buff, this.F1.n8*2, this.F1.n8);
return res;
}
async batchLEMtoCG2(buff) {
await this.loadEngine();
const res = await engine.batchConvert("G2", "LEM", "C", buff);
// const res = await engine.batchConvert("G2", "LEM", "C", buff);
const res = await engine.batchConvert("g2m_batchLEMtoC", buff, this.F2.n8*2, this.F2.n8);
return res;
}
/////
async batchUtoLEMG1(buff) {
await this.loadEngine();
// const res = await engine.batchConvert("G1", "LEM", "U", buff );
const res = await engine.batchConvert("g1m_batchUtoLEM", buff, this.F1.n8*2, this.F1.n8*2);
return res;
}
async batchUtoLEMG2(buff) {
await this.loadEngine();
// const res = await engine.batchConvert("G2", "LEM", "U",buff);
const res = await engine.batchConvert("g2m_batchUtoLEM", buff, this.F2.n8*2, this.F2.n8*2);
return res;
}
async batchCtoLEMG1(buff) {
await this.loadEngine();
// const res = await engine.batchConvert("G1", "LEM", "C", buff);
const res = await engine.batchConvert("g1m_batchCtoLEM", buff, this.F1.n8, this.F1.n8*2);
return res;
}
async batchCtoLEMG2(buff) {
await this.loadEngine();
// const res = await engine.batchConvert("G2", "LEM", "C", buff);
const res = await engine.batchConvert("g2m_batchCtoLEM", buff, this.F2.n8, this.F2.n8*2);
return res;
}
async batchToMontgomeryFr(buff) {
await this.loadEngine();
const res = await engine.batchConvert("frm_batchToMontgomery", buff, this.Fr.n8, this.Fr.n8);
return res;
}
async batchFromMontgomeryFr(buff) {
await this.loadEngine();
const res = await engine.batchConvert("frm_batchFromMontgomery", buff, this.Fr.n8, this.Fr.n8);
return res;
}
async fftG1(buff, log) {
await this.loadEngine();
const res = await engine.fft("G1", buff, false, log);
return res;
}
async ifftG1(buff, log) {
await this.loadEngine();
const res = await engine.fft("G1", buff, true, log);
return res;
}
async fftG2(buff, log) {
await this.loadEngine();
const res = await engine.fft("G2", buff, false, log);
return res;
}
async ifftG2(buff, log) {
await this.loadEngine();
const res = await engine.fft("G2", buff, true, log);
return res;
}
async fftFr(buff, log) {
await this.loadEngine();
const res = await engine.fft("Fr", buff, false, log);
return res;
}
async ifftFr(buff, log) {
await this.loadEngine();
const res = await engine.fft("Fr", buff, true, log);
return res;
}
async multiExpAffineG1(buffBases, buffScalars) {
await this.loadEngine();
const res = await engine.multiExpAffine("G1", buffBases, buffScalars);
return res;
}
async multiExpAffineG2(buffBases, buffScalars) {
await this.loadEngine();
const res = await engine.multiExpAffine("G2", buffBases, buffScalars);
return res;
}
_preparePairing() {
this.loopCount = Scalar.fromString("29793968203157093288");// CONSTANT

View File

@@ -340,6 +340,17 @@ class EC {
return [x, y, this.F.one];
}
fromRprLEJM(buff, o) {
o = o || 0;
const x = this.F.fromRprLEM(buff, o);
const y = this.F.fromRprLEM(buff, o+this.F.n8);
const z = this.F.fromRprLEM(buff, o+this.F.n8*2);
if (this.F.isZero(x) && this.F.isZero(y)) {
return this.zero;
}
return [x, y, z];
}
fromRprBEM(buff, o) {
o = o || 0;
const x = this.F.fromRprBEM(buff, o);
@@ -352,7 +363,7 @@ class EC {
fromRprCompressed(buff, o) {
const F = this.F;
const v = new Uint8Array(buff, o, F.n8);
const v = new Uint8Array(buff.buffer, o, F.n8);
if (v[0] & 0x40) return this.zero;
const P = new Array(3);
@@ -377,7 +388,7 @@ class EC {
toRprCompressed(buff, o, p) {
p = this.affine(p);
const v = new Uint8Array(buff, o, this.F.n8);
const v = new Uint8Array(buff.buffer, o, this.F.n8);
if (this.isZero(p)) {
v.fill(0);
v[0] = 0x40;

View File

@@ -23,6 +23,7 @@ const MEM_SIZE = 4096; // Memory size in 64K Pakes (256Mb)
const assert = require("assert");
const thread = require("./engine_thread");
const FFT = require("./fft");
const inBrowser = (typeof window !== "undefined");
let NodeWorker;
@@ -76,11 +77,11 @@ async function buildEngine(curve, wasm, singleThread) {
if (singleThread) {
engine.code = wasm.code;
engine.taskManager = thread();
await engine.taskManager({
command: "INIT",
await engine.taskManager([{
cmd: "INIT",
init: MEM_SIZE,
code: wasm.code
});
}]);
engine.concurrency = 1;
} else {
engine.workers = [];
@@ -94,6 +95,7 @@ async function buildEngine(curve, wasm, singleThread) {
} else {
concurrency = 8;
}
engine.concurrency = concurrency;
for (let i = 0; i<concurrency; i++) {
@@ -117,12 +119,11 @@ async function buildEngine(curve, wasm, singleThread) {
const initPromises = [];
for (let i=0; i<engine.workers.length;i++) {
const copyCode = wasm.code.buffer.slice(0);
initPromises.push(engine.postAction(i, {
command: "INIT",
initPromises.push(engine.postAction(i, [{
cmd: "INIT",
init: MEM_SIZE,
code: copyCode
}, [copyCode]));
}], [copyCode]));
}
await Promise.all(initPromises);
@@ -174,7 +175,7 @@ class Engine {
queueAction(actionData, transfers) {
if (this.singleThread) {
const res = this.taskManager(actionData);
return res[0];
return res;
} else {
const d = new Deferred();
this.actionQueue.push({
@@ -294,37 +295,52 @@ class Engine {
terminate() {
for (let i=0; i<this.workers.length; i++) {
this.workers[i].postMessage({command: "TERMINATE"});
this.workers[i].postMessage([{cmd: "TERMINATE"}]);
}
}
async batchApplyKey(Gs, buff, first, inc) {
async batchApplyKey(groupName, buff, first, inc) {
const self = this;
const G = self.curve[Gs];
const G = self.curve[groupName];
const Fr = self.curve.Fr;
const sG = G.F.n8*2;
let fnName;
if (groupName == "G1") {
fnName = "g1m_batchApplyKey";
} else if (groupName == "G2") {
fnName = "g2m_batchApplyKey";
} else {
throw new Error("Invalid group: " + groupName);
}
const nPoints = Math.floor(buff.byteLength / sG);
const pointsPerChunk = Math.floor(nPoints/self.concurrency);
const opPromises = [];
const bInc = new Uint8Array(Fr.n8);
Fr.toRprLEM(bInc.buffer, 0, inc);
Fr.toRprLEM(bInc, 0, inc);
let t = Fr.e(first);
for (let i=0; i<self.concurrency; i++) {
const n = Math.min(nPoints - i, pointsPerChunk);
let n;
if (i< self.concurrency-1) {
n = pointsPerChunk;
} else {
n = nPoints - i*pointsPerChunk;
}
if (n==0) continue;
const bFirst = new Uint8Array(Fr.n8);
Fr.toRprLEM(bFirst.buffer, 0, t);
Fr.toRprLEM(bFirst, 0, t);
opPromises.push(
self.queueAction({
command: "BATCH_APPLY_KEY",
Gs: Gs,
buff: buff.slice(i*pointsPerChunk*sG, i*pointsPerChunk*sG + n*sG),
first: bFirst,
inc: bInc,
n: n
})
self.queueAction([
{cmd: "ALLOCSET", var: 0, buff: buff.slice(i*pointsPerChunk*sG, i*pointsPerChunk*sG + n*sG)},
{cmd: "ALLOCSET", var: 1, buff: bFirst},
{cmd: "ALLOCSET", var: 2, buff: bInc},
{cmd: "ALLOC", var: 3, len: n*sG},
{cmd: "CALL", fnName: fnName, params: [
{var: 0}, {val: n}, {var: 1}, {var: 2}, {var:3}
]},
{cmd: "GET", out: 0, var: 3, len: n*sG},
])
);
t = Fr.mul(t, Fr.pow(inc, n));
}
@@ -332,67 +348,358 @@ class Engine {
const result = await Promise.all(opPromises);
const outBuff = new Uint8Array(buff.byteLength);
for (let i=0; i<self.concurrency; i++) {
const n = Math.min(nPoints - i, pointsPerChunk);
if (n==0) continue;
outBuff.set(result[i], i*pointsPerChunk*sG);
let p=0;
for (let i=0; i<result.length; i++) {
outBuff.set(result[i][0], p);
p += result[i][0].byteLength;
}
return outBuff;
}
async batchConvert(Gs, fr, to, fullBuffIn) {
array2bufferG(Gs, arr) {
const self = this;
const G = self.curve[Gs];
let sGin;
if (["LEM", "U"].indexOf(fr)>=0) {
sGin = G.F.n8*2;
} else if (["C"].indexOf(fr)>=0) {
sGin = G.F.n8;
} else {
throw new Error("Invaid convertion format: "+fr);
const sG = G.F.n8*2;
const buff = new Uint8Array(sG*arr.length);
for (let i=0; i<arr.length; i++) {
G.toRprLEM(buff, i*sG, arr[i]);
}
let sGout;
if (["LEM", "U"].indexOf(fr)>=0) {
sGout = G.F.n8*2;
} else if (["C"].indexOf(fr)>=0) {
sGout = G.F.n8;
} else {
throw new Error("Invaid convertion format: "+fr);
return buff;
}
buffer2arrayG(Gs, buff) {
const self = this;
const G = self.curve[Gs];
const sG = G.F.n8*2;
const n= buff.length / sG;
const arr = new Array(n);
for (let i=0; i<n; i++) {
const P = G.fromRprLEM(buff, i*sG);
arr[i] = P;
}
const nPoints = Math.floor(fullBuffIn.byteLength / sGin);
return arr;
}
async fft(groupName, buff, inverse, log) {
const self = this;
const MAX_BITS_THREAD = 12;
const G = self.curve[groupName];
const Fr = self.curve.Fr;
const PFr = new FFT(G, self.curve.Fr, G.mulScalar.bind(G));
let sIn, sMid, sOut, fnIn2Mid, fnMid2Out, fnName, fnFFT, fnFFTJoin, fnFFTFinal;
if (groupName == "G1") {
sIn = G.F.n8*2;
sMid = G.F.n8*3;
sOut = G.F.n8*2;
fnIn2Mid = "g1m_batchToJacobian";
if (inverse) {
fnName = "g1m_ifft";
fnFFTFinal = "g1m_fftFinal";
} else {
fnName = "g1m_fft";
}
fnFFT = "g1m_fft";
fnFFTJoin = "g1m_fftJoin";
fnMid2Out = "g1m_batchToAffine";
} else if (groupName == "G2") {
sIn = G.F.n8*2;
sMid = G.F.n8*3;
sOut = G.F.n8*2;
fnIn2Mid = "g2m_batchToJacobian";
if (inverse) {
fnName = "g2m_ifft";
fnFFTFinal = "g2m_fftFinal";
} else {
fnName = "g2m_fft";
}
fnFFT = "g2m_fft";
fnFFTJoin = "g2m_fftJoin";
fnMid2Out = "g2m_batchToAffine";
} else if (groupName == "Fr") {
sIn = G.n8;
sMid = G.n8;
sOut = G.n8;
if (inverse) {
fnName = "frm_ifft";
fnFFTFinal = "frm_fftFinal";
} else {
fnName = "frm_fft";
}
fnFFT = "frm_fft";
fnFFTJoin = "frm_fftJoin";
}
let returnArray = false;
if (Array.isArray(buff)) {
buff = self.array2bufferG(groupName, buff);
returnArray = true;
}
const nPoints = buff.byteLength / sIn;
const bits = log2(nPoints);
assert( (1 << bits) == nPoints, "fft must be ultiple of 2" );
let bInv;
if (inverse) {
bInv = new Uint8Array(Fr.n8);
Fr.toRprLEM(bInv, 0, Fr.inv(Fr.e(nPoints)));
}
let buffOut;
if (nPoints <= (1 << MAX_BITS_THREAD)) {
const task = [];
task.push({cmd: "ALLOC", var: 0, len: sMid*nPoints});
task.push({cmd: "SET", var: 0, buff: buff});
if (fnIn2Mid) {
task.push({cmd: "CALL", fnName:fnIn2Mid, params: [{var:0}, {val: nPoints}, {var: 0}]});
}
task.push({cmd: "CALL", fnName:fnName, params: [{var:0}, {val: nPoints}]});
if (fnMid2Out) {
task.push({cmd: "CALL", fnName:fnMid2Out, params: [{var:0}, {val: nPoints}, {var: 0}]});
}
task.push({cmd: "GET", out: 0, var: 0, len: sOut*nPoints});
const res = await self.queueAction(task);
buffOut = res[0];
} else {
let chunks;
const pointsInChunk = 1 << MAX_BITS_THREAD;
const chunkSize = pointsInChunk * sIn;
const nChunks = nPoints / pointsInChunk;
const promises = [];
for (let i = 0; i< nChunks; i++) {
const task = [];
task.push({cmd: "ALLOC", var: 0, len: sMid*pointsInChunk});
const buffChunk = buff.slice( (pointsInChunk * i)*sIn, (pointsInChunk * (i+1))*sIn);
task.push({cmd: "SET", var: 0, buff: buffChunk});
if (fnIn2Mid) {
task.push({cmd: "CALL", fnName:fnIn2Mid, params: [{var:0}, {val: pointsInChunk}, {var: 0}]});
}
task.push({cmd: "CALL", fnName:fnFFT, params: [{var:0}, {val: pointsInChunk}]});
task.push({cmd: "GET", out:0, var: 0, len: sMid*pointsInChunk});
promises.push(self.queueAction(task).then( (r) => {
if (log) log(`fft: ${i}/${nChunks}`);
return r;
}));
}
chunks = await Promise.all(promises);
for (let i = 0; i< nChunks; i++) chunks[i] = chunks[i][0];
for (let i = MAX_BITS_THREAD; i<bits; i++) {
if (log) log(`${i}/${bits}`);
const nGroups = 1 << (bits - i -1);
const nChunksPerGroup = nChunks / nGroups;
const opPromises = [];
for (let j=0; j<nGroups; j++) {
for (let k=0; k <nChunksPerGroup/2; k++) {
const bFirst = new Uint8Array(Fr.n8);
Fr.toRprLEM(bFirst, 0, Fr.pow( PFr.w[i+1], k*pointsInChunk));
const bInc = new Uint8Array(Fr.n8);
Fr.toRprLEM(bInc, 0, PFr.w[i+1]);
const o1 = j*nChunksPerGroup + k;
const o2 = j*nChunksPerGroup + k + nChunksPerGroup/2;
const task = [];
task.push({cmd: "ALLOCSET", var: 0, buff: chunks[o1]});
task.push({cmd: "ALLOCSET", var: 1, buff: chunks[o2]});
task.push({cmd: "ALLOCSET", var: 2, buff: bFirst});
task.push({cmd: "ALLOCSET", var: 3, buff: bInc});
task.push({cmd: "CALL", fnName: fnFFTJoin, params:[
{var: 0},
{var: 1},
{val: pointsInChunk},
{var: 2},
{var: 3}
]});
if (i==bits-1) {
if (fnFFTFinal) {
task.push({cmd: "ALLOCSET", var: 4, buff: bInv});
task.push({cmd: "CALL", fnName: fnFFTFinal, params:[
{var: 0},
{val: pointsInChunk},
{var: 4},
]});
task.push({cmd: "CALL", fnName: fnFFTFinal, params:[
{var: 1},
{val: pointsInChunk},
{var: 4},
]});
}
if (fnMid2Out) {
task.push({cmd: "CALL", fnName:fnMid2Out, params: [{var:0}, {val: pointsInChunk}, {var: 0}]});
task.push({cmd: "CALL", fnName:fnMid2Out, params: [{var:1}, {val: pointsInChunk}, {var: 1}]});
}
task.push({cmd: "GET", out: 0, var: 0, len: pointsInChunk*sOut});
task.push({cmd: "GET", out: 1, var: 1, len: pointsInChunk*sOut});
} else {
task.push({cmd: "GET", out: 0, var: 0, len: pointsInChunk*sMid});
task.push({cmd: "GET", out: 1, var: 1, len: pointsInChunk*sMid});
}
opPromises.push(self.queueAction(task));
}
}
const res = await Promise.all(opPromises);
for (let j=0; j<nGroups; j++) {
for (let k=0; k <nChunksPerGroup/2; k++) {
const o1 = j*nChunksPerGroup + k;
const o2 = j*nChunksPerGroup + k + nChunksPerGroup/2;
const resChunk = res.shift();
chunks[o1] = resChunk[0];
chunks[o2] = resChunk[1];
}
}
}
buffOut = new Uint8Array(nPoints * sOut);
if (inverse) {
buffOut.set(chunks[0].slice((pointsInChunk-1)*sOut));
let p= sOut;
for (let i=nChunks-1; i>0; i--) {
buffOut.set(chunks[i], p);
p += chunkSize;
delete chunks[i]; // Liberate mem
}
buffOut.set(chunks[0].slice(0, (pointsInChunk-1)*sOut), p);
delete chunks[nChunks-1];
} else {
for (let i=0; i<nChunks; i++) {
buffOut.set(chunks[i], pointsInChunk*sOut*i);
delete chunks[i];
}
return buffOut;
}
}
if (returnArray) {
const arr = self.buffer2arrayG(groupName, buffOut);
return arr;
} else {
return buffOut;
}
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 ) );
}
}
async multiExpAffine(groupName, buffBases, buffScalars) {
const self = this;
const G = self.curve[groupName];
const sBase = G.F.n8*2;
const sScalar = self.curve.Fr.n8;
const nPoints = Math.floor(buffBases.byteLength / sBase);
assert( nPoints * sBase === buffBases.byteLength);
assert( nPoints * sScalar === buffScalars.byteLength);
const pointsPerChunk = Math.floor(nPoints/self.concurrency);
const opPromises = [];
let fnName;
if (groupName == "G1") {
fnName = "g1m_multiexpAffine";
} else if (groupName == "G2") {
fnName = "g2m_multiexpAffine";
}
for (let i=0; i<self.concurrency; i++) {
const n = Math.min(nPoints - i, pointsPerChunk);
let n;
if (i< self.concurrency-1) {
n = pointsPerChunk;
} else {
n = nPoints - i*pointsPerChunk;
}
if (n==0) continue;
const buffBasesChunk = buffBases.slice(i*pointsPerChunk*sBase, i*pointsPerChunk*sBase + n*sBase);
const buffScalarsChunk = buffScalars.slice(i*pointsPerChunk*sScalar, i*pointsPerChunk*sScalar + n*sScalar);
const task = [
{cmd: "ALLOCSET", var: 0, buff: buffBasesChunk},
{cmd: "ALLOCSET", var: 1, buff: buffScalarsChunk},
{cmd: "ALLOC", var: 2, len: G.F.n8*3},
{cmd: "CALL", fnName: fnName, params: [
{var: 0},
{var: 1},
{val: sScalar},
{val: nPoints},
{var: 2}
]},
{cmd: "GET", out: 0, var: 2, len: G.F.n8*3}
];
opPromises.push(
self.queueAction({
command: "BATCH_CONVERT",
Gs: Gs,
fr: fr,
sGin: sGin,
sGout: sGout,
to: to,
buff: fullBuffIn.slice(i*pointsPerChunk*sGin, i*pointsPerChunk*sGin + n*sGin),
n: n
})
self.queueAction(task)
);
}
const result = await Promise.all(opPromises);
const fullBuffOut = new Uint8Array(nPoints*sGout);
let res = G.zero;
for (let i=0; i<result.length; i++) {
const P = G.fromRprLEJM(result[i][0], 0);
res = G.add(res, P);
}
return res;
}
async batchConvert(fnName, buffIn, sIn, sOut) {
const self = this;
const nPoints = Math.floor(buffIn.byteLength / sIn);
assert( nPoints * sIn === buffIn.byteLength);
const pointsPerChunk = Math.floor(nPoints/self.concurrency);
const opPromises = [];
for (let i=0; i<self.concurrency; i++) {
const n = Math.min(nPoints - i, pointsPerChunk);
let n;
if (i< self.concurrency-1) {
n = pointsPerChunk;
} else {
n = nPoints - i*pointsPerChunk;
}
if (n==0) continue;
fullBuffOut.set(result[i], i*pointsPerChunk*sGout);
const buffChunk = buffIn.slice(i*pointsPerChunk*sIn, i*pointsPerChunk*sIn + n*sIn);
const task = [
{cmd: "ALLOCSET", var: 0, buff:buffChunk},
{cmd: "ALLOC", var: 1, len:sOut * n},
{cmd: "CALL", fnName: fnName, params: [
{var: 0},
{val: n},
{var: 1}
]},
{cmd: "GET", out: 0, var: 1, len:sOut * n},
];
opPromises.push(
self.queueAction(task)
);
}
const result = await Promise.all(opPromises);
const fullBuffOut = new Uint8Array(nPoints*sOut);
let p =0;
for (let i=0; i<result.length; i++) {
fullBuffOut.set(result[i][0], p);
p+=result[i][0].byteLength;
}
return fullBuffOut;
}
}

View File

@@ -15,15 +15,15 @@ module.exports = function thread(self) {
data = e;
}
if (data.command == "INIT") {
init(data).then(function() {
if (data[0].cmd == "INIT") {
init(data[0]).then(function() {
self.postMessage(data.result);
});
} else if (data.command == "TERMINATE") {
} else if (data[0].cmd == "TERMINATE") {
process.exit();
} else {
const res = taskManager(data);
self.postMessage(res[0], [res[1]]);
const res = runTask(data);
self.postMessage(res);
}
};
}
@@ -68,6 +68,51 @@ module.exports = function thread(self) {
u8.set(new Uint8Array(buffer), pointer);
}
function runTask(task) {
const self=this;
if (task[0].cmd == "INIT") {
return init(task[0]);
}
const ctx = {
vars: [],
out: []
};
const oldAlloc = u32[0];
for (let i=0; i<task.length; i++) {
switch (task[i].cmd) {
case "ALLOCSET":
ctx.vars[task[i].var] = allocBuffer(task[i].buff);
break;
case "ALLOC":
ctx.vars[task[i].var] = alloc(task[i].len);
break;
case "SET":
setBuffer(ctx.vars[task[i].var], task[i].buff);
break;
case "CALL": {
const params = [];
for (let j=0; j<task[i].params.length; j++) {
const p = task[i].params[j];
if (typeof p.var !== "undefined") {
params.push(ctx.vars[p.var]);
} else if (typeof p.val != "undefined") {
params.push(p.val);
}
}
instance.exports[task[i].fnName](...params);
break;
}
case "GET":
ctx.out[task[i].out] = getBuffer(ctx.vars[task[i].var], task[i].len).slice();
break;
default:
throw new Error("Invalid cmd");
}
}
u32[0] = oldAlloc;
return ctx.out;
}
function batchApplyKey(task) {
const outBuffLen = task.buff.byteLength;
const oldAlloc = u32[0];
@@ -83,12 +128,25 @@ module.exports = function thread(self) {
const outBuff = getBuffer(pBuffOut, outBuffLen).slice();
u32[0] = oldAlloc;
return [ outBuff, outBuff];
return [ outBuff, outBuff.buffer];
}
function batchConvert(task) {
const oldAlloc = u32[0];
const outBuffLen = task.n*task.sOut;
const pBufIn = allocBuffer(task.buff);
const pBuffOut = alloc(outBuffLen);
instance.exports[task.fnName](pBufIn, task.n, pBuffOut);
const outBuff = getBuffer(pBuffOut, outBuffLen).slice();
u32[0] = oldAlloc;
return [ outBuff, outBuff.buffer];
}
function batchConvertOld(task) {
const oldAlloc = u32[0];
const outBuffLen = task.n*task.sGin;
const pBufIn = allocBuffer(task.buff);
const pBuffOut = alloc(outBuffLen);
@@ -122,9 +180,62 @@ module.exports = function thread(self) {
const outBuff = getBuffer(pBuffOut, outBuffLen).slice();
u32[0] = oldAlloc;
return [ outBuff, outBuff];
return [ outBuff, outBuff.buffer];
}
function fft(task) {
const oldAlloc = u32[0];
const maxBuffLen = task.n*task.sGin*3/2;
const pBuff = alloc(maxBuffLen);
setBuffer(pBuff, task.buff);
if (task.Gs == "G1") {
instance.exports.g1m_batchToJacobian(pBuff, task.n, pBuff);
if (task.inverse) {
instance.exports.g1m_ifft(pBuff, task.n);
} else {
instance.exports.g1m_fft(pBuff, task.n);
}
instance.exports.g1m_batchToAffine(pBuff, task.n, pBuff);
} else if (task.Gs == "G2") {
instance.exports.g2m_batchToJacobian(pBuff, task.n, pBuff);
if (task.inverse) {
instance.exports.g2m_ifft(pBuff, task.n);
} else {
instance.exports.g2m_fft(pBuff, task.n);
}
instance.exports.g2m_batchToAffine(pBuff, task.n, pBuff);
} else if (task.Gs == "Fr") {
if (task.inverse) {
instance.exports.frm_ifft(pBuff, task.n);
} else {
instance.exports.frm_fft(pBuff, task.n);
}
} else {
throw new Error("Invalid group: "+task.gs);
}
const outBuff = getBuffer(pBuff, task.n*task.sGin).slice();
u32[0] = oldAlloc;
return [ outBuff, outBuff.buffer];
}
function multiexp(task) {
const oldAlloc = u32[0];
const pBuffBases = allocBuffer(task.buffBases);
const pBuffScalars = allocBuffer(task.buffScalars);
const pOut = alloc(task.sOut);
instance.exports[task.fnName](pBuffBases, pBuffScalars, task.sScalar, task.n, pOut);
const outBuff = getBuffer(pOut, task.sOut).slice();
u32[0] = oldAlloc;
return [ outBuff, outBuff.buffer];
}
function taskManager(task) {
if (task.command == "INIT") {
return init(task);
@@ -132,11 +243,15 @@ module.exports = function thread(self) {
return batchApplyKey(task);
} else if (task.command == "BATCH_CONVERT") {
return batchConvert(task);
} else if (task.command == "FFT") {
return fft(task);
} else if (task.command == "MULTIEXP") {
return multiexp(task);
} else {
console.log("Invalid task", task);
throw new Error("Invalid task");
}
}
return taskManager;
return runTask;
};

146
src/fft.js Normal file
View File

@@ -0,0 +1,146 @@
/*
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 assert = require("assert");
class FFT {
constructor (G, F, opMulGF) {
this.F = F;
this.G = G;
this.opMulGF = opMulGF;
let rem = F.sqrt_t;
let s = F.sqrt_s;
let nqr = F.one;
while (F.eq(F.pow(nqr, F.half), F.one)) nqr = F.add(nqr, F.one);
this.w = new Array(s+1);
this.wi = new Array(s+1);
this.w[s] = this.F.pow(nqr, 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) {
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;
}
}
fft(p) {
if (p.length <= 1) return p;
const bits = log2(p.length-1)+1;
this._setRoots(bits);
const m = 1 << bits;
assert (p.length == m);
const res = __fft(this, p, 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;
assert (p.length == m);
const res = __ffti(this, p, 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.opMulGF(res[(m-i)%m], twoinvm);
}
return resn;
}
}
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.G.add(pall[offset], pall[offset + step]),
PF.G.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.G.add(p1[i], PF.opMulGF(p2[i], PF.roots[bits][i]));
out[i+ndiv2] = PF.G.sub(p1[i], PF.opMulGF(p2[i], PF.roots[bits][i]));
}
return out;
}
module.exports = FFT;

View File

@@ -71,7 +71,6 @@ class PolField {
rootsi[j] = r;
r = this.F.mul(r, this.w[i]);
}
this.roots[i] = rootsi;
}
}
@@ -262,7 +261,6 @@ class PolField {
}
_fft(pall, bits, offset, step) {
const n = 1 << bits;
@@ -523,5 +521,4 @@ function __fft(PF, pall, bits, offset, step) {
return out;
}
module.exports = PolField;

View File

@@ -14,7 +14,7 @@ if (supportsNativeBigInt) {
// Returns a buffer with Little Endian Representation
Scalar.__proto__.toRprLE = function rprBE(buff, o, e, n8) {
const s = "0000000" + e.toString(16);
const v = new Uint32Array(buff, o, n8/4);
const v = new Uint32Array(buff.buffer, o, n8/4);
const l = (((s.length-7)*4 - 1) >> 5)+1; // Number of 32bit words;
for (let i=0; i<l; i++) v[i] = parseInt(s.substring(s.length-8*i-8, s.length-8*i), 16);
for (let i=l; i<v.length; i++) v[i] = 0;
@@ -23,7 +23,7 @@ Scalar.__proto__.toRprLE = function rprBE(buff, o, e, n8) {
// Returns a buffer with Big Endian Representation
Scalar.__proto__.toRprBE = function rprLEM(buff, o, e, n8) {
const s = "0000000" + e.toString(16);
const v = new DataView(buff, o, n8);
const v = new DataView(buff.buffer, o, n8);
const l = (((s.length-7)*4 - 1) >> 5)+1; // Number of 32bit words;
for (let i=0; i<l; i++) v.setUint32(n8-i*4 -4, parseInt(s.substring(s.length-8*i-8, s.length-8*i), 16), false);
for (let i=0; i<n8/4-l; i++) v[i] = 0;
@@ -32,7 +32,7 @@ Scalar.__proto__.toRprBE = function rprLEM(buff, o, e, n8) {
// Pases a buffer with Little Endian Representation
Scalar.__proto__.fromRprLE = function rprLEM(buff, o, n8) {
n8 = n8 || buff.byteLength;
const v = new Uint32Array(buff, o, n8/4);
const v = new Uint32Array(buff.buffer, o, n8/4);
const a = new Array(n8/4);
v.forEach( (ch,i) => a[a.length-i-1] = ch.toString(16).padStart(8,"0") );
return Scalar.fromString(a.join(""), 16);
@@ -41,7 +41,7 @@ Scalar.__proto__.fromRprLE = function rprLEM(buff, o, n8) {
// Pases a buffer with Big Endian Representation
Scalar.__proto__.fromRprBE = function rprLEM(buff, o, n8) {
n8 = n8 || buff.byteLength;
const v = new DataView(buff, o, n8);
const v = new DataView(buff.buffer, o, n8);
const a = new Array(n8/4);
for (let i=0; i<n8/4; i++) {
a[i] = v.getUint32(i*4, false).toString(16).padStart(8, "0");

View File

@@ -8,9 +8,10 @@ module.exports.stringifyBigInts = function stringifyBigInts(o) {
return o.map(stringifyBigInts);
} else if (typeof o == "object") {
const res = {};
for (let k in o) {
const keys = Object.keys(o);
keys.forEach( (k) => {
res[k] = stringifyBigInts(o[k]);
}
});
return res;
} else {
return o;
@@ -24,9 +25,10 @@ module.exports.unstringifyBigInts = function unstringifyBigInts(o) {
return o.map(unstringifyBigInts);
} else if (typeof o == "object") {
const res = {};
for (let k in o) {
const keys = Object.keys(o);
keys.forEach( (k) => {
res[k] = unstringifyBigInts(o[k]);
}
});
return res;
} else {
return o;

View File

@@ -8,9 +8,10 @@ module.exports.stringifyBigInts = function stringifyBigInts(o) {
return o.map(stringifyBigInts);
} else if (typeof o == "object") {
const res = {};
for (let k in o) {
const keys = Object.keys(o);
keys.forEach( (k) => {
res[k] = stringifyBigInts(o[k]);
}
});
return res;
} else {
return o;
@@ -24,9 +25,10 @@ module.exports.unstringifyBigInts = function unstringifyBigInts(o) {
return o.map(unstringifyBigInts);
} else if (typeof o == "object") {
const res = {};
for (let k in o) {
const keys = Object.keys(o);
keys.forEach( (k) => {
res[k] = unstringifyBigInts(o[k]);
}
});
return res;
} else {
return o;

30
test/bn128.js Normal file
View File

@@ -0,0 +1,30 @@
const assert = require("assert");
describe("bn128 tester", function () {
this.timeout(100000);
it("It shoud do an inverse FFT in G1", async () => {
const bn128 = require("../index").bn128;
const Fr = bn128.Fr;
const G1 = bn128.G1;
const a = [];
for (let i=0; i<8; i++) a[i] = Fr.e(i+1);
const aG_expected = [];
for (let i=0; i<8; i++) aG_expected[i] = G1.mulScalar(G1.g, a[i]);
const A = bn128.PFr.fft(a);
const AG = [];
for (let i=0; i<8; i++) AG[i] = G1.mulScalar(G1.g, A[i]);
const aG_calculated = await G1.ifft(AG);
for (let i=0; i<8; i++) {
assert(G1.eq(aG_calculated[i], aG_expected[i]));
}
});
});