Skip to content

Commit c1129dc

Browse files
emizzle0x-r4bbit
authored andcommitted
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.
1 parent 444b9ea commit c1129dc

28 files changed

+1049
-257
lines changed

packages/core/code-runner/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class CodeRunner {
4646
private whitelistVar(varName: string, cb = () => { }) {
4747
// @ts-ignore
4848
this.vm._options.require.external.push(varName); // @ts-ignore
49+
cb();
4950
}
5051

5152
private registerVar(varName: string, code: any, cb = () => { }) {

packages/core/core/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface Contract {
1010
deployedAddress: string;
1111
className: string;
1212
silent?: boolean;
13+
methods: any;
1314
}
1415

1516
export interface ContractConfig {
@@ -100,6 +101,8 @@ export interface Configuration {
100101
};
101102
plugins: EmbarkPlugins;
102103
reloadConfig(): void;
104+
105+
dappPath(...args: string[]): string;
103106
}
104107

105108
type ActionCallback<T> = (params: any, cb: Callback<T>) => void;
@@ -108,6 +111,7 @@ import { Logger } from 'embark-logger';
108111

109112
export interface Embark {
110113
env: string;
114+
pluginConfig: any;
111115
events: EmbarkEvents;
112116
plugins: EmbarkPlugins;
113117
registerAPICall(method: string, endpoint: string, cb: (...args: any[]) => void): void;
@@ -118,7 +122,7 @@ export interface Embark {
118122
currentContext: string[];
119123
registerActionForEvent<T>(
120124
name: string,
121-
options?: ActionCallback<T> | { priority: number },
125+
options?: ActionCallback<T> | { priority: number; },
122126
action?: ActionCallback<T>,
123127
): void;
124128
}

packages/embarkjs/snark/.babelrc.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* global module require */
2+
3+
const cloneDeep = require('lodash.clonedeep');
4+
5+
module.exports = (api) => {
6+
const env = api.env();
7+
8+
const base = {};
9+
10+
const browser = cloneDeep(base);
11+
Object.assign(browser, {
12+
ignore: [
13+
'src/node'
14+
]
15+
});
16+
17+
const node = cloneDeep(base);
18+
19+
const test = cloneDeep(node);
20+
21+
switch (env) {
22+
case 'browser':
23+
return browser;
24+
case 'node':
25+
return node;
26+
case 'test':
27+
return test;
28+
default:
29+
return base;
30+
}
31+
};

packages/embarkjs/snark/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build-test

packages/embarkjs/snark/.npmrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
engine-strict = true
2+
package-lock = false
3+
save-exact = true
4+
scripts-prepend-node-path = true

packages/embarkjs/snark/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# `embarkjs-snark`
2+
3+
> zkSnarks plugin for embarkjs
4+
5+
Exposes functions for interaction with zkSNARKS. See [`embark-snark` README](../../plugins/snark/README.md) for more information.
6+
7+
Visit [embark.status.im](https://embark.status.im/) to get started with
8+
[Embark](https://github.com/embark-framework/embark).

packages/embarkjs/snark/package.json

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
"name": "embarkjs-snark",
3+
"version": "5.1.1-nightly.2",
4+
"author": "Iuri Matias <iuri.matias@gmail.com>",
5+
"contributors": [
6+
"Eric Mastro <eric.mastro@gmail.com> (https://github.com/emizzle/)"
7+
],
8+
"description": "zkSnarks plugin for embarkjs",
9+
"homepage": "https://github.com/embark-framework/embark/tree/master/packages/embarkjs/snark#readme",
10+
"bugs": "https://github.com/embark-framework/embark/issues",
11+
"keywords": [
12+
"blockchain",
13+
"dapps",
14+
"ethereum",
15+
"zksnarks",
16+
"snarks",
17+
"zero knowledge"
18+
],
19+
"license": "MIT",
20+
"repository": {
21+
"directory": "packages/embarkjs/snark",
22+
"type": "git",
23+
"url": "https://github.com/embark-framework/embark.git"
24+
},
25+
"main": "./dist/node/index.js",
26+
"types": "./dist/index.d.ts",
27+
"browser": {
28+
"./dist/node/index.js": "./dist/browser/index.js",
29+
"./dist/browser/embarkjs-snark.js": "./dist/browser/browser/embarkjs-snark.js"
30+
},
31+
"browserslist": [
32+
"last 1 version",
33+
"not dead",
34+
"> 0.2%"
35+
],
36+
"files": [
37+
"dist"
38+
],
39+
"embark-collective": {
40+
"build:browser": true,
41+
"build:node": true,
42+
"typecheck": {
43+
"compilerOptions": {
44+
"module": "ESNext"
45+
}
46+
}
47+
},
48+
"scripts": {
49+
"_build": "npm run solo -- build",
50+
"_typecheck": "npm run solo -- typecheck",
51+
"ci": "npm run qa",
52+
"clean": "npm run reset",
53+
"lint": "npm-run-all lint:*",
54+
"// lint:js": "eslint test/",
55+
"lint:ts": "tslint -c tslint.json \"src/**/*.ts\"",
56+
"qa": "npm-run-all _typecheck _build",
57+
"reset": "npx rimraf coverage dist embarkjs-*.tgz package",
58+
"solo": "embark-solo"
59+
},
60+
"dependencies": {
61+
"@babel/runtime-corejs3": "7.8.4",
62+
"core-js": "3.6.4",
63+
"embark-core": "^5.1.1-nightly.2",
64+
"embarkjs": "^5.1.1-nightly.2",
65+
"fs-extra": "8.1.0",
66+
"snarkjs": "0.1.20"
67+
},
68+
"devDependencies": {
69+
"embark-solo": "^5.1.1-nightly.2",
70+
"eslint": "6.2.2",
71+
"eslint-config-prettier": "6.1.0",
72+
"eslint-plugin-prettier": "3.1.0",
73+
"lodash.clonedeep": "4.5.0",
74+
"npm-run-all": "4.1.5",
75+
"rimraf": "3.0.0",
76+
"tslint": "5.20.1",
77+
"typescript": "3.7.2"
78+
},
79+
"engines": {
80+
"node": ">=10.17.0",
81+
"npm": ">=6.11.3",
82+
"yarn": ">=1.19.1"
83+
}
84+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* global EmbarkJS */
2+
import { CircuitSetup, PluginConfig } from "..";
3+
import Circuit from "../circuit";
4+
5+
export default class EmbarkJsSnark {
6+
[key: string]: any;
7+
private buildDir: string;
8+
private buildDirUrl: string;
9+
private contractsJsonDirUrl: string;
10+
constructor(private setups: CircuitSetup[], private config: PluginConfig) {
11+
this.buildDir = this.config.buildDir || "public/snarks/";
12+
this.buildDirUrl = this.config.buildDirUrl || "/snarks/";
13+
this.contractsJsonDirUrl = this.config.contractsJsonDirUrl || "/snarks/contracts/";
14+
}
15+
16+
buildUrl(filepath: string) {
17+
return filepath.replace(this.buildDir || "public/", this.buildDirUrl || "/");
18+
}
19+
20+
public async init() {
21+
for (const setup of this.setups) {
22+
if (setup.config.exclude) {
23+
continue;
24+
}
25+
if (!setup.provingKey) {
26+
throw new Error("Error getting proving key: path not provided.");
27+
}
28+
if (!setup.verificationKey) {
29+
throw new Error("Error getting verification key: path not provided.");
30+
}
31+
if (!setup.compiledCircuit) {
32+
throw new Error("Error getting compiled circuit: path not provided.");
33+
}
34+
setup.compiledCircuit = await (await fetch(this.buildUrl(setup.compiledCircuit))).json();
35+
setup.provingKey = await (await fetch(this.buildUrl(setup.provingKey))).json();
36+
setup.verificationKey = await (await fetch(this.buildUrl(setup.verificationKey))).json();
37+
const verifierContractJson = await (await fetch(`${this.contractsJsonDirUrl}${setup.verifierContractName}.json`)).json();
38+
setup.verificationContract = new EmbarkJS.Blockchain.Contract(verifierContractJson);
39+
const nameTitleCase = setup.name.charAt(0).toUpperCase() + setup.name.substr(1).toLowerCase();
40+
this[nameTitleCase] = new Circuit(setup);
41+
}
42+
}
43+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { CircuitSetup } from ".";
2+
import * as snarkjs from "snarkjs";
3+
const { unstringifyBigInts } = require("snarkjs/src/stringifybigint");
4+
5+
const LOG_PREFIX = "[embarkjs-snark]: ";
6+
7+
export default class Circuit {
8+
constructor(private setup: CircuitSetup) {
9+
this.setup.provingKey = unstringifyBigInts(this.setup.provingKey);
10+
this.setup.verificationKey = unstringifyBigInts(this.setup.verificationKey);
11+
}
12+
13+
/**
14+
* Given public signals and a proof to prove those public signals can be verified,
15+
* generates an array of inputs the can be used to call the verifyProof function
16+
* in the verification contract (Solidity).
17+
*
18+
* @remarks Derived from the {@link https://github.com/iden3/snarkjs/blob/f2e5bc56b33aedbbbf7fed38b3f234d3d2b1adb7/cli.js#L365-L392 | "generatecall" snarkjs cli function}
19+
*
20+
* @param publicSignals - public inputs to be verified using the proof
21+
* @param proof - the proof used to verify the inputs are valid
22+
* @returns an array of solidity inputs that can be used to call the "verifyProof"
23+
* function of the deployed vertificadtion contract
24+
*/
25+
private generateSolidityInputs(publicSignals, proof): string[] {
26+
publicSignals = unstringifyBigInts(publicSignals);
27+
proof = unstringifyBigInts(proof);
28+
29+
const p256 = (n) => {
30+
let nstr = n.toString(16);
31+
while (nstr.length < 64) { nstr = "0" + nstr; }
32+
nstr = `0x${nstr}`;
33+
return nstr;
34+
};
35+
36+
let inputs = "";
37+
for (const publicSignal of publicSignals) {
38+
if (inputs !== "") { inputs = inputs + ","; }
39+
inputs = inputs + p256(publicSignal);
40+
}
41+
42+
let S;
43+
if ((typeof proof.protocol === "undefined") || (proof.protocol === "original")) {
44+
S = [
45+
[proof.pi_a[0], proof.pi_a[1]],
46+
[proof.pi_ap[0], proof.pi_ap[1]],
47+
[[proof.pi_b[0][1], proof.pi_b[0][0]], [proof.pi_b[1][1], proof.pi_b[1][0]]],
48+
[proof.pi_bp[0], proof.pi_bp[1]],
49+
[proof.pi_c[0], proof.pi_c[1]],
50+
[proof.pi_cp[0], proof.pi_cp[1]],
51+
[proof.pi_h[0], proof.pi_h[1]],
52+
[proof.pi_kp[0], proof.pi_kp[1]]
53+
];
54+
} else if ((proof.protocol === "groth") || (proof.protocol === "kimleeoh")) {
55+
S = [
56+
[proof.pi_a[0], proof.pi_a[1]],
57+
[[proof.pi_b[0][1], proof.pi_b[0][0]], [proof.pi_b[1][1], proof.pi_b[1][0]]],
58+
[proof.pi_c[0], proof.pi_c[1]]
59+
];
60+
} else {
61+
throw new Error("InvalidProof");
62+
}
63+
64+
const two56ify = (arr: any[]): any[] => {
65+
return arr.map(n => {
66+
if (Array.isArray(n)) {
67+
return two56ify(n);
68+
}
69+
return p256(n);
70+
});
71+
};
72+
S = two56ify(S);
73+
S.push([inputs]);
74+
return S;
75+
}
76+
77+
public async calculate(inputs: any) {
78+
const circuit = new snarkjs.Circuit(this.setup.compiledCircuit);
79+
const witness = circuit.calculateWitness(inputs);
80+
const { proof, publicSignals } = snarkjs[this.setup.config.protocol].genProof(
81+
this.setup.provingKey,
82+
witness
83+
);
84+
85+
return { proof, publicSignals };
86+
}
87+
88+
public async verify(inputs: any) {
89+
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.`);
90+
if (!this.setup.verificationContract) {
91+
return console.error(`Error verifying inputs, verification contract for '${this.setup.name}' not found.`);
92+
}
93+
94+
console.log(`${LOG_PREFIX}Calculating witness and generating proof...`);
95+
const { proof, publicSignals } = await this.calculate(inputs);
96+
const solidityInputs = this.generateSolidityInputs(publicSignals, proof);
97+
98+
console.log(`${LOG_PREFIX}Verifying inputs on chain...`);
99+
return await this.setup.verificationContract.methods.verifyProof(...solidityInputs).call();
100+
}
101+
102+
public async verifyOffChain(inputs) {
103+
console.log(`${LOG_PREFIX}Calculating witness and generating proof...`);
104+
const { proof, publicSignals } = await this.calculate(inputs);
105+
106+
console.log(`${LOG_PREFIX}Verifying inputs off chain...`);
107+
return snarkjs[this.setup.config.protocol].isValid(this.setup.verificationKey, proof, publicSignals);
108+
}
109+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Circuit from "./circuit";
2+
import * as fs from "fs-extra";
3+
import { CircuitSetup } from ".";
4+
5+
export default class EmbarkJsSnark {
6+
[key: string]: any;
7+
constructor(private setups: CircuitSetup[]) { }
8+
9+
public async init() {
10+
11+
for (const setup of this.setups) {
12+
if (setup.config.exclude) {
13+
continue;
14+
}
15+
if (!setup.provingKey) {
16+
throw new Error("Error getting proving key: path not provided.");
17+
}
18+
if (!setup.verificationKey) {
19+
throw new Error("Error getting verification key: path not provided.");
20+
}
21+
if (!setup.compiledCircuit) {
22+
throw new Error("Error getting compiled circuit: path not provided.");
23+
}
24+
setup.compiledCircuit = await fs.readJson(setup.compiledCircuit);
25+
setup.provingKey = await fs.readJson(setup.provingKey);
26+
setup.verificationKey = await fs.readJson(setup.verificationKey);
27+
const nameTitleCase = setup.name.charAt(0).toUpperCase() + setup.name.substr(1).toLowerCase();
28+
this[nameTitleCase] = new Circuit(setup);
29+
}
30+
}
31+
}

packages/embarkjs/snark/src/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import EmbarkJsSnark from "./embarkjs-snark";
2+
export default EmbarkJsSnark;
3+
export type SnarksProtocol = 'original' | 'groth' | 'kimleeoh';
4+
5+
export interface CircuitConfig {
6+
protocol?: SnarksProtocol;
7+
exclude?: boolean;
8+
}
9+
10+
export interface PluginConfig {
11+
circuits: string[];
12+
circuitsConfig: {
13+
[key: string]: CircuitConfig;
14+
};
15+
buildDir: string;
16+
buildDirUrl: string;
17+
contractsBuildDir: string;
18+
contractsJsonDirUrl: string;
19+
}
20+
21+
export interface CircuitSetup {
22+
provingKey?: string;
23+
verificationKey?: string;
24+
compiledCircuit?: string;
25+
config: CircuitConfig;
26+
filepath: string;
27+
name: string;
28+
verifierContractName?: string;
29+
verificationContract?: any;
30+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare const EmbarkJS: any;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import * as embarkSnark from '..';
2+
3+
module.exports = embarkSnark;

0 commit comments

Comments
 (0)