Skip to content

Commit

Permalink
feat(@embark/snarks): Allow embark-snark to be used in the dapp
Browse files Browse the repository at this point in the history
`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.
  • Loading branch information
emizzle authored and 0x-r4bbit committed Mar 18, 2020
1 parent 444b9ea commit c1129dc
Show file tree
Hide file tree
Showing 28 changed files with 1,049 additions and 257 deletions.
1 change: 1 addition & 0 deletions packages/core/code-runner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => { }) {
Expand Down
6 changes: 5 additions & 1 deletion packages/core/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface Contract {
deployedAddress: string;
className: string;
silent?: boolean;
methods: any;
}

export interface ContractConfig {
Expand Down Expand Up @@ -100,6 +101,8 @@ export interface Configuration {
};
plugins: EmbarkPlugins;
reloadConfig(): void;

dappPath(...args: string[]): string;
}

type ActionCallback<T> = (params: any, cb: Callback<T>) => void;
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down
31 changes: 31 additions & 0 deletions packages/embarkjs/snark/.babelrc.js
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions packages/embarkjs/snark/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build-test
4 changes: 4 additions & 0 deletions packages/embarkjs/snark/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
engine-strict = true
package-lock = false
save-exact = true
scripts-prepend-node-path = true
8 changes: 8 additions & 0 deletions packages/embarkjs/snark/README.md
Original file line number Diff line number Diff line change
@@ -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).
84 changes: 84 additions & 0 deletions packages/embarkjs/snark/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
43 changes: 43 additions & 0 deletions packages/embarkjs/snark/src/browser/embarkjs-snark.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
109 changes: 109 additions & 0 deletions packages/embarkjs/snark/src/circuit.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
31 changes: 31 additions & 0 deletions packages/embarkjs/snark/src/embarkjs-snark.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
30 changes: 30 additions & 0 deletions packages/embarkjs/snark/src/index.ts
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions packages/embarkjs/snark/src/interfaces.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare const EmbarkJS: any;
3 changes: 3 additions & 0 deletions packages/embarkjs/snark/src/node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as embarkSnark from '..';

module.exports = embarkSnark;
Loading

0 comments on commit c1129dc

Please sign in to comment.