feat(@embark/snarks): Allow embark-snark to be used in the dapp

`embark-snark` has been updated such that it can be used, in conjunction with `embarkjs-snark`, in the console, and in the DApp.

This could, for example, be used to build a dapp like https://tornado.cash.

Please see the README for usage instructions.

Updated tests were excluded in this PR as a consideration for time already spent on getting this library completed. Tests should be updated in a future PR.
This commit is contained in:
emizzle 2020-01-13 22:21:21 +11:00 committed by Pascal Precht
parent 444b9eae87
commit c1129dc15f
28 changed files with 1049 additions and 257 deletions

View File

@ -46,6 +46,7 @@ class CodeRunner {
private whitelistVar(varName: string, cb = () => { }) {
// @ts-ignore
this.vm._options.require.external.push(varName); // @ts-ignore
cb();
}
private registerVar(varName: string, code: any, cb = () => { }) {

View File

@ -10,6 +10,7 @@ export interface Contract {
deployedAddress: string;
className: string;
silent?: boolean;
methods: any;
}
export interface ContractConfig {
@ -100,6 +101,8 @@ export interface Configuration {
};
plugins: EmbarkPlugins;
reloadConfig(): void;
dappPath(...args: string[]): string;
}
type ActionCallback<T> = (params: any, cb: Callback<T>) => void;
@ -108,6 +111,7 @@ import { Logger } from 'embark-logger';
export interface Embark {
env: string;
pluginConfig: any;
events: EmbarkEvents;
plugins: EmbarkPlugins;
registerAPICall(method: string, endpoint: string, cb: (...args: any[]) => void): void;
@ -118,7 +122,7 @@ export interface Embark {
currentContext: string[];
registerActionForEvent<T>(
name: string,
options?: ActionCallback<T> | { priority: number },
options?: ActionCallback<T> | { priority: number; },
action?: ActionCallback<T>,
): void;
}

View File

@ -0,0 +1,31 @@
/* global module require */
const cloneDeep = require('lodash.clonedeep');
module.exports = (api) => {
const env = api.env();
const base = {};
const browser = cloneDeep(base);
Object.assign(browser, {
ignore: [
'src/node'
]
});
const node = cloneDeep(base);
const test = cloneDeep(node);
switch (env) {
case 'browser':
return browser;
case 'node':
return node;
case 'test':
return test;
default:
return base;
}
};

1
packages/embarkjs/snark/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build-test

View File

@ -0,0 +1,4 @@
engine-strict = true
package-lock = false
save-exact = true
scripts-prepend-node-path = true

View File

@ -0,0 +1,8 @@
# `embarkjs-snark`
> zkSnarks plugin for embarkjs
Exposes functions for interaction with zkSNARKS. See [`embark-snark` README](../../plugins/snark/README.md) for more information.
Visit [embark.status.im](https://embark.status.im/) to get started with
[Embark](https://github.com/embark-framework/embark).

View File

@ -0,0 +1,84 @@
{
"name": "embarkjs-snark",
"version": "5.1.1-nightly.2",
"author": "Iuri Matias <iuri.matias@gmail.com>",
"contributors": [
"Eric Mastro <eric.mastro@gmail.com> (https://github.com/emizzle/)"
],
"description": "zkSnarks plugin for embarkjs",
"homepage": "https://github.com/embark-framework/embark/tree/master/packages/embarkjs/snark#readme",
"bugs": "https://github.com/embark-framework/embark/issues",
"keywords": [
"blockchain",
"dapps",
"ethereum",
"zksnarks",
"snarks",
"zero knowledge"
],
"license": "MIT",
"repository": {
"directory": "packages/embarkjs/snark",
"type": "git",
"url": "https://github.com/embark-framework/embark.git"
},
"main": "./dist/node/index.js",
"types": "./dist/index.d.ts",
"browser": {
"./dist/node/index.js": "./dist/browser/index.js",
"./dist/browser/embarkjs-snark.js": "./dist/browser/browser/embarkjs-snark.js"
},
"browserslist": [
"last 1 version",
"not dead",
"> 0.2%"
],
"files": [
"dist"
],
"embark-collective": {
"build:browser": true,
"build:node": true,
"typecheck": {
"compilerOptions": {
"module": "ESNext"
}
}
},
"scripts": {
"_build": "npm run solo -- build",
"_typecheck": "npm run solo -- typecheck",
"ci": "npm run qa",
"clean": "npm run reset",
"lint": "npm-run-all lint:*",
"// lint:js": "eslint test/",
"lint:ts": "tslint -c tslint.json \"src/**/*.ts\"",
"qa": "npm-run-all _typecheck _build",
"reset": "npx rimraf coverage dist embarkjs-*.tgz package",
"solo": "embark-solo"
},
"dependencies": {
"@babel/runtime-corejs3": "7.8.4",
"core-js": "3.6.4",
"embark-core": "^5.1.1-nightly.2",
"embarkjs": "^5.1.1-nightly.2",
"fs-extra": "8.1.0",
"snarkjs": "0.1.20"
},
"devDependencies": {
"embark-solo": "^5.1.1-nightly.2",
"eslint": "6.2.2",
"eslint-config-prettier": "6.1.0",
"eslint-plugin-prettier": "3.1.0",
"lodash.clonedeep": "4.5.0",
"npm-run-all": "4.1.5",
"rimraf": "3.0.0",
"tslint": "5.20.1",
"typescript": "3.7.2"
},
"engines": {
"node": ">=10.17.0",
"npm": ">=6.11.3",
"yarn": ">=1.19.1"
}
}

View File

@ -0,0 +1,43 @@
/* global EmbarkJS */
import { CircuitSetup, PluginConfig } from "..";
import Circuit from "../circuit";
export default class EmbarkJsSnark {
[key: string]: any;
private buildDir: string;
private buildDirUrl: string;
private contractsJsonDirUrl: string;
constructor(private setups: CircuitSetup[], private config: PluginConfig) {
this.buildDir = this.config.buildDir || "public/snarks/";
this.buildDirUrl = this.config.buildDirUrl || "/snarks/";
this.contractsJsonDirUrl = this.config.contractsJsonDirUrl || "/snarks/contracts/";
}
buildUrl(filepath: string) {
return filepath.replace(this.buildDir || "public/", this.buildDirUrl || "/");
}
public async init() {
for (const setup of this.setups) {
if (setup.config.exclude) {
continue;
}
if (!setup.provingKey) {
throw new Error("Error getting proving key: path not provided.");
}
if (!setup.verificationKey) {
throw new Error("Error getting verification key: path not provided.");
}
if (!setup.compiledCircuit) {
throw new Error("Error getting compiled circuit: path not provided.");
}
setup.compiledCircuit = await (await fetch(this.buildUrl(setup.compiledCircuit))).json();
setup.provingKey = await (await fetch(this.buildUrl(setup.provingKey))).json();
setup.verificationKey = await (await fetch(this.buildUrl(setup.verificationKey))).json();
const verifierContractJson = await (await fetch(`${this.contractsJsonDirUrl}${setup.verifierContractName}.json`)).json();
setup.verificationContract = new EmbarkJS.Blockchain.Contract(verifierContractJson);
const nameTitleCase = setup.name.charAt(0).toUpperCase() + setup.name.substr(1).toLowerCase();
this[nameTitleCase] = new Circuit(setup);
}
}
}

View File

@ -0,0 +1,109 @@
import { CircuitSetup } from ".";
import * as snarkjs from "snarkjs";
const { unstringifyBigInts } = require("snarkjs/src/stringifybigint");
const LOG_PREFIX = "[embarkjs-snark]: ";
export default class Circuit {
constructor(private setup: CircuitSetup) {
this.setup.provingKey = unstringifyBigInts(this.setup.provingKey);
this.setup.verificationKey = unstringifyBigInts(this.setup.verificationKey);
}
/**
* Given public signals and a proof to prove those public signals can be verified,
* generates an array of inputs the can be used to call the verifyProof function
* in the verification contract (Solidity).
*
* @remarks Derived from the {@link https://github.com/iden3/snarkjs/blob/f2e5bc56b33aedbbbf7fed38b3f234d3d2b1adb7/cli.js#L365-L392 | "generatecall" snarkjs cli function}
*
* @param publicSignals - public inputs to be verified using the proof
* @param proof - the proof used to verify the inputs are valid
* @returns an array of solidity inputs that can be used to call the "verifyProof"
* function of the deployed vertificadtion contract
*/
private generateSolidityInputs(publicSignals, proof): string[] {
publicSignals = unstringifyBigInts(publicSignals);
proof = unstringifyBigInts(proof);
const p256 = (n) => {
let nstr = n.toString(16);
while (nstr.length < 64) { nstr = "0" + nstr; }
nstr = `0x${nstr}`;
return nstr;
};
let inputs = "";
for (const publicSignal of publicSignals) {
if (inputs !== "") { inputs = inputs + ","; }
inputs = inputs + p256(publicSignal);
}
let S;
if ((typeof proof.protocol === "undefined") || (proof.protocol === "original")) {
S = [
[proof.pi_a[0], proof.pi_a[1]],
[proof.pi_ap[0], proof.pi_ap[1]],
[[proof.pi_b[0][1], proof.pi_b[0][0]], [proof.pi_b[1][1], proof.pi_b[1][0]]],
[proof.pi_bp[0], proof.pi_bp[1]],
[proof.pi_c[0], proof.pi_c[1]],
[proof.pi_cp[0], proof.pi_cp[1]],
[proof.pi_h[0], proof.pi_h[1]],
[proof.pi_kp[0], proof.pi_kp[1]]
];
} else if ((proof.protocol === "groth") || (proof.protocol === "kimleeoh")) {
S = [
[proof.pi_a[0], proof.pi_a[1]],
[[proof.pi_b[0][1], proof.pi_b[0][0]], [proof.pi_b[1][1], proof.pi_b[1][0]]],
[proof.pi_c[0], proof.pi_c[1]]
];
} else {
throw new Error("InvalidProof");
}
const two56ify = (arr: any[]): any[] => {
return arr.map(n => {
if (Array.isArray(n)) {
return two56ify(n);
}
return p256(n);
});
};
S = two56ify(S);
S.push([inputs]);
return S;
}
public async calculate(inputs: any) {
const circuit = new snarkjs.Circuit(this.setup.compiledCircuit);
const witness = circuit.calculateWitness(inputs);
const { proof, publicSignals } = snarkjs[this.setup.config.protocol].genProof(
this.setup.provingKey,
witness
);
return { proof, publicSignals };
}
public async verify(inputs: any) {
console.log(`${LOG_PREFIX}NOTE -- Private inputs will **not** be sent to the blockchain. They are used to calculate the witness and generate a proof.`);
if (!this.setup.verificationContract) {
return console.error(`Error verifying inputs, verification contract for '${this.setup.name}' not found.`);
}
console.log(`${LOG_PREFIX}Calculating witness and generating proof...`);
const { proof, publicSignals } = await this.calculate(inputs);
const solidityInputs = this.generateSolidityInputs(publicSignals, proof);
console.log(`${LOG_PREFIX}Verifying inputs on chain...`);
return await this.setup.verificationContract.methods.verifyProof(...solidityInputs).call();
}
public async verifyOffChain(inputs) {
console.log(`${LOG_PREFIX}Calculating witness and generating proof...`);
const { proof, publicSignals } = await this.calculate(inputs);
console.log(`${LOG_PREFIX}Verifying inputs off chain...`);
return snarkjs[this.setup.config.protocol].isValid(this.setup.verificationKey, proof, publicSignals);
}
}

View File

@ -0,0 +1,31 @@
import Circuit from "./circuit";
import * as fs from "fs-extra";
import { CircuitSetup } from ".";
export default class EmbarkJsSnark {
[key: string]: any;
constructor(private setups: CircuitSetup[]) { }
public async init() {
for (const setup of this.setups) {
if (setup.config.exclude) {
continue;
}
if (!setup.provingKey) {
throw new Error("Error getting proving key: path not provided.");
}
if (!setup.verificationKey) {
throw new Error("Error getting verification key: path not provided.");
}
if (!setup.compiledCircuit) {
throw new Error("Error getting compiled circuit: path not provided.");
}
setup.compiledCircuit = await fs.readJson(setup.compiledCircuit);
setup.provingKey = await fs.readJson(setup.provingKey);
setup.verificationKey = await fs.readJson(setup.verificationKey);
const nameTitleCase = setup.name.charAt(0).toUpperCase() + setup.name.substr(1).toLowerCase();
this[nameTitleCase] = new Circuit(setup);
}
}
}

View File

@ -0,0 +1,30 @@
import EmbarkJsSnark from "./embarkjs-snark";
export default EmbarkJsSnark;
export type SnarksProtocol = 'original' | 'groth' | 'kimleeoh';
export interface CircuitConfig {
protocol?: SnarksProtocol;
exclude?: boolean;
}
export interface PluginConfig {
circuits: string[];
circuitsConfig: {
[key: string]: CircuitConfig;
};
buildDir: string;
buildDirUrl: string;
contractsBuildDir: string;
contractsJsonDirUrl: string;
}
export interface CircuitSetup {
provingKey?: string;
verificationKey?: string;
compiledCircuit?: string;
config: CircuitConfig;
filepath: string;
name: string;
verifierContractName?: string;
verificationContract?: any;
}

View File

@ -0,0 +1 @@
declare const EmbarkJS: any;

View File

@ -0,0 +1,3 @@
import * as embarkSnark from '..';
module.exports = embarkSnark;

View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"composite": true,
"declarationDir": "./dist",
"module": "ESNext",
"rootDir": "./src",
"tsBuildInfoFile": "./node_modules/.cache/tsc/tsconfig.embarkjs-snark.tsbuildinfo"
},
"extends": "../../../tsconfig.base.json",
"include": [
"src/**/*"
],
"references": [
{
"path": "../../core/core"
},
{
"path": "../embarkjs"
}
]
}

View File

@ -0,0 +1,3 @@
{
"extends": "../../../tslint.json"
}

View File

@ -66,10 +66,7 @@ if (typeof WebSocket !== 'undefined') {
<% for (let stackName in (customPlugins || [])) { %>
<% for (let pluginName in (customPlugins[stackName] || [])) { %>
let __embark<%- pluginName %> = require('<%- customPlugins[stackName][pluginName] %>');
__embark<%- pluginName %> = __embark<%- pluginName %>.default || __embark<%- pluginName %>;
const customPluginConfig = require('./config/<%- stackName %>.json');
EmbarkJS.<%- stackName %> = new __embark<%- pluginName %>({pluginConfig: customPluginConfig});
<%- customPlugins[stackName][pluginName] %>
<% }; %>
<% }; %>

View File

@ -28,13 +28,17 @@ class EmbarkJS {
this.embarkJSPlugins = {};
this.customEmbarkJSPlugins = {};
this.events.setCommandHandler("embarkjs:plugin:register", (stackName, pluginName, packageName) => {
this.events.setCommandHandler("embarkjs:plugin:register", (stackName, pluginName, packageName, cb) => {
this.embarkJSPlugins[stackName] = this.embarkJSPlugins[stackName] || {};
this.embarkJSPlugins[stackName][pluginName] = packageName;
if (typeof cb === "function") {
cb();
}
});
this.events.setCommandHandler("embarkjs:plugin:register:custom", (stackName, pluginName, packageName) => {
this.events.setCommandHandler("embarkjs:plugin:register:custom", (stackName, pluginName, code, cb) => {
this.customEmbarkJSPlugins[stackName] = this.customEmbarkJSPlugins[stackName] || {};
this.customEmbarkJSPlugins[stackName][pluginName] = packageName;
this.customEmbarkJSPlugins[stackName][pluginName] = code;
cb();
});
this.events.setCommandHandler("embarkjs:console:register", (stackName, pluginName, packageName, cb) => {
@ -141,7 +145,9 @@ class EmbarkJS {
__embark${pluginName} = __embark${pluginName}.default || __embark${pluginName};
const customPluginOptions = ${JSON.stringify(options)};
EmbarkJS.${stackName} = new __embark${pluginName}({pluginConfig: customPluginOptions});
if (typeof EmbarkJS.${stackName}.init === "function") {
EmbarkJS.${stackName}.init();
}
`;
await this.events.request2('runcode:eval', customPluginCode);

View File

@ -2,6 +2,8 @@
> Snark plugin for Embark
Use zkSNARKS in your DApp!
`embark-snark` uses `snarkjs` to handle the trusted setup, generate proofs and verify the proofs.
Compiles circom circuits and generate solidity proofs in an Embark DApp.
## Installation
@ -12,26 +14,55 @@ In your embark dapp directory:
npm install embark-snark --save
```
Then add embark-snark to the plugins section in `embark.json`:
Then add `embark-snark` to the plugins section in `embark.json`:
```json
"plugins": {
"embark-snark": {
"circuits": ["app/circuits/**"],
"inputs": {
"buildDir": "public/",
"buildDirUrl": "/",
"contractsBuildDir": "contracts/",
"contractsJsonDirUrl": "/contracts/",
"circuits": [
"circuits/**"
],
"circuitsConfig": {
"multiplier": {
"a": 3,
"b": 11
"exclude": false
},
"withdraw": {
"protocol": "groth"
},
"merkleTree": {
"exclude": true
}
}
}
}
```
### Plugin options
|Option|Description|Default|
|---|---|---|
|`buildDir`|Determines where the output of the snarks operations will be stored in the filesytem **relative to the dapp root**. This includes the compiled circuits, proving keys, and verification keys.|`public/snarks/`|
|`buildDirUrl`|Absolute URL path (must start with a `/`) of the build directory after it is served by the webserver. This will be used to build a URL for the browser to fetch needed snarks files.|`/snarks/`|
|`contractsBuildDir`|Determines where the generated snarks verification contract will be stored in the dapp, relative to the Dapp root.|`contracts/`|
|`contractsJsonDirUrl`|Absolute URL path (must start with a "/") of the JSON contract files directory after it is served by the webserver, relative to the Dapp's build directory (as specified by the top-level `buildDir` of `embark.json`).<br><br>For example, if we specify `buildDir: "public/snarks/"`in `embark.json` (top-level setting, different to the plugin config setting `buildDir`), Embark will put all JSON contract configs in `public/snarks/contracts`. When our webserver serves out the `public/` directory, it will serve it out on `/`, and therefore our JSON contract configs will be accessible via `/snarks/contracts/` URL.|`/snarks/contracts/`|
|`circuits`|Array of glob patterns indicating where the circuits live in our dapp, relative to the dapp root.|`["circuits/**"]`|
|`circuitsConfig`|Object with a key that matches the circuit name, and a value that is the configuration object for the circuit, see below.|`{ protocol: "groth", exclude: false }`|
You can defined where your circuits will be and what are the inputs.
### Circuit config options
Now you can create your first circuits, for example,
`app/circuits/multiplier.circom`:
|Option|Description|Default|
|---|---|---|
|`protocol`|zkSnarks protocol used to generate verification key, proving key, and proof.<br><br>Valid options are `"groth"`, `"kimleeoh"`, and `"original"`|`"groth"`|
|`exclude`|If `true`, circuit will be used for verification. This is useful when circuits need to import other (usually commonly used) circuits, but those circuits will not be used for starting a verfication.|`false`|
## Usage
Let's say that we want to prove that we have two numbers that when multiplied together, equals 33, but we don't want anyone else using our DApp, nor the DApp logic, to know which numbers they are. Let's assume that those numbers are 3 and 11.
First, create a circuit to does the multiplication, for example,
`circuits/multiplier.circom`:
```
template Multiplier() {
@ -42,9 +73,29 @@ template Multiplier() {
component main = Multiplier();
```
Notice that we have two private inputs and an output. In a more realistic circuit, there can also be public inputs that can be shared, as well as multiple outputs.
Embark will now compile the circuits and generate a solidity contracts to
verify the proof as well as deploy it.
During `embark run`, Embark will compile the circuits, perform a trusted setup, generate solidity contracts to verify the proof, and deploy the contracts.
Now, in the Embark console, or in your DApp (ie in DevTools), you can now verify circuit inputs using:
```javascript
// syntax: EmbarkJS.Snark.<circuit>.verify(<inputs>)
const isValid = EmbarkJS.Snark.Multiplier.verify({a: 3, b: 11});
```
This will generate a witness using the secret inputs (which will contain the product 33) and a proof. Finally, **the proof and public signals** are submitted to the generated verification contract **on chain**, which will return `true` or `false`, indicating whether or not the proof and public signals are valid. In other words, the return value of the contract call determines if we can prove to other people, or the DApp logic, that we indeed have the secret inputs, without revealing the inputs themselves! It should be noted that **private inputs** will **not** be submitted to the chain, otherwise that would defeat the whole purpose!
Additionally, we can verify our inputs without submitting any data to the chain, by running a local proof:
```javascript
// syntax: EmbarkJS.Snark.<circuit>.verifyOffChain(<inputs>)
const isValid = EmbarkJS.Snark.Multiplier.verifyOffChain({a: 3, b: 11});
```
If, for example, you'd like the proof and public signals to be calculated, but verification run a different way, that can be done by calling:
```javascript
// syntax: EmbarkJS.Snark.<circuit>.calculate(<inputs>)
const { proof, publicSignals } = EmbarkJS.Snark.Multiplier.calculate({a: 3, b: 11});
```
## Requirements

View File

@ -3,7 +3,8 @@
"version": "5.3.0-nightly.5",
"author": "Anthony Laibe",
"contributors": [
"Michael Bradley <michaelsbradleyjr@gmail.com> (https://github.com/michaelsbradleyjr/)"
"Michael Bradley <michaelsbradleyjr@gmail.com> (https://github.com/michaelsbradleyjr/)",
"Eric Mastro <eric.mastro@gmail.com> (https://github.com/emizzle/)"
],
"description": "Snark plugin for Embark",
"homepage": "https://github.com/embarklabs/embark/tree/master/packages/plugins/snark#readme",
@ -43,18 +44,25 @@
"_typecheck": "npm run solo -- typecheck",
"ci": "npm run qa",
"clean": "npm run reset",
"lint": "eslint src/ test/",
"lint": "npm-run-all lint:*",
"lint:js": "eslint test/",
"lint:ts": "tslint -c tslint.json \"src/**/*.ts\"",
"qa": "npm-run-all lint _typecheck _build test",
"reset": "npx rimraf coverage dist embark-*.tgz package",
"solo": "embark-solo",
"test": "jest"
"// test": "jest"
},
"dependencies": {
"@babel/runtime-corejs3": "7.8.4",
"circom": "0.0.35",
"core-js": "3.4.3",
"embark-core": "^5.1.1-nightly.2",
"embark-logger": "^5.1.1-nightly.2",
"embark-utils": "^5.1.1-nightly.2",
"embarkjs-snark": "^5.1.1-nightly.2",
"find-up": "4.1.0",
"glob": "7.1.4",
"node-object-hash": "2.0.0",
"snarkjs": "0.1.20"
},
"devDependencies": {
@ -71,7 +79,8 @@
"jest": "25.1.0",
"npm-run-all": "4.1.5",
"prettier": "1.18.2",
"rimraf": "3.0.0"
"rimraf": "3.0.0",
"tslint": "5.20.1"
},
"eslintConfig": {
"env": {

View File

@ -1,211 +0,0 @@
import { exec } from 'child_process';
import { sync as findUp } from 'find-up';
import globCb from 'glob';
import * as path from 'path';
import * as zkSnark from 'snarkjs';
import { promisify } from 'util';
const glob = promisify(globCb);
zkSnark.bigInt.prototype.toJSON = function() {
return this.toString();
};
export class Snarks {
constructor(embark) {
this.embark = embark;
this.circuitsConfig = embark.pluginConfig;
this.compiledCircuitsPath = embark.config.dappPath('.embark', 'snarks');
this.fs = embark.fs;
this.logger = embark.logger;
if (this.circuitsConfig) {
this.registerEvents();
}
}
static Extensions = {
Json: '.json',
Circom: '.circom',
VkProof: '.vk_proof',
VkVerifier: '.vk_verifier',
Solidity: '.sol'
};
static circomBinary = findUp('node_modules/.bin/circom', { cwd: __dirname });
static snarkjsBinary = findUp('node_modules/.bin/snarkjs', {
cwd: __dirname
});
registerEvents() {
this.embark.registerActionForEvent(
'compiler:contracts:compile:before',
this.compileAndGenerateContracts.bind(this)
);
}
verifierFilepath(basename) {
return path.join(
this.compiledCircuitsPath,
`${basename}${Snarks.Extensions.VkVerifier}`
);
}
verifierContractPath(basename) {
return path.join(
this.compiledCircuitsPath,
`${basename}${Snarks.Extensions.Solidity}`
);
}
proofFilepath(basename) {
return path.join(
this.compiledCircuitsPath,
`${basename}${Snarks.Extensions.VkProof}`
);
}
async compileCircuits() {
await this.fs.ensureDir(this.compiledCircuitsPath);
const patterns = this.circuitsConfig.circuits;
if (!patterns || !patterns.length) {
return;
}
this.logger.info('Compiling circuits...');
await Promise.all(
patterns.map(async pattern => {
const filepaths = await glob(pattern);
await Promise.all(
filepaths
.filter(
filename => path.extname(filename) === Snarks.Extensions.Circom
)
.map(filepath => this.compileCircuit(filepath))
);
})
);
}
compileCircuit(filepath) {
return new Promise((resolve, reject) => {
const output = path.join(
this.compiledCircuitsPath,
`${path.basename(filepath, Snarks.Extensions.Circom)}${
Snarks.Extensions.Json
}`
);
exec(`${Snarks.circomBinary} ${filepath} -o ${output}`, error => {
if (error) {
return reject(error);
}
resolve();
});
});
}
async generateProofs() {
this.logger.info('Generating proofs...');
await Promise.all(
(await this.fs.readdir(this.compiledCircuitsPath))
.filter(filename => path.extname(filename) === Snarks.Extensions.Json)
.map(filename => this.generateProof(filename))
);
}
async generateProof(filename) {
const filepath = path.join(this.compiledCircuitsPath, filename);
const basename = path.basename(filename, Snarks.Extensions.Json);
const circuit = await this.getCircuit(filepath);
const setup = await this.generateSetup(circuit, basename);
const input = this.circuitsConfig.inputs[basename];
if (!input) {
return;
}
const witness = circuit.calculateWitness(input);
const { proof, publicSignals } = zkSnark.original.genProof(
setup.vk_proof,
witness
);
if (!zkSnark.original.isValid(setup.vk_verifier, proof, publicSignals)) {
throw new Error(
`The proof is not valid for ${basename} with inputs: ${JSON.stringify(
input
)}`
);
}
return this.generateVerifier(basename);
}
async getCircuit(filepath) {
const definition = JSON.parse(await this.fs.readFile(filepath, 'utf8'));
return new zkSnark.Circuit(definition);
}
async generateSetup(circuit, basename) {
const setup = zkSnark.original.setup(circuit);
await Promise.all([
this.fs.writeFile(
this.proofFilepath(basename),
JSON.stringify(setup.vk_proof),
'utf8'
),
this.fs.writeFile(
this.verifierFilepath(basename),
JSON.stringify(setup.vk_verifier),
'utf8'
)
]);
return setup;
}
generateVerifier(basename) {
return new Promise((resolve, reject) => {
const source = this.verifierFilepath(basename);
const output = this.verifierContractPath(basename);
exec(
`${Snarks.snarkjsBinary} generateverifier --vk ${source} -v ${output}`,
error => {
if (error) {
return reject(error);
}
resolve();
}
);
});
}
async addVerifiersToContracts(contractFiles) {
(await this.fs.readdir(this.compiledCircuitsPath))
.filter(filename => path.extname(filename) === Snarks.Extensions.Solidity)
.map(filename => this.addVerifierToContracts(filename, contractFiles));
}
addVerifierToContracts(filename, contractFiles) {
filename = path.join(this.compiledCircuitsPath, filename);
contractFiles.push({ path: filename });
}
async compileAndGenerateContracts(contractFiles, callback) {
try {
await this.compileCircuits();
await this.generateProofs();
await this.addVerifiersToContracts(contractFiles);
} catch (error) {
return callback(error);
}
callback(null, contractFiles);
}
}
export default embark => new Snarks(embark);

View File

@ -0,0 +1,285 @@
import { Embark, EmbarkEvents } from 'embark-core';
import { File, Types as FileTypes } from 'embark-utils';
import { Logger } from 'embark-logger';
import { exec } from 'child_process';
import { sync as findUp } from 'find-up';
import globCb from 'glob';
import * as path from 'path';
import * as zkSnark from 'snarkjs';
import { promisify } from 'util';
import { PluginConfig, CircuitConfig, CircuitSetup } from 'embarkjs-snark';
import compileCircuit from "circom";
import SetupTracker from "./setupTracker";
const glob = promisify(globCb);
const LOG_PREFIX = '[embark-snark]: ';
const STACK_NAME = "Snarks";
const MODULE_NAME = "zk";
// tslint:disable-next-line: space-before-function-paren
zkSnark.bigInt.prototype.toJSON = function () {
return this.toString();
};
export class Snarks {
private config: PluginConfig;
private logger: Logger;
private fs: any;
private events: EmbarkEvents;
private circuitSetups: CircuitSetup[] = [];
private setupTracker: any = {};
private outputPath: string;
private buildDir: string;
private contractsBuildDir: string;
constructor(private embark: Embark) {
this.outputPath = embark.config.dappPath(".embark", "snarks");
this.config = embark.pluginConfig;
this.buildDir = embark.config.dappPath(this.config.buildDir) || this.outputPath;
this.contractsBuildDir = embark.config.dappPath(this.config.contractsBuildDir) || this.outputPath;
this.fs = embark.fs;
this.logger = embark.logger;
this.events = embark.events;
if (!this.config) {
this.logger.warn(
`${LOG_PREFIX}Plugin config for 'embark-snark' not found. Please add a 'plugins.embark-snark' section to 'embark.json'.`
);
return;
}
this.setupTracker = new SetupTracker(this.fs, this.outputPath);
this.registerEvents();
this.init();
}
static Extensions = {
Json: '.json',
Circom: '.circom',
VkProof: '.vk_proof',
VkVerifier: '.vk_verifier',
Solidity: '.sol'
};
static snarkjsBinary = findUp('node_modules/.bin/snarkjs', {
cwd: __dirname
});
registerEvents() {
this.embark.registerActionForEvent('pipeline:generateAll:before', { priority: 40 }, this.setupEmbarkJs.bind(this));
this.embark.registerActionForEvent('compiler:contracts:compile:before', this.compileAndGenerateContracts.bind(this));
}
async init() {
const patterns = this.config.circuits;
if (!patterns || !patterns.length) {
this.logger.warn(`${LOG_PREFIX}No circuits defined. Please add 'circuits' glob pattern to embark.json.`);
return;
}
for (const pattern of patterns) {
const filepaths =
(await glob(pattern))
.filter(filename => path.extname(filename) === Snarks.Extensions.Circom);
for (const filepath of filepaths) {
const name = path.basename(filepath, Snarks.Extensions.Circom);
const config = this.circuitsConfig(name);
this.circuitSetups.push({ name, filepath, config });
}
}
if (!this.circuitSetups.length) {
this.logger.warn(`${LOG_PREFIX}No circuits found in ${JSON.stringify(patterns)}. Nothing to do.`);
}
}
getCircularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
}
async setupEmbarkJs(_params, cb) {
const consoleCode = `
const embarkjsSnark = require('embarkjs-snark').default;
const setups = ${JSON.stringify(this.circuitSetups)};
for (const setup of setups) {
if (setup.config.exclude) {
continue;
}
setup.verificationContract = eval(setup.verifierContractName);
}
EmbarkJS.Snark = new embarkjsSnark(setups);
try {
await EmbarkJS.Snark.init();
} catch (err) {
console.error("Failed to initialize EmbarkJS.Snark: ", err);
}
`;
const embarkJsCode = `
const embarkjsSnark = require('embarkjs-snark').default;
const setups = ${JSON.stringify(this.circuitSetups, this.getCircularReplacer())};
const pluginConfig = ${JSON.stringify(this.config)};
EmbarkJS.Snark = new embarkjsSnark(setups, pluginConfig);
EmbarkJS.Snark.init().then(() => {
console.log("EmbarkJS.Snark initialized");
}).catch((err) => {
console.error("Failed to initialize EmbarkJS.Snark: ", err);
});
`;
await this.events.request2("runcode:whitelist", "embarkjs-snark");
await Promise.all([
this.events.request2("embarkjs:plugin:register:custom", STACK_NAME, MODULE_NAME, embarkJsCode),
this.events.request2("runcode:eval", consoleCode)
]);
cb();
}
circuitsConfig(basename) {
const defaultConfig: CircuitConfig = {
protocol: "groth",
exclude: false
};
if (!this.config.circuitsConfig) {
this.config.circuitsConfig = {};
}
if (!this.config.circuitsConfig[basename]) {
this.config.circuitsConfig[basename] = {};
}
return { ...defaultConfig, ...this.config.circuitsConfig[basename] };
}
async compileCircuit(filepath) {
const compiledCircuitPath = path.join(
this.buildDir,
`${path.basename(filepath, Snarks.Extensions.Circom)}${
Snarks.Extensions.Json
}`
);
const compiledCircuit = await compileCircuit(filepath);
await this.fs.writeJson(compiledCircuitPath, compiledCircuit);
return { compiledCircuit, compiledCircuitPath };
}
buildFilepath(basename, ext: string) {
return path.join(
this.buildDir,
`${basename}${ext}`
);
}
makeRelative(filepath): string {
return filepath.replace(`${this.embark.config.dappPath()}/`, "");
}
async generateSetup(name: string, config: CircuitConfig, compiledCircuit: any): Promise<{ vkPath: string, pkPath: string; }> {
const zkCircuit = new zkSnark.Circuit(compiledCircuit);
const circuitHasChanged = await this.setupTracker.hasChanged(name, config, compiledCircuit);
const vkPath = this.buildFilepath(name, Snarks.Extensions.VkVerifier);
const pkPath = this.buildFilepath(name, Snarks.Extensions.VkProof);
const [vkExists, pkExists] = await Promise.all([
this.fs.pathExists(vkPath),
this.fs.pathExists(pkPath)
]);
if (!circuitHasChanged && vkExists && pkExists) {
this.logger.info(`${LOG_PREFIX}Using existing circuit setup for '${name}'`);
return { vkPath, pkPath };
}
this.logger.info(`${LOG_PREFIX}Grab a coffee ☕️ this could take quite a while...`);
const setup = zkSnark[config.protocol].setup(zkCircuit);
delete setup.toxic; // must discard toxic lambda
await Promise.all([
this.fs.writeJson(vkPath, setup.vk_verifier),
this.fs.writeJson(pkPath, setup.vk_proof)
]);
await this.setupTracker.update(name, config, compiledCircuit);
return { vkPath, pkPath };
}
async getCircuit(filepath) {
const definition = JSON.parse(await this.fs.readFile(filepath, 'utf8'));
return new zkSnark.Circuit(definition);
}
generateVerifier(name: string): Promise<{ contractPath: string, contractName: string; }> {
return new Promise((resolve, reject) => {
const vk = this.buildFilepath(name, Snarks.Extensions.VkVerifier);
const contractPath = path.join(
this.contractsBuildDir,
`${name}${Snarks.Extensions.Solidity}`
);
exec(
`${Snarks.snarkjsBinary} generateverifier --vk ${vk} -v ${contractPath}`,
async error => {
if (error) {
return reject(error);
}
const circuitName = name.charAt(0).toUpperCase() + name.substr(1).toLowerCase();
const contractName = `${circuitName}Verifier`;
let solidityOutput = await this.fs.readFile(contractPath, 'utf8');
solidityOutput = solidityOutput.replace("contract Verifier", `contract ${contractName}`);
await this.fs.writeFile(
contractPath,
solidityOutput,
'utf8'
);
resolve({ contractPath, contractName });
}
);
});
}
async compileAndGenerateContracts(contractFiles: File[], callback) {
try {
const circuits = this.circuitSetups
.filter((circuit) => circuit.config.exclude !== true);
await Promise.all([
await this.fs.ensureDir(this.buildDir),
await this.fs.ensureDir(this.contractsBuildDir)
]);
for (const circuit of circuits) {
this.logger.info(`${LOG_PREFIX}Compiling circuit '${circuit.name}'...`);
const { compiledCircuit, compiledCircuitPath } = await this.compileCircuit(circuit.filepath);
circuit.compiledCircuit = this.makeRelative(compiledCircuitPath);
this.logger.info(`${LOG_PREFIX}Generating '${circuit.name}' zkSnarks setup...`);
const { vkPath, pkPath } = await this.generateSetup(circuit.name, circuit.config, compiledCircuit);
circuit.verificationKey = this.makeRelative(vkPath);
circuit.provingKey = this.makeRelative(pkPath);
this.logger.info(`${LOG_PREFIX}Generating '${circuit.name}' verification contract...`);
const { contractPath, contractName } = await this.generateVerifier(circuit.name);
const contractFile = new File({
type: FileTypes.dappFile,
path: this.makeRelative(contractPath)
});
contractFiles.push(contractFile);
circuit.verifierContractName = contractName;
}
} catch (error) {
return callback(error);
}
callback(null, contractFiles);
}
}
export default embark => new Snarks(embark);

View File

@ -0,0 +1,54 @@
import * as path from 'path';
import Hasher from "node-object-hash";
import { CircuitConfig } from "embarkjs-snark";
export default class SetupTracker {
private _lastRun: any = null;
private lastRunPath: string;
private hasher: any;
constructor(private fs: any, private trackerPath: string) {
this.fs.ensureDirSync(this.trackerPath);
this.lastRunPath = path.join(this.trackerPath, "setupTracker.json");
this.hasher = Hasher({ coerce: false });
}
get lastRun(): any {
return (async () => {
if (!this._lastRun) {
let lastRun;
try {
lastRun = await this.fs.readJson(this.lastRunPath);
} catch (_err) {
lastRun = {};
this.lastRun = lastRun;
}
this._lastRun = lastRun;
}
return this._lastRun;
})();
}
// There is no way to avoid this being a fire and forget.
// IOW, calling functions *cannot* know when this function has completed.
// In general, we shouldn't worry as the in-memory copy is updated synchronously
// and the persisted data in the file is not required until the next run.
set lastRun(lastRun: any) {
this._lastRun = lastRun;
this.fs.writeJson(this.lastRunPath, lastRun);
}
hash(config: CircuitConfig, compiledCircuit: any) {
return this.hasher.hash(JSON.stringify(config) + JSON.stringify(compiledCircuit));
}
async hasChanged(circuitName: string, config: CircuitConfig, compiledCircuit: any) {
const lastRun = await this.lastRun;
return lastRun[circuitName.toLowerCase()] !== this.hash(config, compiledCircuit);
}
async update(circuitName, config: CircuitConfig, compiledCircuit: any) {
const lastRun = await this.lastRun;
lastRun[circuitName] = this.hash(config, compiledCircuit);
this.lastRun = lastRun;
}
}

View File

@ -27,30 +27,15 @@ describe('embark-snark', () => {
describe('bigInt patching', () => {
it("should patch the prototype of snarkjs' bigInt constructor with a toJSON method", () => {
expect(snarkjs.bigInt.prototype.toJSON).toBeUndefined();
({ Snarks, default: plugin } = require('../src/index.js'));
({ Snarks, default: plugin } = require('../dist/index.js'));
expect(new snarkjs.bigInt().toJSON()).toBe(someString);
});
});
});
describe('plugin', () => {
let dappPath, embark;
beforeAll(() => {
dappPath = jest.fn(() => {});
embark = { config: { dappPath } };
});
it('should call the implementation (Snarks class) with its argument', () => {
plugin(embark);
expect(dappPath).toHaveBeenCalled();
});
});
describe('Snarks class', () => {
describe('static properties', () => {
it('should have the expected static properties', () => {
expect(Snarks.circomBinary).toBe(somePath);
expect(Snarks.snarkjsBinary).toBe(somePath);
});
});
@ -60,7 +45,9 @@ describe('embark-snark', () => {
beforeAll(() => {
dappPath = jest.fn(() => somePath);
fs = {};
fs = {
ensureDirSync: jest.fn(() => {})
};
logger = {};
pluginConfig = {};
@ -85,6 +72,11 @@ describe('embark-snark', () => {
Snarks.prototype.registerEvents.mockRestore();
});
it('should call the implementation (Snarks class) with its argument', () => {
plugin(embark);
expect(dappPath).toHaveBeenCalled();
});
it('should setup the expected instance properties', () => {
const snarks = new Snarks(embark);
expect(snarks.embark).toBe(embark);

View File

@ -8,5 +8,19 @@
"extends": "../../../tsconfig.base.json",
"include": [
"src/**/*"
],
"references": [
{
"path": "../../core/core"
},
{
"path": "../../core/logger"
},
{
"path": "../../core/utils"
},
{
"path": "../../embarkjs/snark"
}
]
}

View File

@ -0,0 +1,3 @@
{
"extends": "../../../tslint.json"
}

View File

@ -40,6 +40,9 @@
{
"path": "packages/embarkjs/ipfs"
},
{
"path": "packages/embarkjs/snark"
},
{
"path": "packages/embarkjs/swarm"
},

223
yarn.lock
View File

@ -1301,6 +1301,22 @@
core-js "^2.6.5"
regenerator-runtime "^0.13.2"
"@babel/runtime-corejs3@7.7.4":
version "7.7.4"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.7.4.tgz#f861adc1cecb9903dfd66ea97917f02ff8d79888"
integrity sha512-BBIEhzk8McXDcB3IbOi8zQPzzINUp4zcLesVlBSOcyGhzPUU8Xezk5GAG7Sy5GVhGmAO0zGd2qRSeY2g4Obqxw==
dependencies:
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.2"
"@babel/runtime-corejs3@7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.8.3.tgz#a2445836d0699e5ba77eea2c790ad9ea51e2cd27"
integrity sha512-lrIU4aVbmlM/wQPzhEvzvNJskKyYptuXb0fGC0lTQTupTOYtR2Vqbu6/jf8vTr4M8Wt1nIzxVrSvPI5qESa/xA==
dependencies:
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.2"
"@babel/runtime-corejs3@7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.8.4.tgz#ccc4e042e2fae419c67fa709567e5d2179ed3940"
@ -3767,6 +3783,11 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.3.tgz#856c99cdc1551d22c22b18b5402719affec9839a"
integrity sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw==
"@types/i18n@0.8.3":
version "0.8.3"
resolved "https://registry.yarnpkg.com/@types/i18n/-/i18n-0.8.3.tgz#f602164f2fae486ea87590f6be5d6dd5db1664e6"
integrity sha512-JyNZyqamS3jfJ/qTXkAIAtBKlapN5OrEVosdde6LT41Gf209NzXppDVoNkn1pABGZbSnT8dgEgiuTFBIgxweIQ==
"@types/i18n@0.8.6":
version "0.8.6"
resolved "https://registry.yarnpkg.com/@types/i18n/-/i18n-0.8.6.tgz#10f9761fbdfe077fca57d979e8e8a97eac028485"
@ -4368,6 +4389,11 @@ acorn-jsx@^5.1.0:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384"
integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==
acorn-jsx@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
acorn-node@^1.3.0:
version "1.8.2"
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
@ -4412,6 +4438,11 @@ acorn@^7.1.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==
acorn@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf"
integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==
address@1.1.2, address@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
@ -4958,6 +4989,13 @@ async-each@^1.0.1:
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
async-es@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/async-es/-/async-es-2.6.1.tgz#72e763072a4bb536e6834b4481d965617b528fc3"
integrity sha512-3TRUpnT+8BEgZagVqDstkJTEAw45paBCfNG5W5WX2eimZSXt+K5RnjjB2KlYfx1rgn/fu7wPMq1BlQmwg8yeWQ==
dependencies:
lodash-es "^4.17.10"
async-es@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/async-es/-/async-es-3.2.0.tgz#8837aa12f675de80fac56b94a4b4cef515343de3"
@ -4988,7 +5026,7 @@ async-iterator-to-pull-stream@^1.3.0:
get-iterator "^1.0.2"
pull-stream-to-async-iterator "^1.0.1"
async-limiter@~1.0.0:
async-limiter@^1.0.0, async-limiter@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
@ -5005,6 +5043,13 @@ async@1.x, async@^1.4.2, async@~1.5.2:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
async@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
dependencies:
lodash "^4.17.10"
async@3.2.0, async@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
@ -8475,6 +8520,61 @@ elliptic@^6.0.0, elliptic@^6.4.0, elliptic@^6.4.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
embark-core@^5.1.1-nightly.2:
version "5.2.3"
resolved "https://registry.yarnpkg.com/embark-core/-/embark-core-5.2.3.tgz#bbf03bf43eb61a2f83fbc0841a167bf0dc59fbef"
integrity sha512-/q5SWxyg8VRKHjAeO3cdHfH2GDPClAcy/zExvX/88N4R348uXFaHV+1Ts4BAsrIgjuCkj2OlRyCvOR42GgXJyA==
dependencies:
"@babel/runtime-corejs3" "7.7.4"
"@types/deep-equal" "1.0.1"
async "2.6.1"
colors "1.3.2"
core-js "3.4.3"
decompress "4.2.0"
deep-equal "1.0.1"
embark-i18n "^5.2.3"
embark-logger "^5.2.3"
embark-utils "^5.2.3"
find-up "4.1.0"
flatted "0.2.3"
fs-extra "8.1.0"
globule "1.2.1"
hosted-git-info "2.8.4"
lodash.clonedeep "4.5.0"
node-ipc "9.1.1"
parse-json "4.0.0"
request "2.88.0"
semver "5.6.0"
shelljs "0.8.3"
uuid "3.3.2"
web3 "1.2.6"
web3-utils "1.2.6"
window-size "1.1.1"
embark-i18n@^5.2.3:
version "5.2.3"
resolved "https://registry.yarnpkg.com/embark-i18n/-/embark-i18n-5.2.3.tgz#f944e9821fdc67d3a3cf7d15cd19796ace4d95df"
integrity sha512-qC2cRR8IohWs2OS1XWRILwn+j7yOS0TT8vrup/F0ns9ynP11V0trPPc8YQRmIofaJo51PPOQmPqAbu/H4ayraw==
dependencies:
"@babel/runtime-corejs3" "7.7.4"
"@types/i18n" "0.8.3"
colors "1.3.2"
core-js "3.4.3"
i18n "0.8.3"
os-locale "4.0.0"
embark-logger@^5.1.1-nightly.2, embark-logger@^5.2.3:
version "5.2.3"
resolved "https://registry.yarnpkg.com/embark-logger/-/embark-logger-5.2.3.tgz#94109293bd734289032a53cd31cd438387eca84c"
integrity sha512-rx7U3Nl3DqvWrDtR5mcvsF2iB8oxlhlzuO7CpDgHAMw0pexR3vB+hUxdB4daE3akouFpI3SR9XSm6LLIOmjhhA==
dependencies:
"@babel/runtime-corejs3" "7.8.3"
async "2.6.1"
colors "1.4.0"
core-js "3.6.4"
date-and-time "0.12.0"
fs-extra "8.1.0"
embark-test-contract-0@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/embark-test-contract-0/-/embark-test-contract-0-0.0.2.tgz#53913fb40e3df4b816a7bef9f00a5f78fa3d56b4"
@ -8487,6 +8587,52 @@ embark-test-contract-1@0.0.1, embark-test-contract-1@^0.0.1:
resolved "https://registry.yarnpkg.com/embark-test-contract-1/-/embark-test-contract-1-0.0.1.tgz#802b84150e8038fef6681a3f23b6f4c0dc5b3e80"
integrity sha512-yFaXMOXOMfYRNFOKEspdXrZzyovceWedtegHtbfs8RZWRzRYY+v6ZDtGm13TRJt3Qt4VZhKky0l7G/SHZMGHCA==
embark-utils@^5.1.1-nightly.2, embark-utils@^5.2.3:
version "5.2.3"
resolved "https://registry.yarnpkg.com/embark-utils/-/embark-utils-5.2.3.tgz#e227f7a16bb7adfba56bd8f87222614a47e67ebf"
integrity sha512-ogKbimjf2ZgaJCnrkZ4p6rRXlvarDK1juPrSwT5xRbYIuAbJTOVa+6pRmVDoWJqVOkSmZRtM333sZbPtAgu7Iw==
dependencies:
"@babel/runtime-corejs3" "7.7.4"
"@embarklabs/ethereumjs-wallet" "0.6.4"
"@types/follow-redirects" "1.5.0"
"@types/fs-extra" "7.0.0"
"@types/node" "12.7.8"
"@types/pretty-ms" "5.0.1"
async "2.6.1"
bip39 "3.0.2"
clipboardy "1.2.3"
colors "1.3.2"
core-js "3.4.3"
embark-i18n "^5.2.3"
embark-logger "^5.2.3"
find-up "2.1.0"
follow-redirects "1.9.0"
fs-extra "8.1.0"
fuzzy "0.1.3"
glob "7.1.4"
globule "1.2.1"
merge "1.2.1"
multihashes "0.4.14"
ora "4.0.3"
pretty-ms "5.1.0"
propose "0.0.5"
shelljs "0.8.3"
web3 "1.2.6"
web3-eth "1.2.6"
web3-eth-abi "1.2.6"
ws "7.1.2"
embarkjs@^5.1.1-nightly.2:
version "5.2.3"
resolved "https://registry.yarnpkg.com/embarkjs/-/embarkjs-5.2.3.tgz#70dfb779e1537e5174fb0062e802d98c6f1e1bd6"
integrity sha512-T95mThp95BCDzmTSad0v+kxreLxVPDFn6lf0unjBpkyCWpySg7LX9GjoW36Dhb0SE+sDLvyuqJNjigp05zlXjw==
dependencies:
"@babel/runtime-corejs3" "7.7.4"
async "2.6.1"
async-es "2.6.1"
colors "1.3.2"
core-js "3.4.3"
emoji-regex@^7.0.1, emoji-regex@^7.0.2, emoji-regex@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@ -9042,7 +9188,7 @@ eslint-utils@^1.3.1:
dependencies:
eslint-visitor-keys "^1.0.0"
eslint-utils@^1.4.3:
eslint-utils@^1.4.2, eslint-utils@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==
@ -9054,6 +9200,49 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
eslint@6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.2.2.tgz#03298280e7750d81fcd31431f3d333e43d93f24f"
integrity sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw==
dependencies:
"@babel/code-frame" "^7.0.0"
ajv "^6.10.0"
chalk "^2.1.0"
cross-spawn "^6.0.5"
debug "^4.0.1"
doctrine "^3.0.0"
eslint-scope "^5.0.0"
eslint-utils "^1.4.2"
eslint-visitor-keys "^1.1.0"
espree "^6.1.1"
esquery "^1.0.1"
esutils "^2.0.2"
file-entry-cache "^5.0.1"
functional-red-black-tree "^1.0.1"
glob-parent "^5.0.0"
globals "^11.7.0"
ignore "^4.0.6"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
inquirer "^6.4.1"
is-glob "^4.0.0"
js-yaml "^3.13.1"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.3.0"
lodash "^4.17.14"
minimatch "^3.0.4"
mkdirp "^0.5.1"
natural-compare "^1.4.0"
optionator "^0.8.2"
progress "^2.0.0"
regexpp "^2.0.1"
semver "^6.1.2"
strip-ansi "^5.2.0"
strip-json-comments "^3.0.1"
table "^5.2.3"
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
eslint@6.8.0, eslint@^6.6.0:
version "6.8.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
@ -9197,6 +9386,15 @@ espree@^5.0.1:
acorn-jsx "^5.0.0"
eslint-visitor-keys "^1.0.0"
espree@^6.1.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==
dependencies:
acorn "^7.1.1"
acorn-jsx "^5.2.0"
eslint-visitor-keys "^1.1.0"
espree@^6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d"
@ -11831,7 +12029,7 @@ inquirer@^0.12.0:
strip-ansi "^3.0.0"
through "^2.3.6"
inquirer@^6.2.0, inquirer@^6.2.2:
inquirer@^6.2.0, inquirer@^6.2.2, inquirer@^6.4.1:
version "6.5.2"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==
@ -14277,6 +14475,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
lodash-es@^4.17.10:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
lodash._reinterpolate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@ -14392,7 +14595,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10:
"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@ -15680,6 +15883,11 @@ node-notifier@^6.0.0:
shellwords "^0.1.1"
which "^1.3.1"
node-object-hash@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-2.0.0.tgz#9971fcdb7d254f05016bd9ccf508352bee11116b"
integrity sha512-VZR0zroAusy1ETZMZiGeLkdu50LGjG5U1KHZqTruqtTyQ2wfWhHG2Ow4nsUbfTFGlaREgNHcCWoM/OzEm6p+NQ==
node-object-hash@^1.2.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94"
@ -23536,6 +23744,13 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
ws@7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.1.2.tgz#c672d1629de8bb27a9699eb599be47aeeedd8f73"
integrity sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==
dependencies:
async-limiter "^1.0.0"
ws@7.2.2, ws@^7.0.0:
version "7.2.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.2.tgz#36df62f68f0d1a6ec66d3f880a02476f3a81f24f"