Skip to content

Commit 40e4769

Browse files
committed
refactor(proof): move snarkjs code inside proof pkg
1 parent c1466d1 commit 40e4769

14 files changed

+472
-205
lines changed

packages/proof/package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,17 @@
3535
"access": "public"
3636
},
3737
"devDependencies": {
38+
"@iden3/binfileutils": "0.0.11",
3839
"@rollup/plugin-commonjs": "^24.1.0",
3940
"@rollup/plugin-json": "^5.0.1",
4041
"@rollup/plugin-node-resolve": "^15.0.2",
41-
"@rollup/plugin-replace": "^5.0.3",
4242
"@rollup/plugin-virtual": "^3.0.2",
43-
"ffjavascript": "^0.2.54",
43+
"fastfile": "0.0.20",
4444
"poseidon-lite": "^0.2.0",
4545
"rimraf": "^5.0.5",
4646
"rollup": "^4.0.2",
4747
"rollup-plugin-cleanup": "^3.2.1",
48-
"rollup-plugin-typescript2": "^0.31.2",
49-
"snarkjs": "0.7.1"
48+
"rollup-plugin-typescript2": "^0.31.2"
5049
},
5150
"peerDependencies": {
5251
"@semaphore-protocol/group": "3.12.3",
@@ -57,6 +56,8 @@
5756
"@ethersproject/bytes": "^5.7.0",
5857
"@ethersproject/keccak256": "^5.7.0",
5958
"@ethersproject/strings": "^5.5.0",
60-
"@zk-kit/incremental-merkle-tree": "0.4.3"
59+
"@zk-kit/incremental-merkle-tree": "0.4.3",
60+
"circom_runtime": "0.1.24",
61+
"ffjavascript": "0.2.60"
6162
}
6263
}

packages/proof/rollup.browser.config.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import commonjs from "@rollup/plugin-commonjs"
22
import json from "@rollup/plugin-json"
33
import { nodeResolve } from "@rollup/plugin-node-resolve"
4-
import replace from "@rollup/plugin-replace"
54
import virtual from "@rollup/plugin-virtual"
65
import * as fs from "fs"
76
import cleanup from "rollup-plugin-cleanup"
@@ -47,24 +46,11 @@ export default {
4746
}),
4847
virtual({
4948
fs: empty,
50-
os: empty,
51-
crypto: empty,
52-
readline: empty,
53-
ejs: empty,
54-
events: empty,
55-
stream: empty,
56-
util: empty,
5749
constants
5850
}),
59-
nodeResolve({
60-
browser: true,
61-
preferBuiltins: false,
62-
exportConditions: ["browser", "default", "module", "require"]
63-
}),
64-
commonjs(),
65-
replace({
66-
preventAssignment: false,
67-
"process.browser": true
51+
nodeResolve(),
52+
commonjs({
53+
esmExternals: true
6854
}),
6955
cleanup({ comments: "jsdoc" }),
7056
json()

packages/proof/rollup.node.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ export default {
3737
useTsconfigDeclarationDir: true
3838
}),
3939
nodeResolve(),
40-
commonjs(),
40+
commonjs({
41+
esmExternals: true
42+
}),
4143
cleanup({ comments: "jsdoc" }),
4244
json()
4345
]

packages/proof/src/calculateNullifierHash.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BytesLike, Hexable } from "@ethersproject/bytes"
2-
import { poseidon2 } from "poseidon-lite/poseidon2"
2+
import { poseidon2 } from "poseidon-lite"
33
import hash from "./hash"
44

