diff --git a/index.js b/index.js index 0cf4e31..efc81fd 100644 --- a/index.js +++ b/index.js @@ -12,5 +12,6 @@ exports.EC = require("./src/ec"); exports.bn128 = require("./src/bn128.js"); exports.utils = require("./src/utils"); +exports.ChaCha = require("./src/chacha"); diff --git a/package-lock.json b/package-lock.json index 91586ae..53c0444 100644 --- a/package-lock.json +++ b/package-lock.json @@ -124,6 +124,30 @@ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==" }, + "blake2b": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.3.tgz", + "integrity": "sha512-pkDss4xFVbMb4270aCyGD3qLv92314Et+FsKzilCLxDz5DuZ2/1g3w4nmBbu6nKApPspnjG7JcwTjGZnduB1yg==", + "dev": true, + "requires": { + "blake2b-wasm": "^1.1.0", + "nanoassert": "^1.0.0" + } + }, + "blake2b-wasm": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/blake2b-wasm/-/blake2b-wasm-1.1.7.tgz", + "integrity": "sha512-oFIHvXhlz/DUgF0kq5B1CqxIDjIJwh9iDeUUGQUcvgiGz7Wdw03McEO7CfLBy7QKGdsydcMCgO9jFNBAFCtFcA==", + "dev": true, + "requires": { + "nanoassert": "^1.0.0" + } + }, + "blakejs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz", + "integrity": "sha1-ad+S75U6qIylGjLfarHFShVfx6U=" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -772,6 +796,12 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nanoassert": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", + "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=", + "dev": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -1128,6 +1158,15 @@ "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", "dev": true }, + "wasmsnark": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/wasmsnark/-/wasmsnark-0.0.10.tgz", + "integrity": "sha512-ARrJWhxvnBJXMERwBcnEnO5ByLwYhJZr1xwac9dl61SN7+1eOmG7Od3SJL1GzU6zaf86b9wza20y1d5ThCecLw==", + "requires": { + "big-integer": "^1.6.42", + "blakejs": "^1.1.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 017e6c5..ae7ca92 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,11 @@ }, "homepage": "https://github.com/iden3/ffjs#readme", "dependencies": { - "big-integer": "^1.6.48" + "big-integer": "^1.6.48", + "wasmsnark": "0.0.10" }, "devDependencies": { + "blake2b": "^2.1.3", "chai": "^4.2.0", "eslint": "^6.8.0" } diff --git a/src/bn128.js b/src/bn128.js index ebf58f9..0a23325 100644 --- a/src/bn128.js +++ b/src/bn128.js @@ -22,11 +22,18 @@ const F1Field = require("./f1field"); const F2Field = require("./f2field"); const F3Field = require("./f3field"); const EC = require("./ec.js"); +const buildEngine = require("./engine"); +const bn128_wasm = require("wasmsnark").bn128_wasm; + + +let engine = null; + class BN128 { constructor() { + this.name = "bn128"; this.q = Scalar.fromString("21888242871839275222246405745257275088696311157297823662689037894645226208583"); this.r = Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617"); @@ -53,6 +60,10 @@ class BN128 { this.G1 = new EC(this.F1, this.g1); this.G2 = new EC(this.F2, this.g2); + this.G1.b = this.F1.e(3); + this.G2.b = this.F2.div([this.F1.e(3),this.F1.e(0)], [this.F1.e(9), this.F1.e(1)]); + this.G2.cofactor = Scalar.e("0x30644e72e131a029b85045b68181585e06ceecda572a2489345f2299c0f9fa8d"); + this.nonResidueF6 = [ this.F1.e("9"), this.F1.e("1") ]; this.F6 = new F3Field(this.F2, this.nonResidueF6); @@ -68,6 +79,54 @@ class BN128 { this._preparePairing(); + this.G1.batchApplyKey = this.batchApplyKeyG1.bind(this); + this.G2.batchApplyKey = this.batchApplyKeyG2.bind(this); + this.G1.batchLEMtoU = this.batchLEMtoUG1.bind(this); + this.G2.batchLEMtoU = this.batchLEMtoUG2.bind(this); + this.G1.batchLEMtoC = this.batchLEMtoCG1.bind(this); + this.G2.batchLEMtoC = this.batchLEMtoCG2.bind(this); + } + + async loadEngine() { + if (!engine) { + engine = await buildEngine(this, bn128_wasm, true); + } + } + + async batchApplyKeyG1(buff, first, inc) { + await this.loadEngine(); + const res = await engine.batchApplyKey("G1", buff, first, inc); + return res; + } + + async batchApplyKeyG2(buff, first, inc) { + await this.loadEngine(); + const res = await engine.batchApplyKey("G2", buff, first, inc); + return res; + } + + async batchLEMtoUG1(buff) { + await this.loadEngine(); + const res = await engine.batchConvert("G1", "LEM", "U", buff ); + return res; + } + + async batchLEMtoUG2(buff) { + await this.loadEngine(); + const res = await engine.batchConvert("G2", "LEM", "U",buff); + return res; + } + + async batchLEMtoCG1(buff) { + await this.loadEngine(); + const res = await engine.batchConvert("G1", "LEM", "C", buff); + return res; + } + + async batchLEMtoCG2(buff) { + await this.loadEngine(); + const res = await engine.batchConvert("G2", "LEM", "C", buff); + return res; } _preparePairing() { @@ -253,7 +312,7 @@ class BN128 { finalExponentiation(elt) { // TODO: There is an optimization in FF - const res = this.F12.exp(elt,this.final_exponent); + const res = this.F12.pow(elt,this.final_exponent); return res; } @@ -307,7 +366,7 @@ class BN128 { const D = this.F2.sub( X1, this.F2.mul(x2,Z1) ); // D = X1 - X2*Z1 -// console.log("Y: "+ A[0].affine(this.q).toString(16)); + // console.log("Y: "+ A[0].affine(this.q).toString(16)); const E = this.F2.sub( Y1, this.F2.mul(y2,Z1) ); // E = Y1 - Y2*Z1 const F = this.F2.square(D); // F = D^2 @@ -342,14 +401,14 @@ class BN128 { _mul_by_024(a, ell_0, ell_VW, ell_VV) { // Old implementation -/* + /* const b = [ [ell_0, this.F2.zero, ell_VV], [this.F2.zero, ell_VW, this.F2.zero] ]; return this.F12.mul(a,b); -*/ + */ // This is a new implementation, // But it does not look worthy diff --git a/src/chacha.js b/src/chacha.js new file mode 100644 index 0000000..602fad9 --- /dev/null +++ b/src/chacha.js @@ -0,0 +1,96 @@ + + +const Scalar = require("./scalar"); + + +function quarterRound(st, a, b, c, d) { + + st[a] = (st[a] + st[b]) >>> 0; + st[d] = (st[d] ^ st[a]) >>> 0; + st[d] = ((st[d] << 16) | ((st[d]>>>16) & 0xFFFF)) >>> 0; + + st[c] = (st[c] + st[d]) >>> 0; + st[b] = (st[b] ^ st[c]) >>> 0; + st[b] = ((st[b] << 12) | ((st[b]>>>20) & 0xFFF)) >>> 0; + + st[a] = (st[a] + st[b]) >>> 0; + st[d] = (st[d] ^ st[a]) >>> 0; + st[d] = ((st[d] << 8) | ((st[d]>>>24) & 0xFF)) >>> 0; + + st[c] = (st[c] + st[d]) >>> 0; + st[b] = (st[b] ^ st[c]) >>> 0; + st[b] = ((st[b] << 7) | ((st[b]>>>25) & 0x7F)) >>> 0; +} + +function doubleRound(st) { + quarterRound(st, 0, 4, 8,12); + quarterRound(st, 1, 5, 9,13); + quarterRound(st, 2, 6,10,14); + quarterRound(st, 3, 7,11,15); + + quarterRound(st, 0, 5,10,15); + quarterRound(st, 1, 6,11,12); + quarterRound(st, 2, 7, 8,13); + quarterRound(st, 3, 4, 9,14); +} + +module.exports = class ChaCha { + + constructor(seed) { + seed = seed || [0,0,0,0,0,0,0,0]; + this.state = [ + 0x61707865, + 0x3320646E, + 0x79622D32, + 0x6B206574, + seed[0], + seed[1], + seed[2], + seed[3], + seed[4], + seed[5], + seed[6], + seed[7], + 0, + 0, + 0, + 0 + ]; + this.idx = 16; + this.buff = new Array(16); + } + + nextU32() { + if (this.idx == 16) this.update(); + return this.buff[this.idx++]; + } + + nextU64() { + return Scalar.add(Scalar.mul(this.nextU32(), 0x100000000), this.nextU32()); + } + + nextBool() { + return (this.nextU32() & 1) == 1; + } + + update() { + // Copy the state + for (let i=0; i<16; i++) this.buff[i] = this.state[i]; + + // Apply the rounds + for (let i=0; i<10; i++) doubleRound(this.buff); + + // Add to the initial + for (let i=0; i<16; i++) this.buff[i] = (this.buff[i] + this.state[i]) >>> 0; + + this.idx = 0; + + this.state[12] = (this.state[12] + 1) >>> 0; + if (this.state[12] != 0) return; + this.state[13] = (this.state[13] + 1) >>> 0; + if (this.state[13] != 0) return; + this.state[14] = (this.state[14] + 1) >>> 0; + if (this.state[14] != 0) return; + this.state[15] = (this.state[15] + 1) >>> 0; + } +}; diff --git a/src/ec.js b/src/ec.js index 30746fd..764682e 100644 --- a/src/ec.js +++ b/src/ec.js @@ -17,7 +17,27 @@ snarkjs. If not, see . */ + + const fUtils = require("./futils.js"); +const Scalar = require("./scalar"); +const assert = require("assert"); + + +function isGreatest(F, a) { + if (Array.isArray(a)) { + for (let i=a.length-1; i>=0; i--) { + if (!F.F.isZero(a[i])) { + return isGreatest(F.F, a[i]); + } + } + return 0; + } else { + const na = F.neg(a); + return Scalar.gt(a, na); + } +} + class EC { @@ -138,8 +158,10 @@ class EC { affine(p) { const F = this.F; - if (this.eq(p, this.zero)) { + if (this.isZero(p)) { return this.zero; + } else if (F.eq(p[2], F.one)) { + return p; } else { const Z_inv = F.inv(p[2]); const Z2_inv = F.square(Z_inv); @@ -209,11 +231,165 @@ class EC { return (F.eq(U1,U2) && F.eq(S1,S2)); } + isZero(p) { + return this.F.isZero(p[2]); + } + toString(p) { const cp = this.affine(p); return `[ ${this.F.toString(cp[0])} , ${this.F.toString(cp[1])} ]`; } + fromRng(rng) { + const F = this.F; + let P = []; + let greatest; + do { + P[0] = F.fromRng(rng); + greatest = rng.nextBool(); + const x3b = F.add(F.mul(F.square(P[0]), P[0]), this.b); + P[1] = F.sqrt(x3b); + } while ((P[1] == null)||(F.isZero[P])); + + const s = isGreatest(F, P[1]); + if (greatest ^ s) P[1] = F.neg(P[1]); + P[2] = F.one; + + if (this.cofactor) { + P = this.mulScalar(P, this.cofactor); + } + + P = this.affine(P); + + return P; + + } + + toRprLE(buff, o, p) { + p = this.affine(p); + if (this.isZero(p)) { + const BuffV = new Uint8Array(buff, o, this.F.n8*2); + BuffV.fill(0); + return; + } + this.F.toRprLE(buff, o, p[0]); + this.F.toRprLE(buff, o+this.F.n8, p[1]); + } + + toRprBE(buff, o, p) { + p = this.affine(p); + if (this.isZero(p)) { + const BuffV = new Uint8Array(buff, o, this.F.n8*2); + BuffV.fill(0); + return; + } + this.F.toRprBE(buff, o, p[0]); + this.F.toRprBE(buff, o+this.F.n8, p[1]); + } + + toRprLEM(buff, o, p) { + p = this.affine(p); + if (this.isZero(p)) { + const BuffV = new Uint8Array(buff, o, this.F.n8*2); + BuffV.fill(0); + return; + } + this.F.toRprLEM(buff, o, p[0]); + this.F.toRprLEM(buff, o+this.F.n8, p[1]); + } + + + toRprBEM(buff, o, p) { + p = this.affine(p); + if (this.isZero(p)) { + const BuffV = new Uint8Array(buff, o, this.F.n8*2); + BuffV.fill(0); + return; + } + this.F.toRprBEM(buff, o, p[0]); + this.F.toRprBEM(buff, o+this.F.n8, p[1]); + } + + fromRprLE(buff, o) { + o = o || 0; + const x = this.F.fromRprLE(buff, o); + const y = this.F.fromRprLE(buff, o+this.F.n8); + if (this.F.isZero(x) && this.F.isZero(y)) { + return this.zero; + } + return [x, y, this.F.one]; + } + + fromRprBE(buff, o) { + o = o || 0; + const x = this.F.fromRprBE(buff, o); + const y = this.F.fromRprBE(buff, o+this.F.n8); + if (this.F.isZero(x) && this.F.isZero(y)) { + return this.zero; + } + return [x, y, this.F.one]; + } + + fromRprLEM(buff, o) { + o = o || 0; + const x = this.F.fromRprLEM(buff, o); + const y = this.F.fromRprLEM(buff, o+this.F.n8); + if (this.F.isZero(x) && this.F.isZero(y)) { + return this.zero; + } + return [x, y, this.F.one]; + } + + fromRprBEM(buff, o) { + o = o || 0; + const x = this.F.fromRprBEM(buff, o); + const y = this.F.fromRprBEM(buff, o+this.F.n8); + if (this.F.isZero(x) && this.F.isZero(y)) { + return this.zero; + } + return [x, y, this.F.one]; + } + + fromRprCompressed(buff, o) { + const F = this.F; + const v = new Uint8Array(buff, o, F.n8); + if (v[0] & 0x40) return this.zero; + const P = new Array(3); + + const greatest = ((v[0] & 0x80) != 0); + v[0] = v[0] & 0x7F; + P[0] = F.fromRprBE(buff, o); + if (greatest) v[0] = v[0] | 0x80; // set back again the old value + + const x3b = F.add(F.mul(F.square(P[0]), P[0]), this.b); + P[1] = F.sqrt(x3b); + + if (P[1] === null) { + assert(false, "Invalid Point!"); + } + + const s = isGreatest(F, P[1]); + if (greatest ^ s) P[1] = F.neg(P[1]); + P[2] = F.one; + + return P; + } + + toRprCompressed(buff, o, p) { + p = this.affine(p); + const v = new Uint8Array(buff, o, this.F.n8); + if (this.isZero(p)) { + v.fill(0); + v[0] = 0x40; + return; + } + this.F.toRprBE(buff, o, p[0]); + + if (isGreatest(this.F, p[1])) { + v[0] = v[0] | 0x80; + } + } + } module.exports = EC; diff --git a/src/engine.js b/src/engine.js new file mode 100644 index 0000000..9e1e476 --- /dev/null +++ b/src/engine.js @@ -0,0 +1,401 @@ +/* global navigator, Blob, Worker, WebAssembly */ +/* + Copyright 2019 0KIMS association. + + This file is part of wasmsnark (Web Assembly zkSnark Prover). + + wasmsnark 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. + + wasmsnark 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 wasmsnark. If not, see . +*/ + +const MEM_SIZE = 4096; // Memory size in 64K Pakes (256Mb) + + +const assert = require("assert"); +const thread = require("./engine_thread"); + +const inBrowser = (typeof window !== "undefined"); +let NodeWorker; +let NodeCrypto; +if (!inBrowser) { + NodeWorker = require("worker_threads").Worker; + NodeCrypto = require("crypto"); +} + +class Deferred { + constructor() { + this.promise = new Promise((resolve, reject)=> { + this.reject = reject; + this.resolve = resolve; + }); + } +} + + +async function buildEngine(curve, wasm, singleThread) { + const engine = new Engine(); + + engine.curve = curve; + + engine.memory = new WebAssembly.Memory({initial:MEM_SIZE}); + engine.u8 = new Uint8Array(engine.memory.buffer); + engine.u32 = new Uint32Array(engine.memory.buffer); + + const wasmModule = await WebAssembly.compile(wasm.code); + + engine.instance = await WebAssembly.instantiate(wasmModule, { + env: { + "memory": engine.memory + } + }); + + engine.singleThread = singleThread; + engine.initalPFree = engine.u32[0]; // Save the Pointer to free space. + engine.pq = wasm.pq; + engine.pr = wasm.pr; + engine.pG1gen = wasm.pG1gen; + engine.pG1zero = wasm.pG1zero; + engine.pG2gen = wasm.pG2gen; + engine.pG2zero = wasm.pG2zero; + engine.pOneT = wasm.pOneT; + + engine.pTmp0 = engine.alloc(curve.G2.F.n8*3); + engine.pTmp1 = engine.alloc(curve.G2.F.n8*3); + + + if (singleThread) { + engine.code = wasm.code; + engine.taskManager = thread(); + await engine.taskManager({ + command: "INIT", + init: MEM_SIZE, + code: wasm.code + }); + engine.concurrency = 1; + } else { + engine.workers = []; + engine.pendingDeferreds = []; + engine.working = []; + + let concurrency; + + if ((typeof(navigator) === "object") && navigator.hardwareConcurrency) { + concurrency = navigator.hardwareConcurrency; + } else { + concurrency = 8; + } + + for (let i = 0; i 0); i++) { + if (this.working[i] == false) { + const work = this.actionQueue.shift(); + this.postAction(i, work.data, work.transfers, work.deferred); + } + } + } + + queueAction(actionData, transfers) { + if (this.singleThread) { + const res = this.taskManager(actionData); + return res[0]; + } else { + const d = new Deferred(); + this.actionQueue.push({ + data: actionData, + transfers: transfers, + deferred: d + }); + this.processWorks(); + return d.promise; + } + } + + resetMemory() { + this.u32[0] = this.initalPFree; + } + + allocG1(P) { + const pointer = this.alloc(this.curve.G1.F.n8*3); + if (P) this.setG1(pointer, P); + return pointer; + } + + allocG2(P) { + const pointer = this.alloc(this.curve.G2.F.n8*3); + if (P) this.setG2(pointer, P); + return pointer; + } + + allocF1(e) { + const pointer = this.alloc(this.curve.F1.n8); + if (e) this.setF1(pointer, e); + return pointer; + } + + allocF2(e) { + const pointer = this.alloc(this.curve.F2.n8); + if (e) this.setF2(pointer, e); + return pointer; + } + + allocFr(e) { + const pointer = this.alloc(this.curve.Fr.n8); + if (e) this.setFr(pointer, e); + return pointer; + } + + setG1(pointer, point) { + this.curve.G1.toRprLE(this.memory, pointer, point); + this.instance.g1m_toMontgomery(pointer, pointer); + } + + setG2(pointer, point) { + this.curve.G2.toRprLE(this.memory, pointer, point); + this.instance.g2m_toMontgomery(pointer, pointer); + } + + setF1(pointer, element) { + this.curve.F2.toRprLE(this.memory, pointer, element); + this.instance.f1m_toMontgomery(pointer, pointer); + } + + setF2(pointer, element) { + this.curve.F1.toRprLE(this.memory, pointer, element); + this.instance.f2m_toMontgomery(pointer, pointer); + } + + setFr(pointer, element) { + this.curve.Fr.toRprLE(this.memory, pointer, element); + this.instance.frm_toMontgomery(pointer, pointer); + } + + getG1(pointer) { + this.instance.g1m_fromMontgomery(pointer, this.pTmp1); + return this.curve.G1.fromRprLE(this.memory, this.pTmp1); + } + + getG2(pointer) { + this.instance.g2m_fromMontgomery(pointer, this.pTmp1); + return this.curve.G2.fromRprLE(this.memory, this.pTmp1); + } + + getF1(pointer) { + this.instance.f1m_fromMontgomery(pointer, this.pTmp1); + return this.curve.F1.fromRprLE(this.memory, this.pTmp1); + } + + getF2(pointer) { + this.instance.f2m_fromMontgomery(pointer, this.pTmp1); + return this.curve.F2.fromRprLE(this.memory, this.pTmp1); + } + + getFt(pointer) { + this.instance.frm_fromMontgomery(pointer, this.pTmp1); + return this.curve.Frm.fromRprLE(this.memory, this.pTmp1); + } + + allocBuff(buff) { + const pointer = this.alloc(buff.byteLength); + this.setBuffer(pointer, buff); + return pointer; + } + + getBuffer(pointer, length) { + return new Uint8Array(this.u8.buffer, this.u8.byteOffset + pointer, length); + } + + setBuffer(pointer, buffer) { + this.u8.set(new Uint8Array(buffer), pointer); + } + + alloc(length) { + while (this.u32[0] & 3) this.u32[0]++; // Return always aligned pointers + const res = this.u32[0]; + this.u32[0] += length; + return res; + } + + terminate() { + for (let i=0; i=0) { + sGin = G.F.n8*2; + } else if (["C"].indexOf(fr)>=0) { + sGin = G.F.n8; + } else { + throw new Error("Invaid convertion format: "+fr); + } + 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); + } + const nPoints = Math.floor(fullBuffIn.byteLength / sGin); + const pointsPerChunk = Math.floor(nPoints/self.concurrency); + const opPromises = []; + for (let i=0; i memory.buffer.byteLength) { + memory.grow(100); + } + return res; + } + + function allocBuffer(buffer) { + const p = alloc(buffer.byteLength); + setBuffer(p, buffer); + return p; + } + + function getBuffer(pointer, length) { + return new Uint8Array(u8.buffer, u8.byteOffset + pointer, length); + } + + function setBuffer(pointer, buffer) { + u8.set(new Uint8Array(buffer), pointer); + } + + function batchApplyKey(task) { + const outBuffLen = task.buff.byteLength; + const oldAlloc = u32[0]; + const pBufIn = allocBuffer(task.buff); + const pFirst = allocBuffer(task.first); + const pInc = allocBuffer(task.inc); + const pBuffOut = alloc(outBuffLen); + if (task.Gs == "G1") { + instance.exports.g1m_batchApplyKey(pBufIn, task.n, pFirst, pInc, pBuffOut); + } else { + instance.exports.g2m_batchApplyKey(pBufIn, task.n, pFirst, pInc, pBuffOut); + } + + const outBuff = getBuffer(pBuffOut, outBuffLen).slice(); + u32[0] = oldAlloc; + return [ outBuff, outBuff]; + } + + function batchConvert(task) { + const oldAlloc = u32[0]; + + const outBuffLen = task.n*task.sGin; + const pBufIn = allocBuffer(task.buff); + const pBuffOut = alloc(outBuffLen); + if (task.Gs == "G1") { + if (task.fr=="LEM") { + if (task.to=="U") { + instance.exports.g1m_batchLEMtoU(pBufIn, task.n, pBuffOut); + } else if (task.to=="C") { + instance.exports.g1m_batchLEMtoC(pBufIn, task.n, pBuffOut); + } else { + throw new Error("Invalid to: "+task.to); + } + } else { + throw new Error("Invalid fr: "+task.fr); + } + } else if (task.Gs == "G2") { + if (task.fr=="LEM") { + if (task.to=="U") { + instance.exports.g2m_batchLEMtoU(pBufIn, task.n, pBuffOut); + } else if (task.to=="C") { + instance.exports.g2m_batchLEMtoC(pBufIn, task.n, pBuffOut); + } else { + throw new Error("Invalid to: "+task.to); + } + } else { + throw new Error("Invalid fr: "+task.fr); + } + } else { + throw new Error("Invalid group: "+task.gs); + } + + const outBuff = getBuffer(pBuffOut, outBuffLen).slice(); + u32[0] = oldAlloc; + return [ outBuff, outBuff]; + } + + function taskManager(task) { + if (task.command == "INIT") { + return init(task); + } else if (task.command == "BATCH_APPLY_KEY") { + return batchApplyKey(task); + } else if (task.command == "BATCH_CONVERT") { + return batchConvert(task); + } else { + console.log("Invalid task", task); + throw new Error("Invalid task"); + } + } + + return taskManager; +}; diff --git a/src/f1field.js b/src/f1field.js index 35cf4b0..c2da4ca 100644 --- a/src/f1field.js +++ b/src/f1field.js @@ -1,6 +1,53 @@ +const Scalar = require("./scalar"); +const assert = require("assert"); + const supportsNativeBigInt = typeof BigInt === "function"; + +let F1Field; if (supportsNativeBigInt) { - module.exports = require("./f1field_native"); + F1Field = require("./f1field_native"); } else { - module.exports = require("./f1field_bigint"); + F1Field = require("./f1field_bigint"); } + + +// Returns a buffer with Little Endian Representation +F1Field.prototype.toRprLE = function toRprLE(buff, o, e) { + Scalar.toRprLE(buff, o, e, this.n64*8); +}; + +// Returns a buffer with Big Endian Representation +F1Field.prototype.toRprBE = function toRprBE(buff, o, e) { + Scalar.toRprBE(buff, o, e, this.n64*8); +}; + +// Returns a buffer with Big Endian Montgomery Representation +F1Field.prototype.toRprBEM = function toRprBEM(buff, o, e) { + return this.toRprBE(buff, o, this.mul(this.R, e)); +}; + +F1Field.prototype.toRprLEM = function toRprLEM(buff, o, e) { + return this.toRprLE(buff, o, this.mul(this.R, e)); +}; + + +// Pases a buffer with Little Endian Representation +F1Field.prototype.fromRprLE = function fromRprLE(buff, o) { + return Scalar.fromRprLE(buff, o, this.n8); +}; + +// Pases a buffer with Big Endian Representation +F1Field.prototype.fromRprBE = function fromRprBE(buff, o) { + return Scalar.fromRprBE(buff, o, this.n8); +}; + +F1Field.prototype.fromRprLEM = function fromRprLEM(buff, o) { + return this.mul(this.fromRprLE(buff, o), this.Ri); +}; + +F1Field.prototype.fromRprBEM = function fromRprBEM(buff, o) { + return this.mul(this.fromRprBE(buff, o), this.Ri); +}; + + +module.exports = F1Field; diff --git a/src/f1field_bigint.js b/src/f1field_bigint.js index f91406d..7a9c7a9 100644 --- a/src/f1field_bigint.js +++ b/src/f1field_bigint.js @@ -1,5 +1,6 @@ const bigInt = require("big-integer"); const assert = require("assert"); +const buildSqrt = require("./fsqrt"); function getRandomByte() { if (typeof window !== "undefined") { // Browser @@ -19,28 +20,33 @@ function getRandomByte() { module.exports = class ZqField { constructor(p) { + this.type="F1"; this.one = bigInt.one; this.zero = bigInt.zero; this.p = bigInt(p); - this.minusone = this.p.minus(bigInt.one); + this.m = 1; + this.negone = this.p.minus(bigInt.one); this.two = bigInt(2); this.half = this.p.shiftRight(1); this.bitLength = this.p.bitLength(); this.mask = bigInt.one.shiftLeft(this.bitLength).minus(bigInt.one); this.n64 = Math.floor((this.bitLength - 1) / 64)+1; + this.n32 = this.n64*2; + this.n8 = this.n64*8; this.R = bigInt.one.shiftLeft(this.n64*64); - - const e = this.minusone.shiftRight(this.one); + this.Ri = this.inv(this.R); +/* + const e = this.negone.shiftRight(this.one); this.nqr = this.two; let r = this.pow(this.nqr, e); - while (!r.equals(this.minusone)) { + while (!r.equals(this.negone)) { this.nqr = this.nqr.add(this.one); r = this.pow(this.nqr, e); } this.s = this.zero; - this.t = this.minusone; + this.t = this.negone; while (!this.t.isOdd()) { this.s = this.s.add(this.one); @@ -48,6 +54,8 @@ module.exports = class ZqField { } this.nqr_to_t = this.pow(this.nqr, this.t); +*/ + buildSqrt(this); } e(a,b) { @@ -200,12 +208,12 @@ module.exports = class ZqField { return a.isZero() ? bigInt.one : bigInt.zero; } - sqrt(n) { + sqrt_old(n) { if (n.equals(this.zero)) return this.zero; // Test that have solution - const res = this.pow(n, this.minusone.shiftRight(this.one)); + const res = this.pow(n, this.negone.shiftRight(this.one)); if (!res.equals(this.one)) return null; let m = parseInt(this.s); @@ -273,6 +281,19 @@ module.exports = class ZqField { return a.isZero(); } + fromRng(rng) { + let v; + do { + v = bigInt(0); + for (let i=0; i> 1n; this.bitLength = Scalar.bitLength(this.p); this.mask = (1n << BigInt(this.bitLength)) - 1n; this.n64 = Math.floor((this.bitLength - 1) / 64)+1; + this.n32 = this.n64*2; + this.n8 = this.n64*8; this.R = this.e(1n << BigInt(this.n64*64)); - - const e = this.minusone >> 1n; + this.Ri = this.inv(this.R); +/* + const e = this.negone >> 1n; this.nqr = this.two; let r = this.pow(this.nqr, e); - while (!this.eq(r, this.minusone)) { + while (!this.eq(r, this.negone)) { this.nqr = this.nqr + 1n; r = this.pow(this.nqr, e); } + this.s = 0; - this.t = this.minusone; + this.t = this.negone; while ((this.t & 1n) == 0n) { this.s = this.s + 1; @@ -50,6 +57,8 @@ module.exports = class ZqField { } this.nqr_to_t = this.pow(this.nqr, this.t); +*/ + buildSqrt(this); } e(a,b) { @@ -219,12 +228,12 @@ module.exports = class ZqField { return (a) ? 0n : 1n; } - sqrt(n) { + sqrt_old(n) { if (n == 0n) return this.zero; // Test that have solution - const res = this.pow(n, this.minusone >> this.one); + const res = this.pow(n, this.negone >> this.one); if ( res != 1n ) return null; let m = this.s; @@ -292,5 +301,18 @@ module.exports = class ZqField { return a == 0n; } + fromRng(rng) { + let v; + do { + v=0n; + for (let i=0; i= this.p); + v = (v * this.Ri) % this.p; // Convert from montgomery + return v; + } + }; diff --git a/src/f2field.js b/src/f2field.js index 6da5622..a87c2f0 100644 --- a/src/f2field.js +++ b/src/f2field.js @@ -18,13 +18,23 @@ */ const fUtils = require("./futils.js"); +const buildSqrt = require("./fsqrt"); class F2Field { constructor(F, nonResidue) { + this.type="F2"; this.F = F; this.zero = [this.F.zero, this.F.zero]; this.one = [this.F.one, this.F.zero]; + this.negone = this.neg(this.one); this.nonResidue = nonResidue; + this.m = F.m*2; + this.p = F.p; + this.n64 = F.n64*2; + this.n32 = this.n64*2; + this.n8 = this.n64*8; + + buildSqrt(this); } _mulByNonResidue(a) { @@ -57,6 +67,13 @@ class F2Field { return this.sub(this.zero, a); } + conjugate(a) { + return [ + a[0], + this.F.neg(a[1]) + ]; + } + mul(a, b) { const aA = this.F.mul(a[0] , b[0]); const bB = this.F.mul(a[1] , b[1]); @@ -120,13 +137,97 @@ class F2Field { return fUtils.mulScalar(this, base, e); } - exp(base, e) { + pow(base, e) { return fUtils.exp(this, base, e); } toString(a) { return `[ ${this.F.toString(a[0])} , ${this.F.toString(a[1])} ]`; } + + fromRng(rng) { + const c0 = this.F.fromRng(rng); + const c1 = this.F.fromRng(rng); + return [c0, c1]; + } + + gt(a, b) { + if (this.F.gt(a[0], b[0])) return true; + if (this.F.gt(b[0], a[0])) return false; + if (this.F.gt(a[1], b[1])) return true; + return false; + } + + geq(a, b) { + return this.gt(a, b) || this.eq(a, b); + } + + lt(a, b) { + return !this.geq(a,b); + } + + leq(a, b) { + return !this.gt(a,b); + } + + neq(a, b) { + return !this.eq(a,b); + } + + random() { + return [this.F.random(), this.F.random()]; + } + + + toRprLE(buff, o, e) { + this.F.toRprLE(buff, o, e[0]); + this.F.toRprLE(buff, o+this.F.n8, e[1]); + } + + toRprBE(buff, o, e) { + this.F.toRprBE(buff, o, e[1]); + this.F.toRprBE(buff, o+this.F.n8, e[0]); + } + + toRprLEM(buff, o, e) { + this.F.toRprLEM(buff, o, e[0]); + this.F.toRprLEM(buff, o+this.F.n8, e[1]); + } + + + toRprBEM(buff, o, e) { + this.F.toRprBEM(buff, o, e[1]); + this.F.toRprBEM(buff, o+this.F.n8, e[0]); + } + + fromRprLE(buff, o) { + o = o || 0; + const c0 = this.F.fromRprLE(buff, o); + const c1 = this.F.fromRprLE(buff, o+this.F.n8); + return [c0, c1]; + } + + fromRprBE(buff, o) { + o = o || 0; + const c1 = this.F.fromRprBE(buff, o); + const c0 = this.F.fromRprBE(buff, o+this.F.n8); + return [c0, c1]; + } + + fromRprLEM(buff, o) { + o = o || 0; + const c0 = this.F.fromRprLEM(buff, o); + const c1 = this.F.fromRprLEM(buff, o+this.F.n8); + return [c0, c1]; + } + + fromRprBEM(buff, o) { + o = o || 0; + const c1 = this.F.fromRprBEM(buff, o); + const c0 = this.F.fromRprBEM(buff, o+this.F.n8); + return [c0, c1]; + } + } module.exports = F2Field; diff --git a/src/f3field.js b/src/f3field.js index e2914ce..79d5a0d 100644 --- a/src/f3field.js +++ b/src/f3field.js @@ -21,10 +21,17 @@ const fUtils = require("./futils.js"); class F3Field { constructor(F, nonResidue) { + this.type="F3"; this.F = F; this.zero = [this.F.zero, this.F.zero, this.F.zero]; this.one = [this.F.one, this.F.zero, this.F.zero]; + this.negone = this.neg(this.one); this.nonResidue = nonResidue; + this.m = F.m*3; + this.p = F.p; + this.n64 = F.n64*2; + this.n32 = this.n64*2; + this.n8 = this.n64*8; } _mulByNonResidue(a) { @@ -171,6 +178,102 @@ class F3Field { toString(a) { return `[ ${this.F.toString(a[0])} , ${this.F.toString(a[1])}, ${this.F.toString(a[2])} ]`; } + + fromRng(rng) { + const c0 = this.F.fromRng(rng); + const c1 = this.F.fromRng(rng); + const c2 = this.F.fromRng(rng); + return [c0, c1, c2]; + } + + gt(a, b) { + if (this.F.gt(a[0], b[0])) return true; + if (this.F.gt(b[0], a[0])) return false; + if (this.F.gt(a[1], b[1])) return true; + if (this.F.gt(b[1], a[1])) return false; + if (this.F.gt(a[2], b[2])) return true; + return false; + } + + + geq(a, b) { + return this.gt(a, b) || this.eq(a, b); + } + + lt(a, b) { + return !this.geq(a,b); + } + + leq(a, b) { + return !this.gt(a,b); + } + + neq(a, b) { + return !this.eq(a,b); + } + + random() { + return [this.F.random(), this.F.random(), this.F.random()]; + } + + + toRprLE(buff, o, e) { + this.F.toRprLE(buff, o, e[0]); + this.F.toRprLE(buff, o+this.F.n8, e[1]); + this.F.toRprLE(buff, o+this.F.n8*2, e[2]); + } + + toRprBE(buff, o, e) { + this.F.toRprBE(buff, o, e[2]); + this.F.toRprBE(buff, o+this.F.n8, e[1]); + this.F.toRprBE(buff, o+this.F.n8*2, e[0]); + } + + toRprLEM(buff, o, e) { + this.F.toRprLEM(buff, o, e[0]); + this.F.toRprLEM(buff, o+this.F.n8, e[1]); + this.F.toRprLEM(buff, o+this.F.n8*2, e[2]); + } + + + toRprBEM(buff, o, e) { + this.F.toRprBEM(buff, o, e[2]); + this.F.toRprBEM(buff, o+this.F.n8, e[1]); + this.F.toRprBEM(buff, o+this.F.n8*2, e[0]); + } + + fromRprLE(buff, o) { + o = o || 0; + const c0 = this.F.fromRprLE(buff, o); + const c1 = this.F.fromRprLE(buff, o+this.n8); + const c2 = this.F.fromRprLE(buff, o+this.n8*2); + return [c0, c1, c2]; + } + + fromRprBE(buff, o) { + o = o || 0; + const c2 = this.F.fromRprBE(buff, o); + const c1 = this.F.fromRprBE(buff, o+this.n8); + const c0 = this.F.fromRprBE(buff, o+this.n8*2); + return [c0, c1, c2]; + } + + fromRprLEM(buff, o) { + o = o || 0; + const c0 = this.F.fromRprLEM(buff, o); + const c1 = this.F.fromRprLEM(buff, o+this.n8); + const c2 = this.F.fromRprLEM(buff, o+this.n8*2); + return [c0, c1, c2]; + } + + fromRprBEM(buff, o) { + o = o || 0; + const c2 = this.F.fromRprBEM(buff, o); + const c1 = this.F.fromRprBEM(buff, o+this.n8); + const c0 = this.F.fromRprBEM(buff, o+this.n8*2); + return [c0, c1, c2]; + } + } module.exports = F3Field; diff --git a/src/fsqrt.js b/src/fsqrt.js new file mode 100644 index 0000000..7c5d82c --- /dev/null +++ b/src/fsqrt.js @@ -0,0 +1,256 @@ +const Scalar = require("./scalar"); +const assert = require("assert"); +// Check here: https://eprint.iacr.org/2012/685.pdf + +module.exports = function buildSqrt (F) { + if ((F.m % 2) == 1) { + if (Scalar.eq(Scalar.mod(F.p, 4), 1 )) { + if (Scalar.eq(Scalar.mod(F.p, 8), 1 )) { + if (Scalar.eq(Scalar.mod(F.p, 16), 1 )) { + // alg7_muller(F); + alg5_tonelliShanks(F); + } else if (Scalar.eq(Scalar.mod(F.p, 16), 1 )) { + alg4_kong(F); + } else { + assert(false); + } + } else if (Scalar.eq(Scalar.mod(F.p, 8), 5 )) { + alg3_atkin(F); + } else { + assert(false); + } + } else if (Scalar.eq(Scalar.mod(F.p, 4), 3 )) { + alg2_shanks(F); + } else { + assert(false); + } + } else { + const pm2mod4 = Scalar.mod(Scalar.pow(F.p, F.m/2), 4); + if (pm2mod4 == 1) { + alg10_adj(F); + } else if (pm2mod4 == 3) { + alg9_adj(F); + } else { + alg8_complex(F); + } + + } +}; + + +function alg7_muller(F) { + F.sqrt_q = Scalar.pow(F.p, F.m); + F.sqrt_2 = F.add(F.one, F.one); + F.sqrt_4 = F.add(F.sqrt_2, F.sqrt_2); + F.sqrt_e = Scalar.div( Scalar.sub(F.sqrt_q, 1) , 2); + + F.sqrt_bits = Scalar.bits(Scalar.div( Scalar.sub(F.sqrt_q, 1) , 4)); + + F.sqrt_v = function(alfa) { + const d = []; + d[0] = alfa; + d[1] = this.sub(this.square(alfa), this.sqrt_2); + for (let j=F.sqrt_bits.length-2; j>0; j--) { + const d0 = + this.sub( + this.mul(d[0], d[1]), + alfa + ); + const d1 = + this.sub( + this.square( d[ 1 - F.sqrt_bits[j] ]), + this.sqrt_2 + ); + d[ 1 - F.sqrt_bits[j] ] = d0; + d[ F.sqrt_bits[j] ] = d1; +/* + d[ 1 - F.sqrt_bits[j] ] = + this.sub( + this.mul(d[0], d[1]), + alfa + ); + d[ F.sqrt_bits[j] ] = + this.sub( + this.square( d[ 1 - F.sqrt_bits[j] ]), + this.sqrt_2 + ); +*/ + } + if (F.sqrt_bits[0] == 1) { + return this.sub( + this.mul(d[0], d[1]), + alfa + ); + } else { + return this.sub( + this.square(d[0]), + this.sqrt_2 + ); + } + }; + + F.sqrt = function(a) { + if (this.isZero(a)) return this.zero; + if (this.eq(a, this.sqrt_4)) return this.sqrt_2; + + + let t = this.one; + let a1 = this.pow( this.sub(a, F.sqrt_4), F.sqrt_e); + + while (this.eq(a1, this.one)) { + t = this.random(); + while (this.isZero(t) || this.eq(t, this.one)) { + t = this.random(); + } + + const b = this.sub(this.mul(a, this.square(t)), this.sqrt_4); + if (this.isZero(b)) { + return this.mul(this.sqrt_2, this.inv(t)); + } + + a1 = this.pow(b, F.sqrt_e); + } + + const alfa = this.sub(this.mul(a, this.square(t)), this.sqrt_2); + + const x = this.div( this.sqrt_v(alfa), t); + + const a0 = this.sub(this.square(x), a); + + if (!this.isZero(a0)) return null; + + return x; + + + }; +} + +function alg5_tonelliShanks(F) { + F.sqrt_q = Scalar.pow(F.p, F.m); + + F.sqrt_s = 0; + F.sqrt_t = Scalar.sub(F.sqrt_q, 1); + + while (!Scalar.isOdd(F.sqrt_t)) { + F.sqrt_s = F.sqrt_s + 1; + F.sqrt_t = Scalar.div(F.sqrt_t, 2); + } + + let c0 = F.one; + + while (F.eq(c0, F.one)) { + const c = F.random(); + F.sqrt_z = F.pow(c, F.sqrt_t); + c0 = F.pow(F.sqrt_z, 1 << (F.sqrt_s-1) ); + } + + F.sqrt_tm1d2 = Scalar.div(Scalar.sub(F.sqrt_t, 1),2); + + F.sqrt = function(a) { + const F=this; + if (F.isZero(a)) return F.zero; + let w = F.pow(a, F.sqrt_tm1d2); + const a0 = F.pow( F.mul(F.square(w), a), 1 << (F.sqrt_s-1) ); + if (F.eq(a0, F.negone)) return null; + + let v = F.sqrt_s; + let x = F.mul(a, w); + let b = F.mul(x, w); + let z = F.sqrt_z; + while (!F.eq(b, F.one)) { + let b2k = F.square(b); + let k=1; + while (!F.eq(b2k, F.one)) { + b2k = F.square(b2k); + k++; + } + + w = z; + for (let i=0; i> 5)+1; // Number of 32bit words; + for (let i=0; i> 5)+1; // Number of 32bit words; + for (let i=0; i a[a.length-i-1] = ch.toString(16).padStart(8,"0") ); + return Scalar.fromString(a.join(""), 16); +}; + +// 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 a = new Array(n8/4); + for (let i=0; i { let b = F.sqrt(a); assert(F.eq(F.e(0), F.sqrt(F.e("0")))); assert(F.eq(b, F.e("2"))); - assert(F.sqrt(F.nqr) === null); + // assert(F.sqrt(F.nqr) === null); }); it("Should compute sqrt of 100 random numbers", () => { @@ -237,4 +237,29 @@ describe("Pairing", () => { assert(bn128.F12.eq(res, bn128.F12.one)); */ } }).timeout(10000); + it("Should test rpr of F2", () => { + const P1 = [ + [ + bn128.F1.e("1b2327ce7815d3358fe89fd8e5695305ed23682db29569f549ab8f48cae1f1c4",16), + bn128.F1.e("1ed41ca6b3edc06237af648f845c270ff83bcde333f17863c1b71a43b271b46d",16) + ], + [ + bn128.F1.e("122057912ab892abcf2e729f0f342baea3fe1b484840eb97c7d78cd7530f4ab5",16), + bn128.F1.e("2cb317fd40d56eeb17b0c1ff9443661a42ec00cea060012873b3f643f1a5bff8",16) + ], + [ + bn128.F1.one, + bn128.F1.zero + ] + ]; + const buff = new ArrayBuffer(64); + bn128.G2.toRprCompressed(buff, 0, P1); + + const P2 = bn128.G2.fromRprCompressed(buff, 0); + + console.log(P1[1][0].toString(16)); + console.log(P2[1][0].toString(16)); + + assert(bn128.G2.eq(P1,P2)); + }).timeout(10000); }); diff --git a/test/sqrt.js b/test/sqrt.js new file mode 100644 index 0000000..fd11436 --- /dev/null +++ b/test/sqrt.js @@ -0,0 +1,87 @@ +/* + Copyright 2018 0kims association. + + This file is part of zksnark JavaScript library. + + zksnark JavaScript library 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. + + zksnark JavaScript library 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 + zksnark JavaScript library. If not, see . +*/ + +const chai = require("chai"); + +const Scalar = require("../src/scalar.js"); +const bn128 = require("../src/bn128.js"); +const F1Field = require("../src/f1field.js"); + +const assert = chai.assert; + + +describe("Sqrt testing", () => { +/* + it("Should compute sqrts", () => { + const F = new F1Field(bn128.r); + const a = F.e(2); + const b = F.sqrt_v(a); + console.log(F.toString(b)); + }); +*/ + it("Should compute basic sqrts", () => { + const F = new F1Field(bn128.r); + assert(F.eq(F.e(0), F.sqrt(F.e("0")))); + const a = F.e("9"); + let b = F.sqrt(a); + assert(F.eq(b, F.e("3"))); + assert(F.sqrt(F.sqrt_z) === null); + }); + it("Should compute sqrt p%4 = 1", () => { + const F = new F1Field(bn128.r); + const e = Scalar.div(Scalar.pow(F.p, F.m), 2); + for (let i=0; i<100; i++) { + const x2 = F.random(); + const x = F.sqrt(x2); + if (x==null) { + assert(F.eq( F.pow(x2, e), F.negone)); + } else { + assert(F.eq(F.square(x), x2)); + } + } + }); + it("Should compute sqrt p%4 = 3", () => { + const F = new F1Field(bn128.q); + const e = Scalar.div(Scalar.pow(F.p, F.m), 2); + for (let i=0; i<100; i++) { + const x2 = F.random(); + const x = F.sqrt(x2); + if (x==null) { + assert(F.eq( F.pow(x2, e), F.negone)); + } else { + assert(F.eq(F.square(x), x2)); + } + } + }); + it("Should compute sqrt m=2 p%4 = 3", () => { + const F = bn128.F2; + const e = Scalar.div(Scalar.pow(F.p, F.m), 2); + for (let i=0; i<100; i++) { + const x2 = F.random(); + const x = F.sqrt(x2); + if (x==null) { + assert(F.eq( F.pow(x2, e), F.negone)); + } else { + assert(F.eq(F.square(x), x2)); + } + } + }); + +}); +