UNPKG

snarkjs

Version:

zkSNARKs implementation in JavaScript

1,486 lines (1,215 loc) 502 kB
#! /usr/bin/env node 'use strict'; var fs = require('fs'); var url = require('url'); var r1csfile = require('r1csfile'); var fastFile = require('fastfile'); var ffjavascript = require('ffjavascript'); var Blake2b = require('blake2b-wasm'); var readline = require('readline'); var crypto = require('crypto'); var path = require('path'); var binFileUtils = require('@iden3/binfileutils'); var ejs = require('ejs'); var circom_runtime = require('circom_runtime'); var jsSha3 = require('js-sha3'); var bfj = require('bfj'); var Logger = require('logplease'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); var url__default = /*#__PURE__*/_interopDefaultLegacy(url); var fastFile__namespace = /*#__PURE__*/_interopNamespace(fastFile); var Blake2b__default = /*#__PURE__*/_interopDefaultLegacy(Blake2b); var readline__default = /*#__PURE__*/_interopDefaultLegacy(readline); var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto); var path__default = /*#__PURE__*/_interopDefaultLegacy(path); var binFileUtils__namespace = /*#__PURE__*/_interopNamespace(binFileUtils); var ejs__default = /*#__PURE__*/_interopDefaultLegacy(ejs); var jsSha3__default = /*#__PURE__*/_interopDefaultLegacy(jsSha3); var bfj__default = /*#__PURE__*/_interopDefaultLegacy(bfj); var Logger__default = /*#__PURE__*/_interopDefaultLegacy(Logger); /* 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/>. */ async function loadSymbols(symFileName) { const sym = { labelIdx2Name: [ "one" ], varIdx2Name: [ "one" ], componentIdx2Name: [] }; const fd = await fastFile__namespace.readExisting(symFileName); const buff = await fd.read(fd.totalSize); const symsStr = new TextDecoder("utf-8").decode(buff); const lines = symsStr.split("\n"); for (let i=0; i<lines.length; i++) { const arr = lines[i].split(","); if (arr.length!=4) continue; if (sym.varIdx2Name[arr[1]]) { sym.varIdx2Name[arr[1]] += "|" + arr[3]; } else { sym.varIdx2Name[arr[1]] = arr[3]; } sym.labelIdx2Name[arr[0]] = arr[3]; if (!sym.componentIdx2Name[arr[2]]) { sym.componentIdx2Name[arr[2]] = extractComponent(arr[3]); } } await fd.close(); return sym; function extractComponent(name) { const arr = name.split("."); arr.pop(); // Remove the lasr element return arr.join("."); } } /* 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/>. */ function r1csPrint$1(r1cs, syms, logger) { for (let i=0; i<r1cs.constraints.length; i++) { printCostraint(r1cs.constraints[i]); } function printCostraint(c) { const lc2str = (lc) => { let S = ""; const keys = Object.keys(lc); keys.forEach( (k) => { let name = syms.varIdx2Name[k]; if (name == "one") name = "1"; let vs = r1cs.curve.Fr.toString(lc[k]); if (vs == "1") vs = ""; // Do not show ones if (vs == "-1") vs = "-"; // Do not show ones if ((S!="")&&(vs[0]!="-")) vs = "+"+vs; if (S!="") vs = " "+vs; S= S + vs + name; }); return S; }; const S = `[ ${lc2str(c[0])} ] * [ ${lc2str(c[1])} ] - [ ${lc2str(c[2])} ] = 0`; if (logger) logger.info(S); } } /* 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/>. */ const bls12381r$1 = ffjavascript.Scalar.e("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16); const bn128r$1 = ffjavascript.Scalar.e("21888242871839275222246405745257275088548364400416034343698204186575808495617"); async function r1csInfo$1(r1csName, logger) { const cir = await r1csfile.readR1cs(r1csName); if (ffjavascript.Scalar.eq(cir.prime, bn128r$1)) { if (logger) logger.info("Curve: bn-128"); } else if (ffjavascript.Scalar.eq(cir.prime, bls12381r$1)) { if (logger) logger.info("Curve: bls12-381"); } else { if (logger) logger.info(`Unknown Curve. Prime: ${ffjavascript.Scalar.toString(cir.prime)}`); } if (logger) logger.info(`# of Wires: ${cir.nVars}`); if (logger) logger.info(`# of Constraints: ${cir.nConstraints}`); if (logger) logger.info(`# of Private Inputs: ${cir.nPrvInputs}`); if (logger) logger.info(`# of Public Inputs: ${cir.nPubInputs}`); if (logger) logger.info(`# of Labels: ${cir.nLabels}`); if (logger) logger.info(`# of Outputs: ${cir.nOutputs}`); return cir; } /* 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/>. */ 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 formatHash(b, title) { const a = new DataView(b.buffer, b.byteOffset, b.byteLength); let S = ""; for (let i=0; i<4; i++) { if (i>0) S += "\n"; S += "\t\t"; for (let j=0; j<4; j++) { if (j>0) S += " "; S += a.getUint32(i*16+j*4).toString(16).padStart(8, "0"); } } if (title) S = title + "\n" + S; return S; } function hashIsEqual(h1, h2) { if (h1.byteLength != h2.byteLength) return false; var dv1 = new Int8Array(h1); var dv2 = new Int8Array(h2); for (var i = 0 ; i != h1.byteLength ; i++) { if (dv1[i] != dv2[i]) return false; } return true; } function cloneHasher(h) { const ph = h.getPartialHash(); const res = Blake2b__default["default"](64); res.setPartialHash(ph); return res; } async function sameRatio$2(curve, g1s, g1sx, g2s, g2sx) { if (curve.G1.isZero(g1s)) return false; if (curve.G1.isZero(g1sx)) return false; if (curve.G2.isZero(g2s)) return false; if (curve.G2.isZero(g2sx)) return false; // return curve.F12.eq(curve.pairing(g1s, g2sx), curve.pairing(g1sx, g2s)); const res = await curve.pairingEq(g1s, g2sx, curve.G1.neg(g1sx), g2s); return res; } function askEntropy() { if (process.browser) { return window.prompt("Enter a random text. (Entropy): ", ""); } else { const rl = readline__default["default"].createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question("Enter a random text. (Entropy): ", (input) => resolve(input) ); }); } } function getRandomBytes(n) { let array = new Uint8Array(n); if (process.browser) { // Supported globalThis.crypto.getRandomValues(array); } else { // NodeJS crypto__default["default"].randomFillSync(array); } return array; } async function sha256digest(data) { if (process.browser) { // Supported const buffer = await globalThis.crypto.subtle.digest("SHA-256", data.buffer); return new Uint8Array(buffer); } else { // NodeJS return crypto__default["default"].createHash("sha256").update(data).digest(); } } /** * @param {Uint8Array} data * @param {number} offset */ function readUInt32BE(data, offset) { return new DataView(data.buffer).getUint32(offset, false); } async function getRandomRng(entropy) { // Generate a random Rng while (!entropy) { entropy = await askEntropy(); } const hasher = Blake2b__default["default"](64); hasher.update(getRandomBytes(64)); const enc = new TextEncoder(); // always utf-8 hasher.update(enc.encode(entropy)); const hash = hasher.digest(); const seed = []; for (let i=0;i<8;i++) { seed[i] = readUInt32BE(hash, i*4); } const rng = new ffjavascript.ChaCha(seed); return rng; } async function rngFromBeaconParams(beaconHash, numIterationsExp) { let nIterationsInner; let nIterationsOuter; if (numIterationsExp<32) { nIterationsInner = (1 << numIterationsExp) >>> 0; nIterationsOuter = 1; } else { nIterationsInner = 0x100000000; nIterationsOuter = (1 << (numIterationsExp-32)) >>> 0; } let curHash = beaconHash; for (let i=0; i<nIterationsOuter; i++) { for (let j=0; j<nIterationsInner; j++) { curHash = await sha256digest(curHash); } } const curHashV = new DataView(curHash.buffer, curHash.byteOffset, curHash.byteLength); const seed = []; for (let i=0; i<8; i++) { seed[i] = curHashV.getUint32(i*4, false); } const rng = new ffjavascript.ChaCha(seed); return rng; } function hex2ByteArray(s) { if (s instanceof Uint8Array) return s; if (s.slice(0,2) == "0x") s= s.slice(2); return new Uint8Array(s.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16); })); } function byteArray2hex(byteArray) { return Array.prototype.map.call(byteArray, function(byte) { return ("0" + (byte & 0xFF).toString(16)).slice(-2); }).join(""); } function stringifyBigIntsWithField(Fr, o) { if (o instanceof Uint8Array) { return Fr.toString(o); } else if (Array.isArray(o)) { return o.map(stringifyBigIntsWithField.bind(null, Fr)); } else if (typeof o == "object") { const res = {}; const keys = Object.keys(o); keys.forEach( (k) => { res[k] = stringifyBigIntsWithField(Fr, o[k]); }); return res; } else if ((typeof(o) == "bigint") || o.eq !== undefined) { return o.toString(10); } else { return o; } } /* 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/>. */ async function r1csExportJson(r1csFileName, logger) { const cir = await r1csfile.readR1cs(r1csFileName, true, true, true, logger); const Fr=cir.curve.Fr; delete cir.curve; delete cir.F; return stringifyBigIntsWithField(Fr, cir); } /* 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/>. */ const __dirname$2 = path__default["default"].dirname(url__default["default"].fileURLToPath((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || new URL('cli.cjs', document.baseURI).href)))); let pkgS; try { pkgS = fs__default["default"].readFileSync(path__default["default"].join(__dirname$2, "package.json")); } catch (err) { pkgS = fs__default["default"].readFileSync(path__default["default"].join(__dirname$2, "..","package.json")); } const pkg = JSON.parse(pkgS); const version = pkg.version; let selectedCommand = null; async function clProcessor(commands) { const cl = []; const argv = {}; for (let i=2; i<process.argv.length; i++) { if (process.argv[i][0] == "-") { let S = process.argv[i]; while (S[0] == "-") S = S.slice(1); const arr = S.split("="); if (arr.length > 1) { argv[arr[0]] = arr.slice(1).join("="); } else { argv[arr[0]] = true; } } else { cl.push(process.argv[i]); } } for (let i=0; i<commands.length; i++) { const cmd = commands[i]; const m = calculateMatch(commands[i], cl); let res; if (m) { if ((argv.h) || (argv.help)) { helpCmd(cmd); return; } if (areParamsValid(cmd.cmd, m)) { if (cmd.options) { const options = getOptions(cmd.options); res = await cmd.action(m, options); } else { res = await cmd.action(m, {}); } } else { if (m.length>0) console.log("Invalid number of parameters"); helpCmd(cmd); return 99; } return res; } } if (cl.length>0) console.log("Invalid command"); helpAll(); return 99; function calculateMatch(cmd, cl) { const alias = []; const m = parseLine(cmd.cmd); alias.push(m); if (cmd.alias) { if (Array.isArray(cmd.alias)) { for (let i=0; i<cmd.alias.length; i++) { const a = parseLine(cmd.alias[i]); alias.push({ cmd: a.cmd, params: m.params }); } } else { const a = parseLine(cmd.alias); alias.push({ cmd: a.cmd, params: m.params }); } } for (let i=0; i<cl.length; i++) { for (let j=0; j<alias.length; j++) { const w = alias[j].cmd.shift(); if (cl[i].toUpperCase() == w.toUpperCase()) { if (alias[j].cmd.length == 0) { return buildRemaining(alias[j].params, cl.slice(i+1)); } } else { alias.splice(j, 1); j--; } } } return null; function buildRemaining(defParams, cl) { const res = []; let p=0; for (let i=0; i<defParams.length; i++) { if (defParams[i][0]=="-") { res.push(getOption(defParams[i]).val); } else { if (p<cl.length) { res.push(cl[p++]); } else { res.push(null); } } } while (p<cl.length) { res.push(cl[p++]); } return res; } } function parseLine(l) { const words = l.match(/(\S+)/g); for (let i=0; i<words.length; i++) { if ( (words[i][0] == "<") || (words[i][0] == "[") || (words[i][0] == "-")) { return { cmd: words.slice(0,i), params: words.slice(i) }; } } return { cmd: words, params: [] }; } function getOption(o) { const arr1 = o.slice(1).split(":"); const arr2 = arr1[0].split("|"); for (let i = 0; i<arr2.length; i++) { if (argv[arr2[i]]) return { key: arr2[0], val: argv[arr2[i]] }; } return { key: arr2[0], val: (arr1.length >1) ? arr1[1] : null }; } function areParamsValid(cmd, params) { while ((params.length)&&(!params[params.length-1])) params.pop(); const pl = parseLine(cmd); if (params.length > pl.params.length) return false; let minParams = pl.params.length; while ((minParams>0)&&(pl.params[minParams-1][0] == "[")) minParams --; if (params.length < minParams) return false; for (let i=0; (i< pl.params.length)&&(pl.params[i][0]=="<"); i++) { if (typeof params[i] == "undefined") return false; } return true; } function getOptions(options) { const res = {}; const opts = options.match(/(\S+)/g); for (let i=0; i<opts.length; i++) { const o = getOption(opts[i]); res[o.key] = o.val; } return res; } function printVersion() { console.log("snarkjs@"+version); } function epilog() { console.log(` Copyright (C) 2018 0kims association This program comes with ABSOLUTELY NO WARRANTY; This is free software, and you are welcome to redistribute it under certain conditions; see the COPYING file in the official repo directory at https://github.com/iden3/snarkjs `); } function helpAll() { printVersion(); epilog(); console.log(""); console.log("Usage:"); console.log(" snarkjs <full command> ... <options>"); console.log(" or snarkjs <shortcut> ... <options>"); console.log(""); console.log("Type snarkjs <command> --help to get more information for that command"); console.log(""); console.log("Full Command Description"); console.log("============ ================="); for (let i=0; i<commands.length; i++) { const cmd = commands[i]; let S = ""; const pl = parseLine(cmd.cmd); S += pl.cmd.join(" "); while (S.length<30) S = S+" "; S += cmd.description; console.log(S); S = " Usage: snarkjs "; if (cmd.alias) { if (Array.isArray(cmd.alias)) { S += cmd.alias[0]; } else { S += cmd.alias; } } else { S += pl.cmd.join(" "); } S += " " + pl.params.join(" "); console.log(S); } } function helpCmd(cmd) { if (typeof cmd == "undefined") cmd = selectedCommand; if (typeof cmd == "undefined") return helpAll(); printVersion(); epilog(); console.log(""); if (cmd.longDescription) { console.log(cmd.longDescription); } else { console.log(cmd.description); } console.log("Usage: "); console.log(" snarkjs "+ cmd.cmd); const pl = parseLine(cmd.cmd); let S = " or snarkjs "; if (cmd.alias) { if (Array.isArray(cmd.alias)) { S += cmd.alias[0]; } else { S += cmd.alias; } } else { S += pl.cmd.join(" "); } S += " " + pl.params.join(" "); console.log(S); console.log(""); } } /* 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/>. */ function hashToG2(curve, hash) { const hashV = new DataView(hash.buffer, hash.byteOffset, hash.byteLength); const seed = []; for (let i=0; i<8; i++) { seed[i] = hashV.getUint32(i*4); } const rng = new ffjavascript.ChaCha(seed); const g2_sp = curve.G2.fromRng(rng); return g2_sp; } function getG2sp(curve, persinalization, challenge, g1s, g1sx) { const h = Blake2b__default["default"](64); const b1 = new Uint8Array([persinalization]); h.update(b1); h.update(challenge); const b3 = curve.G1.toUncompressed(g1s); h.update( b3); const b4 = curve.G1.toUncompressed(g1sx); h.update( b4); const hash =h.digest(); return hashToG2(curve, hash); } function calculatePubKey(k, curve, personalization, challengeHash, rng ) { k.g1_s = curve.G1.toAffine(curve.G1.fromRng(rng)); k.g1_sx = curve.G1.toAffine(curve.G1.timesFr(k.g1_s, k.prvKey)); k.g2_sp = curve.G2.toAffine(getG2sp(curve, personalization, challengeHash, k.g1_s, k.g1_sx)); k.g2_spx = curve.G2.toAffine(curve.G2.timesFr(k.g2_sp, k.prvKey)); return k; } function createPTauKey(curve, challengeHash, rng) { const key = { tau: {}, alpha: {}, beta: {} }; key.tau.prvKey = curve.Fr.fromRng(rng); key.alpha.prvKey = curve.Fr.fromRng(rng); key.beta.prvKey = curve.Fr.fromRng(rng); calculatePubKey(key.tau, curve, 0, challengeHash, rng); calculatePubKey(key.alpha, curve, 1, challengeHash, rng); calculatePubKey(key.beta, curve, 2, challengeHash, rng); return key; } const bls12381r = ffjavascript.Scalar.e("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16); const bn128r = ffjavascript.Scalar.e("21888242871839275222246405745257275088548364400416034343698204186575808495617"); const bls12381q = ffjavascript.Scalar.e("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16); const bn128q = ffjavascript.Scalar.e("21888242871839275222246405745257275088696311157297823662689037894645226208583"); async function getCurveFromR(r, options) { let curve; // check that options param is defined and that options.singleThread is defined let singleThread = options && options.singleThread; if (ffjavascript.Scalar.eq(r, bn128r)) { curve = await ffjavascript.buildBn128(singleThread); } else if (ffjavascript.Scalar.eq(r, bls12381r)) { curve = await ffjavascript.buildBls12381(singleThread); } else { throw new Error(`Curve not supported: ${ffjavascript.Scalar.toString(r)}`); } return curve; } async function getCurveFromQ(q, options) { let curve; let singleThread = options && options.singleThread; if (ffjavascript.Scalar.eq(q, bn128q)) { curve = await ffjavascript.buildBn128(singleThread); } else if (ffjavascript.Scalar.eq(q, bls12381q)) { curve = await ffjavascript.buildBls12381(singleThread); } else { throw new Error(`Curve not supported: ${ffjavascript.Scalar.toString(q)}`); } return curve; } async function getCurveFromName(name, options) { let curve; let singleThread = options && options.singleThread; const normName = normalizeName(name); if (["BN128", "BN254", "ALTBN128"].indexOf(normName) >= 0) { curve = await ffjavascript.buildBn128(singleThread); } else if (["BLS12381"].indexOf(normName) >= 0) { curve = await ffjavascript.buildBls12381(singleThread); } else { throw new Error(`Curve not supported: ${name}`); } return curve; function normalizeName(n) { return n.toUpperCase().match(/[A-Za-z0-9]+/g).join(""); } } /* 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/>. */ async function writePTauHeader(fd, curve, power, ceremonyPower) { // Write the header /////////// if (! ceremonyPower) ceremonyPower = power; await fd.writeULE32(1); // Header type const pHeaderSize = fd.pos; await fd.writeULE64(0); // Temporally set to 0 length await fd.writeULE32(curve.F1.n64*8); const buff = new Uint8Array(curve.F1.n8); ffjavascript.Scalar.toRprLE(buff, 0, curve.q, curve.F1.n8); await fd.write(buff); await fd.writeULE32(power); // power await fd.writeULE32(ceremonyPower); // power const headerSize = fd.pos - pHeaderSize - 8; const oldPos = fd.pos; await fd.writeULE64(headerSize, pHeaderSize); fd.pos = oldPos; } async function readPTauHeader(fd, sections) { if (!sections[1]) throw new Error(fd.fileName + ": File has no header"); if (sections[1].length>1) throw new Error(fd.fileName +": File has more than one header"); fd.pos = sections[1][0].p; const n8 = await fd.readULE32(); const buff = await fd.read(n8); const q = ffjavascript.Scalar.fromRprLE(buff); const curve = await getCurveFromQ(q); if (curve.F1.n64*8 != n8) throw new Error(fd.fileName +": Invalid size"); const power = await fd.readULE32(); const ceremonyPower = await fd.readULE32(); if (fd.pos-sections[1][0].p != sections[1][0].size) throw new Error("Invalid PTau header size"); return {curve, power, ceremonyPower}; } async function readPtauPubKey(fd, curve, montgomery) { const buff = await fd.read(curve.F1.n8*2*6 + curve.F2.n8*2*3); return fromPtauPubKeyRpr(buff, 0, curve, montgomery); } function fromPtauPubKeyRpr(buff, pos, curve, montgomery) { const key = { tau: {}, alpha: {}, beta: {} }; key.tau.g1_s = readG1(); key.tau.g1_sx = readG1(); key.alpha.g1_s = readG1(); key.alpha.g1_sx = readG1(); key.beta.g1_s = readG1(); key.beta.g1_sx = readG1(); key.tau.g2_spx = readG2(); key.alpha.g2_spx = readG2(); key.beta.g2_spx = readG2(); return key; function readG1() { let p; if (montgomery) { p = curve.G1.fromRprLEM( buff, pos ); } else { p = curve.G1.fromRprUncompressed( buff, pos ); } pos += curve.G1.F.n8*2; return p; } function readG2() { let p; if (montgomery) { p = curve.G2.fromRprLEM( buff, pos ); } else { p = curve.G2.fromRprUncompressed( buff, pos ); } pos += curve.G2.F.n8*2; return p; } } function toPtauPubKeyRpr(buff, pos, curve, key, montgomery) { writeG1(key.tau.g1_s); writeG1(key.tau.g1_sx); writeG1(key.alpha.g1_s); writeG1(key.alpha.g1_sx); writeG1(key.beta.g1_s); writeG1(key.beta.g1_sx); writeG2(key.tau.g2_spx); writeG2(key.alpha.g2_spx); writeG2(key.beta.g2_spx); async function writeG1(p) { if (montgomery) { curve.G1.toRprLEM(buff, pos, p); } else { curve.G1.toRprUncompressed(buff, pos, p); } pos += curve.F1.n8*2; } async function writeG2(p) { if (montgomery) { curve.G2.toRprLEM(buff, pos, p); } else { curve.G2.toRprUncompressed(buff, pos, p); } pos += curve.F2.n8*2; } return buff; } async function writePtauPubKey(fd, curve, key, montgomery) { const buff = new Uint8Array(curve.F1.n8*2*6 + curve.F2.n8*2*3); toPtauPubKeyRpr(buff, 0, curve, key, montgomery); await fd.write(buff); } async function readContribution$1(fd, curve) { const c = {}; c.tauG1 = await readG1(); c.tauG2 = await readG2(); c.alphaG1 = await readG1(); c.betaG1 = await readG1(); c.betaG2 = await readG2(); c.key = await readPtauPubKey(fd, curve, true); c.partialHash = await fd.read(216); c.nextChallenge = await fd.read(64); c.type = await fd.readULE32(); const buffV = new Uint8Array(curve.G1.F.n8*2*6+curve.G2.F.n8*2*3); toPtauPubKeyRpr(buffV, 0, curve, c.key, false); const responseHasher = Blake2b__default["default"](64); responseHasher.setPartialHash(c.partialHash); responseHasher.update(buffV); c.responseHash = responseHasher.digest(); const paramLength = await fd.readULE32(); const curPos = fd.pos; let lastType =0; while (fd.pos-curPos < paramLength) { const buffType = await readDV(1); if (buffType[0]<= lastType) throw new Error("Parameters in the contribution must be sorted"); lastType = buffType[0]; if (buffType[0]==1) { // Name const buffLen = await readDV(1); const buffStr = await readDV(buffLen[0]); c.name = new TextDecoder().decode(buffStr); } else if (buffType[0]==2) { const buffExp = await readDV(1); c.numIterationsExp = buffExp[0]; } else if (buffType[0]==3) { const buffLen = await readDV(1); c.beaconHash = await readDV(buffLen[0]); } else { throw new Error("Parameter not recognized"); } } if (fd.pos != curPos + paramLength) { throw new Error("Parameters do not match"); } return c; async function readG1() { const pBuff = await fd.read(curve.G1.F.n8*2); return curve.G1.fromRprLEM( pBuff ); } async function readG2() { const pBuff = await fd.read(curve.G2.F.n8*2); return curve.G2.fromRprLEM( pBuff ); } async function readDV(n) { const b = await fd.read(n); return new Uint8Array(b); } } async function readContributions(fd, curve, sections) { if (!sections[7]) throw new Error(fd.fileName + ": File has no contributions"); if (sections[7][0].length>1) throw new Error(fd.fileName +": File has more than one contributions section"); fd.pos = sections[7][0].p; const nContributions = await fd.readULE32(); const contributions = []; for (let i=0; i<nContributions; i++) { const c = await readContribution$1(fd, curve); c.id = i+1; contributions.push(c); } if (fd.pos-sections[7][0].p != sections[7][0].size) throw new Error("Invalid contribution section size"); return contributions; } async function writeContribution$1(fd, curve, contribution) { const buffG1 = new Uint8Array(curve.F1.n8*2); const buffG2 = new Uint8Array(curve.F2.n8*2); await writeG1(contribution.tauG1); await writeG2(contribution.tauG2); await writeG1(contribution.alphaG1); await writeG1(contribution.betaG1); await writeG2(contribution.betaG2); await writePtauPubKey(fd, curve, contribution.key, true); await fd.write(contribution.partialHash); await fd.write(contribution.nextChallenge); await fd.writeULE32(contribution.type || 0); const params = []; if (contribution.name) { params.push(1); // Param Name const nameData = new TextEncoder("utf-8").encode(contribution.name.substring(0,64)); params.push(nameData.byteLength); for (let i=0; i<nameData.byteLength; i++) params.push(nameData[i]); } if (contribution.type == 1) { params.push(2); // Param numIterationsExp params.push(contribution.numIterationsExp); params.push(3); // Beacon Hash params.push(contribution.beaconHash.byteLength); for (let i=0; i<contribution.beaconHash.byteLength; i++) params.push(contribution.beaconHash[i]); } if (params.length>0) { const paramsBuff = new Uint8Array(params); await fd.writeULE32(paramsBuff.byteLength); await fd.write(paramsBuff); } else { await fd.writeULE32(0); } async function writeG1(p) { curve.G1.toRprLEM(buffG1, 0, p); await fd.write(buffG1); } async function writeG2(p) { curve.G2.toRprLEM(buffG2, 0, p); await fd.write(buffG2); } } async function writeContributions(fd, curve, contributions) { await fd.writeULE32(7); // Header type const pContributionsSize = fd.pos; await fd.writeULE64(0); // Temporally set to 0 length await fd.writeULE32(contributions.length); for (let i=0; i< contributions.length; i++) { await writeContribution$1(fd, curve, contributions[i]); } const contributionsSize = fd.pos - pContributionsSize - 8; const oldPos = fd.pos; await fd.writeULE64(contributionsSize, pContributionsSize); fd.pos = oldPos; } function calculateFirstChallengeHash(curve, power, logger) { if (logger) logger.debug("Calculating First Challenge Hash"); const hasher = new Blake2b__default["default"](64); const vG1 = new Uint8Array(curve.G1.F.n8*2); const vG2 = new Uint8Array(curve.G2.F.n8*2); curve.G1.toRprUncompressed(vG1, 0, curve.G1.g); curve.G2.toRprUncompressed(vG2, 0, curve.G2.g); hasher.update(Blake2b__default["default"](64).digest()); let n; n=(2 ** power)*2 -1; if (logger) logger.debug("Calculate Initial Hash: tauG1"); hashBlock(vG1, n); n= 2 ** power; if (logger) logger.debug("Calculate Initial Hash: tauG2"); hashBlock(vG2, n); if (logger) logger.debug("Calculate Initial Hash: alphaTauG1"); hashBlock(vG1, n); if (logger) logger.debug("Calculate Initial Hash: betaTauG1"); hashBlock(vG1, n); hasher.update(vG2); return hasher.digest(); function hashBlock(buff, n) { // this block size is a good compromise between speed and the maximum // input size of the Blake2b update method (65,535,720 bytes). const blockSize = 341000; const nBlocks = Math.floor(n / blockSize); const rem = n % blockSize; const bigBuff = new Uint8Array(blockSize * buff.byteLength); for (let i=0; i<blockSize; i++) { bigBuff.set(buff, i*buff.byteLength); } for (let i=0; i<nBlocks; i++) { hasher.update(bigBuff); if (logger) logger.debug("Initial hash: " +i*blockSize); } for (let i=0; i<rem; i++) { hasher.update(buff); } } } async function keyFromBeacon(curve, challengeHash, beaconHash, numIterationsExp) { const rng = await rngFromBeaconParams(beaconHash, numIterationsExp); const key = createPTauKey(curve, challengeHash, rng); return key; } /* 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/>. */ async function newAccumulator(curve, power, fileName, logger) { await Blake2b__default["default"].ready(); const fd = await binFileUtils__namespace.createBinFile(fileName, "ptau", 1, 7); await writePTauHeader(fd, curve, power, 0); const buffG1 = curve.G1.oneAffine; const buffG2 = curve.G2.oneAffine; // Write tauG1 /////////// await binFileUtils__namespace.startWriteSection(fd, 2); const nTauG1 = (2 ** power) * 2 -1; for (let i=0; i< nTauG1; i++) { await fd.write(buffG1); if ((logger)&&((i%100000) == 0)&&i) logger.log("tauG1: " + i); } await binFileUtils__namespace.endWriteSection(fd); // Write tauG2 /////////// await binFileUtils__namespace.startWriteSection(fd, 3); const nTauG2 = (2 ** power); for (let i=0; i< nTauG2; i++) { await fd.write(buffG2); if ((logger)&&((i%100000) == 0)&&i) logger.log("tauG2: " + i); } await binFileUtils__namespace.endWriteSection(fd); // Write alphaTauG1 /////////// await binFileUtils__namespace.startWriteSection(fd, 4); const nAlfaTauG1 = (2 ** power); for (let i=0; i< nAlfaTauG1; i++) { await fd.write(buffG1); if ((logger)&&((i%100000) == 0)&&i) logger.log("alphaTauG1: " + i); } await binFileUtils__namespace.endWriteSection(fd); // Write betaTauG1 /////////// await binFileUtils__namespace.startWriteSection(fd, 5); const nBetaTauG1 = (2 ** power); for (let i=0; i< nBetaTauG1; i++) { await fd.write(buffG1); if ((logger)&&((i%100000) == 0)&&i) logger.log("betaTauG1: " + i); } await binFileUtils__namespace.endWriteSection(fd); // Write betaG2 /////////// await binFileUtils__namespace.startWriteSection(fd, 6); await fd.write(buffG2); await binFileUtils__namespace.endWriteSection(fd); // Contributions /////////// await binFileUtils__namespace.startWriteSection(fd, 7); await fd.writeULE32(0); // 0 Contributions await binFileUtils__namespace.endWriteSection(fd); await fd.close(); const firstChallengeHash = calculateFirstChallengeHash(curve, power, logger); if (logger) logger.debug(formatHash(Blake2b__default["default"](64).digest(), "Blank Contribution Hash:")); if (logger) logger.info(formatHash(firstChallengeHash, "First Contribution Hash:")); return firstChallengeHash; } // Format of the outpu async function exportChallenge(pTauFilename, challengeFilename, logger) { await Blake2b__default["default"].ready(); const {fd: fdFrom, sections} = await binFileUtils__namespace.readBinFile(pTauFilename, "ptau", 1); const {curve, power} = await readPTauHeader(fdFrom, sections); const contributions = await readContributions(fdFrom, curve, sections); let lastResponseHash, curChallengeHash; if (contributions.length == 0) { lastResponseHash = Blake2b__default["default"](64).digest(); curChallengeHash = calculateFirstChallengeHash(curve, power); } else { lastResponseHash = contributions[contributions.length-1].responseHash; curChallengeHash = contributions[contributions.length-1].nextChallenge; } if (logger) logger.info(formatHash(lastResponseHash, "Last Response Hash: ")); if (logger) logger.info(formatHash(curChallengeHash, "New Challenge Hash: ")); const fdTo = await fastFile__namespace.createOverride(challengeFilename); const toHash = Blake2b__default["default"](64); await fdTo.write(lastResponseHash); toHash.update(lastResponseHash); await exportSection(2, "G1", (2 ** power) * 2 -1, "tauG1"); await exportSection(3, "G2", (2 ** power) , "tauG2"); await exportSection(4, "G1", (2 ** power) , "alphaTauG1"); await exportSection(5, "G1", (2 ** power) , "betaTauG1"); await exportSection(6, "G2", 1 , "betaG2"); await fdFrom.close(); await fdTo.close(); const calcCurChallengeHash = toHash.digest(); if (!hashIsEqual (curChallengeHash, calcCurChallengeHash)) { if (logger) logger.info(formatHash(calcCurChallengeHash, "Calc Curret Challenge Hash: ")); if (logger) logger.error("PTau file is corrupted. Calculated new challenge hash does not match with the eclared one"); throw new Error("PTau file is corrupted. Calculated new challenge hash does not match with the eclared one"); } return curChallengeHash; async function exportSection(sectionId, groupName, nPoints, sectionName) { const G = curve[groupName]; const sG = G.F.n8*2; const nPointsChunk = Math.floor((1<<24)/sG); await binFileUtils__namespace.startReadUniqueSection(fdFrom, sections, sectionId); for (let i=0; i< nPoints; i+= nPointsChunk) { if (logger) logger.debug(`Exporting ${sectionName}: ${i}/${nPoints}`); const n = Math.min(nPoints-i, nPointsChunk); let buff; buff = await fdFrom.read(n*sG); buff = await G.batchLEMtoU(buff); await fdTo.write(buff); toHash.update(buff); } await binFileUtils__namespace.endReadSection(fdFrom); } } /* 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/>. */ async function importResponse(oldPtauFilename, contributionFilename, newPTauFilename, name, importPoints, logger) { await Blake2b__default["default"].ready(); const noHash = new Uint8Array(64); for (let i=0; i<64; i++) noHash[i] = 0xFF; const {fd: fdOld, sections} = await binFileUtils__namespace.readBinFile(oldPtauFilename, "ptau", 1); const {curve, power} = await readPTauHeader(fdOld, sections); const contributions = await readContributions(fdOld, curve, sections); const currentContribution = {}; if (name) currentContribution.name = name; const sG1 = curve.F1.n8*2; const scG1 = curve.F1.n8; // Compressed size const sG2 = curve.F2.n8*2; const scG2 = curve.F2.n8; // Compressed size const fdResponse = await fastFile__namespace.readExisting(contributionFilename); if (fdResponse.totalSize != 64 + // Old Hash ((2 ** power)*2-1)*scG1 + (2 ** power)*scG2 + (2 ** power)*scG1 + (2 ** power)*scG1 + scG2 + sG1*6 + sG2*3) throw new Error("Size of the contribution is invalid"); let lastChallengeHash; if (contributions.length>0) { lastChallengeHash = contributions[contributions.length-1].nextChallenge; } else { lastChallengeHash = calculateFirstChallengeHash(curve, power, logger); } const fdNew = await binFileUtils__namespace.createBinFile(newPTauFilename, "ptau", 1, importPoints ? 7: 2); await writePTauHeader(fdNew, curve, power); const contributionPreviousHash = await fdResponse.read(64); if (hashIsEqual(noHash,lastChallengeHash)) { lastChallengeHash = contributionPreviousHash; contributions[contributions.length-1].nextChallenge = lastChallengeHash; } if(!hashIsEqual(contributionPreviousHash,lastChallengeHash)) throw new Error("Wrong contribution. This contribution is not based on the previous hash"); const hasherResponse = new Blake2b__default["default"](64); hasherResponse.update(contributionPreviousHash); const startSections = []; let res; res = await processSection(fdResponse, fdNew, "G1", 2, (2 ** power) * 2 -1, [1], "tauG1"); currentContribution.tauG1 = res[0]; res = await processSection(fdResponse, fdNew, "G2", 3, (2 ** power) , [1], "tauG2"); currentContribution.tauG2 = res[0]; res = await processSection(fdResponse, fdNew, "G1", 4, (2 ** power) , [0], "alphaG1"); currentContribution.alphaG1 = res[0]; res = await processSection(fdResponse, fdNew, "G1", 5, (2 ** power) , [0], "betaG1"); currentContribution.betaG1 = res[0]; res = await processSection(fdResponse, fdNew, "G2", 6, 1 , [0], "betaG2"); currentContribution.betaG2 = res[0]; currentContribution.partialHash = hasherResponse.getPartialHash(); const buffKey = await fdResponse.read(curve.F1.n8*2*6+curve.F2.n8*2*3); currentContribution.key = fromPtauPubKeyRpr(buffKey, 0, curve, false); hasherResponse.update(new Uint8Array(buffKey)); const hashResponse = hasherResponse.digest(); if (logger) logger.info(formatHash(hashResponse, "Contribution Response Hash imported: ")); if (importPoints) { const nextChallengeHasher = new Blake2b__default["default"](64); nextChallengeHasher.update(hashResponse); await hashSection(nextChallengeHasher, fdNew, "G1", 2, (2 ** power) * 2 -1, "tauG1", logger); await hashSection(nextChallengeHasher, fdNew, "G2", 3, (2 ** power) , "tauG2", logger); await hashSection(nextChallengeHasher, fdNew, "G1", 4, (2 ** power) , "alphaTauG1", logger); await hashSection(nextChallengeHasher, fdNew, "G1", 5, (2 ** power) , "betaTauG1", logger); await hashSection(nextChallengeHasher, fdNew, "G2", 6, 1 , "betaG2", logger); currentContribution.nextChallenge = nextChallengeHasher.digest(); if (logger) logger.info(formatHash(currentContribution.nextChallenge, "Next Challenge Hash: ")); } else { currentContribution.nextChallenge = noHash; } contributions.push(currentContribution); await writeContributions(fdNew, curve, contributions); await fdResponse.close(); await fdNew.close(); await fdOld.close(); return currentContribution.nextChallenge; async function processSection(fdFrom, fdTo, groupName, sectionId, nPoints, singularPointIndexes, sectionName) { if (importPoints) { return await processSectionImportPoints(fdFrom, fdTo, groupName, sectionId, nPoints, singularPointIndexes, sectionName); } else { return await processSectionNoImportPoints(fdFrom, fdTo, groupName, sectionId, nPoints, singularPointIndexes, sectionName); } } async function processSectionImportPoints(fdFrom, fdTo, groupName, sectionId, nPoints, singularPointIndexes, sectionName) { const G = curve[groupName]; const scG = G.F.n8; const sG = G.F.n8*2; const si