55
/**

packages/proof/src/generateProof.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import { BytesLike, Hexable } from "@ethersproject/bytes"
33
import { Group } from "@semaphore-protocol/group"
44
import type { Identity } from "@semaphore-protocol/identity"
55
import { MerkleProof } from "@zk-kit/incremental-merkle-tree"
6-
import { groth16, NumericString } from "snarkjs"
6+
import type { NumericString } from "snarkjs"
77
import hash from "./hash"
88
import packProof from "./packProof"
99
import { SemaphoreProof, SnarkArtifacts } from "./types"
10+
import groth16Prove from "./groth16/prove"
1011

1112
/**
1213
* Generates a Semaphore proof.
@@ -45,7 +46,7 @@ export default async function generateProof(
4546
}
4647
}
4748

48-
const { proof, publicSignals } = await groth16.fullProve(
49+
const { proof, publicSignals } = await groth16Prove(
4950
{
5051
identityTrapdoor: trapdoor,
5152
identityNullifier: nullifier,
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/* eslint-disable no-plusplus */
2+
/* eslint-disable eqeqeq */
3+
/* eslint-disable @typescript-eslint/naming-convention */
4+
/* istanbul ignore file */
5+
6+
// @ts-ignore
7+
import * as binFileUtils from "@iden3/binfileutils"
8+
import { BigBuffer, Scalar, utils } from "ffjavascript"
9+
import { log2 } from "./utils"
10+
import * as zkeyUtils from "./zkey-utils"
11+
import * as wtnsUtils from "./wtns-utils"
12+
import wtnsCalculate from "./wtns-calculate"
13+
14+
const { stringifyBigInts, unstringifyBigInts } = utils
15+
16+
async function buildABC1(curve: any, zkey: any, witness: any, coeffs: any) {
17+
const { n8 } = curve.Fr
18+
const sCoef = 4 * 3 + zkey.n8r
19+
const nCoef = (coeffs.byteLength - 4) / sCoef
20+
21+
const outBuffA = new BigBuffer(zkey.domainSize * n8)
22+
const outBuffB = new BigBuffer(zkey.domainSize * n8)
23+
const outBuffC = new BigBuffer(zkey.domainSize * n8)
24+
25+
const outBuf = [outBuffA, outBuffB]
26+
for (let i = 0; i < nCoef; i++) {
27+
const buffCoef = coeffs.slice(4 + i * sCoef, 4 + i * sCoef + sCoef)
28+
const buffCoefV = new DataView(buffCoef.buffer)
29+
const m = buffCoefV.getUint32(0, true)
30+
const c = buffCoefV.getUint32(4, true)
31+
const s = buffCoefV.getUint32(8, true)
32+
const coef = buffCoef.slice(12, 12 + n8)
33+
outBuf[m].set(
34+
curve.Fr.add(outBuf[m].slice(c * n8, c * n8 + n8), curve.Fr.mul(coef, witness.slice(s * n8, s * n8 + n8))),
35+
c * n8
36+
)
37+
}
38+
39+
for (let i = 0; i < zkey.domainSize; i++) {
40+
outBuffC.set(curve.Fr.mul(outBuffA.slice(i * n8, i * n8 + n8), outBuffB.slice(i * n8, i * n8 + n8)), i * n8)
41+
}
42+
43+
return [outBuffA, outBuffB, outBuffC]
44+
}
45+
46+
async function joinABC(curve: any, _zkey: any, a: any, b: any, c: any) {
47+
const MAX_CHUNK_SIZE = 1 << 22
48+
49+
const { n8 } = curve.Fr
50+
const nElements = Math.floor(a.byteLength / curve.Fr.n8)
51+
52+
const promises = []
53+
54+
for (let i = 0; i < nElements; i += MAX_CHUNK_SIZE) {
55+
const n = Math.min(nElements - i, MAX_CHUNK_SIZE)
56+
57+
const task = []
58+
59+
const aChunk = a.slice(i * n8, (i + n) * n8)
60+
const bChunk = b.slice(i * n8, (i + n) * n8)
61+
const cChunk = c.slice(i * n8, (i + n) * n8)
62+
63+
task.push({ cmd: "ALLOCSET", var: 0, buff: aChunk })
64+
task.push({ cmd: "ALLOCSET", var: 1, buff: bChunk })
65+
task.push({ cmd: "ALLOCSET", var: 2, buff: cChunk })
66+
task.push({ cmd: "ALLOC", var: 3, len: n * n8 })
67+
task.push({
68+
cmd: "CALL",
69+
fnName: "qap_joinABC",
70+
params: [{ var: 0 }, { var: 1 }, { var: 2 }, { val: n }, { var: 3 }]
71+
})
72+
task.push({ cmd: "CALL", fnName: "frm_batchFromMontgomery", params: [{ var: 3 }, { val: n }, { var: 3 }] })
73+
task.push({ cmd: "GET", out: 0, var: 3, len: n * n8 })
74+
promises.push(curve.tm.queueAction(task))
75+
}
76+
77+
const result = await Promise.all(promises)
78+
79+
let outBuff
80+
if (a instanceof BigBuffer) {
81+
// @ts-ignore
82+
outBuff = new BigBuffer(a.byteLength)
83+
} else {
84+
outBuff = new Uint8Array(a.byteLength)
85+
}
86+
87+
let p = 0
88+
for (let i = 0; i < result.length; i++) {
89+
outBuff.set(result[i][0], p)
90+
p += result[i][0].byteLength
91+
}
92+
93+
return outBuff
94+
}
95+
96+
export default async function groth16Prove(_input: any, wasmFile: any, zkeyFileName: any) {
97+
const input = unstringifyBigInts(_input)
98+
99+
const witnessFileName = {
100+
type: "mem"
101+
}
102+
103+
await wtnsCalculate(input, wasmFile, witnessFileName)
104+
105+
const { fd: fdWtns, sections: sectionsWtns } = await binFileUtils.readBinFile(
106+
witnessFileName,
107+
"wtns",
108+
2,
109+
1 << 25,
110+
1 << 23
111+
)
112+
113+
const wtns = await wtnsUtils.readHeader(fdWtns, sectionsWtns)
114+
115+
const { fd: fdZKey, sections: sectionsZKey } = await binFileUtils.readBinFile(
116+
zkeyFileName,
117+
"zkey",
118+
2,
119+
1 << 25,
120+
1 << 23
121+
)
122+
123+
const zkey = await zkeyUtils.readHeader(fdZKey, sectionsZKey, undefined)
124+
125+
if (zkey.protocol !== "groth16") {
126+
throw new Error("zkey file is not groth16")
127+
}
128+
129+
if (!Scalar.eq(zkey.r, wtns.q)) {
130+
throw new Error("Curve of the witness does not match the curve of the proving key")
131+
}
132+
133+
if (wtns.nWitness !== zkey.nVars) {
134+
throw new Error(`Invalid witness length. Circuit: ${zkey.nVars}, witness: ${wtns.nWitness}`)
135+
}
136+
137+
const { curve } = zkey
138+
const { Fr } = curve
139+
const { G1 } = curve
140+
const { G2 } = curve
141+
142+
const power = log2(zkey.domainSize)
143+
144+
const buffWitness = await binFileUtils.readSection(fdWtns, sectionsWtns, 2)
145+
const buffCoeffs = await binFileUtils.readSection(fdZKey, sectionsZKey, 4)
146+
147+
const [buffA_T, buffB_T, buffC_T] = await buildABC1(curve, zkey, buffWitness, buffCoeffs)
148+
149+
const inc = power == Fr.s ? curve.Fr.shift : curve.Fr.w[power + 1]
150+
151+
const buffA = await Fr.ifft(buffA_T, "", "", undefined, "IFFT_A")
152+
const buffAodd = await Fr.batchApplyKey(buffA, Fr.e(1), inc)
153+
const buffAodd_T = await Fr.fft(buffAodd, "", "", undefined, "FFT_A")
154+
155+
const buffB = await Fr.ifft(buffB_T, "", "", undefined, "IFFT_B")
156+
const buffBodd = await Fr.batchApplyKey(buffB, Fr.e(1), inc)
157+
const buffBodd_T = await Fr.fft(buffBodd, "", "", undefined, "FFT_B")
158+
159+
const buffC = await Fr.ifft(buffC_T, "", "", undefined, "IFFT_C")
160+
const buffCodd = await Fr.batchApplyKey(buffC, Fr.e(1), inc)
161+
const buffCodd_T = await Fr.fft(buffCodd, "", "", undefined, "FFT_C")
162+
163+
const buffPodd_T = await joinABC(curve, zkey, buffAodd_T, buffBodd_T, buffCodd_T)
164+
165+
let proof: any = {}
166+
167+
const buffBasesA = await binFileUtils.readSection(fdZKey, sectionsZKey, 5)
168+
proof.pi_a = await curve.G1.multiExpAffine(buffBasesA, buffWitness, undefined, "multiexp A")
169+
170+
const buffBasesB1 = await binFileUtils.readSection(fdZKey, sectionsZKey, 6)
171+
let pib1 = await curve.G1.multiExpAffine(buffBasesB1, buffWitness, undefined, "multiexp B1")
172+
173+
const buffBasesB2 = await binFileUtils.readSection(fdZKey, sectionsZKey, 7)
174+
proof.pi_b = await curve.G2.multiExpAffine(buffBasesB2, buffWitness, undefined, "multiexp B2")
175+
176+
const buffBasesC = await binFileUtils.readSection(fdZKey, sectionsZKey, 8)
177+
proof.pi_c = await curve.G1.multiExpAffine(
178+
buffBasesC,
179+
buffWitness.slice((zkey.nPublic + 1) * curve.Fr.n8),
180+
undefined,
181+
"multiexp C"
182+
)
183+
184+
const buffBasesH = await binFileUtils.readSection(fdZKey, sectionsZKey, 9)
185+
const resH = await curve.G1.multiExpAffine(buffBasesH, buffPodd_T, undefined, "multiexp H")
186+
187+
const r = curve.Fr.random()
188+
const s = curve.Fr.random()
189+
190+
proof.pi_a = G1.add(proof.pi_a, zkey.vk_alpha_1)
191+
proof.pi_a = G1.add(proof.pi_a, G1.timesFr(zkey.vk_delta_1, r))
192+
193+
proof.pi_b = G2.add(proof.pi_b, zkey.vk_beta_2)
194+
proof.pi_b = G2.add(proof.pi_b, G2.timesFr(zkey.vk_delta_2, s))
195+
196+
pib1 = G1.add(pib1, zkey.vk_beta_1)
197+
pib1 = G1.add(pib1, G1.timesFr(zkey.vk_delta_1, s))
198+
199+
proof.pi_c = G1.add(proof.pi_c, resH)
200+
201+
proof.pi_c = G1.add(proof.pi_c, G1.timesFr(proof.pi_a, s))
202+
proof.pi_c = G1.add(proof.pi_c, G1.timesFr(pib1, r))
203+
proof.pi_c = G1.add(proof.pi_c, G1.timesFr(zkey.vk_delta_1, Fr.neg(Fr.mul(r, s))))
204+
205+
let publicSignals = []
206+
207+
for (let i = 1; i <= zkey.nPublic; i++) {
208+
const b = buffWitness.slice(i * Fr.n8, i * Fr.n8 + Fr.n8)
209+
publicSignals.push(Scalar.fromRprLE(b, undefined, undefined))
210+
}
211+
212+
proof.pi_a = G1.toObject(G1.toAffine(proof.pi_a))
213+
proof.pi_b = G2.toObject(G2.toAffine(proof.pi_b))
214+
proof.pi_c = G1.toObject(G1.toAffine(proof.pi_c))
215+
216+
proof.protocol = "groth16"
217+
proof.curve = curve.name
218+
219+
await fdZKey.close()
220+
await fdWtns.close()
221+
222+
proof = stringifyBigInts(proof)
223+
publicSignals = stringifyBigInts(publicSignals)
224+
225+
return { proof, publicSignals }
226+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* eslint-disable import/prefer-default-export */
2+
/* eslint-disable no-return-assign */
3+
/* istanbul ignore file */
4+
5+
export function log2(V: any) {
6+
return (
7+
((V & 0xffff0000) !== 0 ? ((V &= 0xffff0000), 16) : 0) |
8+
((V & 0xff00ff00) !== 0 ? ((V &= 0xff00ff00), 8) : 0) |
9+
((V & 0xf0f0f0f0) !== 0 ? ((V &= 0xf0f0f0f0), 4) : 0) |
10+
((V & 0xcccccccc) !== 0 ? ((V &= 0xcccccccc), 2) : 0) |
11+
// @ts-ignore
12+
((V & 0xaaaaaaaa) !== 0)
13+
)
14+
}

0 commit comments

Comments
 (0)