@semaphore-protocol/proof 4.0.0-alpha.7 → 4.0.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/index.browser.js +63 -45
- package/dist/index.node.cjs +112 -57
- package/dist/index.node.js +103 -52
- package/dist/types/generate-proof.d.ts +11 -1
- package/dist/types/get-snark-artifacts.browser.d.ts +6 -0
- package/dist/types/get-snark-artifacts.node.d.ts +7 -0
- package/dist/types/index.d.ts +2 -4
- package/dist/types/to-bigint.d.ts +5 -0
- package/dist/types/verify-proof.d.ts +3 -2
- package/package.json +8 -9
- package/src/generate-proof.ts +37 -5
- package/src/get-snark-artifacts.browser.ts +9 -0
- package/src/get-snark-artifacts.node.ts +46 -7
- package/src/hash.ts +2 -1
- package/src/index.ts +2 -4
- package/src/to-bigint.ts +7 -1
- package/src/verify-proof.ts +24 -11
- package/dist/types/pack-points.d.ts +0 -8
- package/dist/types/unpack-points.d.ts +0 -8
- package/src/pack-points.ts +0 -20
- package/src/unpack-points.ts +0 -20
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<a href="https://npmjs.org/package/@semaphore-protocol/proof">
|
|
19
19
|
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/proof.svg?style=flat-square" />
|
|
20
20
|
</a>
|
|
21
|
-
<a href="https://js.semaphore.pse.dev/
|
|
21
|
+
<a href="https://js.semaphore.pse.dev/modules/_semaphore_protocol_proof">
|
|
22
22
|
<img alt="Documentation typedoc" src="https://img.shields.io/badge/docs-typedoc-744C7C?style=flat-square">
|
|
23
23
|
</a>
|
|
24
24
|
<a href="https://eslint.org/">
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
</h4>
|
|
50
50
|
</div>
|
|
51
51
|
|
|
52
|
-
| This library provides utility functions to generate and verify Semaphore proofs compatible with the Semaphore [circuits](https://github.com/semaphore-protocol/semaphore/tree/main/circuits). Generating valid zero-knowledge proofs requires files that can only be obtained in an attested
|
|
53
|
-
|
|
|
52
|
+
| This library provides utility functions to generate and verify Semaphore proofs compatible with the Semaphore [circuits](https://github.com/semaphore-protocol/semaphore/tree/main/circuits). Generating valid zero-knowledge proofs requires files that can only be obtained in an attested trusted-setup ceremony. |
|
|
53
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
54
54
|
|
|
55
55
|
## 🛠 Install
|
|
56
56
|
|
|
@@ -70,6 +70,8 @@ yarn add @semaphore-protocol/identity @semaphore-protocol/group @semaphore-proto
|
|
|
70
70
|
|
|
71
71
|
## 📜 Usage
|
|
72
72
|
|
|
73
|
+
For more information on the functions provided by `@semaphore-protocol/proof`, please refer to the [TypeDoc documentation](https://js.semaphore.pse.dev/modules/_semaphore_protocol_proof).
|
|
74
|
+
|
|
73
75
|
\# **generateProof**(
|
|
74
76
|
identity: _Identity_,
|
|
75
77
|
group: _Group_,
|
package/dist/index.browser.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @semaphore-protocol/proof
|
|
3
|
-
* @version 4.0.0-
|
|
3
|
+
* @version 4.0.0-beta
|
|
4
4
|
* @file A library to generate and verify Semaphore proofs.
|
|
5
5
|
* @copyright Ethereum Foundation 2024
|
|
6
6
|
* @license MIT
|
|
7
7
|
* @see [Github]{@link https://github.com/semaphore-protocol/semaphore/tree/main/packages/proof}
|
|
8
8
|
*/
|
|
9
|
+
import { packGroth16Proof, unpackGroth16Proof } from '@zk-kit/utils';
|
|
10
|
+
export { packGroth16Proof, unpackGroth16Proof } from '@zk-kit/utils';
|
|
11
|
+
import { requireNumber, requireDefined, requireObject, requireTypes, requireString, requireArray } from '@semaphore-protocol/utils/errors';
|
|
9
12
|
import { groth16 } from 'snarkjs';
|
|
10
|
-
import { keccak256
|
|
13
|
+
import { keccak256 } from 'ethers/crypto';
|
|
14
|
+
import { toBeHex, toBigInt as toBigInt$1 } from 'ethers/utils';
|
|
15
|
+
import { encodeBytes32String } from 'ethers/abi';
|
|
11
16
|
|
|
12
17
|
/******************************************************************************
|
|
13
18
|
Copyright (c) Microsoft Corporation.
|
|
@@ -80,9 +85,16 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
80
85
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
81
86
|
};
|
|
82
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Returns the zero-knowledge artifact paths. Artifacts exist for each tree
|
|
90
|
+
* depth supported by Semaphore, and they were generated in a trusted-setup.
|
|
91
|
+
* @param treeDepth The depth of the tree.
|
|
92
|
+
* @returns The zero-knowledge artifacts paths.
|
|
93
|
+
*/
|
|
83
94
|
function getSnarkArtifacts(treeDepth) {
|
|
84
95
|
return __awaiter(this, void 0, void 0, function () {
|
|
85
96
|
return __generator(this, function (_a) {
|
|
97
|
+
requireNumber(treeDepth, "treeDepth");
|
|
86
98
|
return [2 /*return*/, {
|
|
87
99
|
wasmFilePath: "https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.wasm"),
|
|
88
100
|
zkeyFilePath: "https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.zkey")
|
|
@@ -101,23 +113,10 @@ function hash(message) {
|
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
/**
|
|
104
|
-
*
|
|
105
|
-
* @param
|
|
106
|
-
* @
|
|
116
|
+
* Converts a bignumberish to a bigint.
|
|
117
|
+
* @param value The value to be converted to bigint.
|
|
118
|
+
* @return The value converted to bigint.
|
|
107
119
|
*/
|
|
108
|
-
function packPoints(proof) {
|
|
109
|
-
return [
|
|
110
|
-
proof.pi_a[0],
|
|
111
|
-
proof.pi_a[1],
|
|
112
|
-
proof.pi_b[0][1],
|
|
113
|
-
proof.pi_b[0][0],
|
|
114
|
-
proof.pi_b[1][1],
|
|
115
|
-
proof.pi_b[1][0],
|
|
116
|
-
proof.pi_c[0],
|
|
117
|
-
proof.pi_c[1]
|
|
118
|
-
];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
120
|
function toBigInt(value) {
|
|
122
121
|
try {
|
|
123
122
|
return toBigInt$1(value);
|
|
@@ -131,7 +130,17 @@ function toBigInt(value) {
|
|
|
131
130
|
}
|
|
132
131
|
|
|
133
132
|
/**
|
|
134
|
-
*
|
|
133
|
+
* It generates a Semaphore proof, i.e. a zero-knowledge proof that an identity that
|
|
134
|
+
* is part of a group has shared an anonymous message.
|
|
135
|
+
* The message may be any arbitrary user-defined value (e.g. a vote), or the hash of that value.
|
|
136
|
+
* The scope is a value used like a topic on which users can generate a valid proof only once,
|
|
137
|
+
* for example the id of an election in which voters can only vote once.
|
|
138
|
+
* The hash of the identity's scope and secret scalar is called a nullifier and can be
|
|
139
|
+
* used to verify whether that identity has already generated a valid proof in that scope.
|
|
140
|
+
* The depth of the tree determines which zero-knowledge artifacts to use to generate the proof.
|
|
141
|
+
* If it is not defined, it will be inferred from the group or Merkle proof passed as the second parameter.
|
|
142
|
+
* Finally, the artifacts themselves can be passed manually with file paths,
|
|
143
|
+
* or they will be automatically fetched.
|
|
135
144
|
* @param identity The Semaphore identity.
|
|
136
145
|
* @param groupOrMerkleProof The Semaphore group or its Merkle proof.
|
|
137
146
|
* @param scope The Semaphore scope.
|
|
@@ -146,8 +155,26 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
146
155
|
return __generator(this, function (_b) {
|
|
147
156
|
switch (_b.label) {
|
|
148
157
|
case 0:
|
|
158
|
+
requireDefined(identity, "identity");
|
|
159
|
+
requireDefined(groupOrMerkleProof, "groupOrMerkleProof");
|
|
160
|
+
requireDefined(message, "message");
|
|
161
|
+
requireDefined(scope, "scope");
|
|
162
|
+
requireObject(identity, "identity");
|
|
163
|
+
requireObject(groupOrMerkleProof, "groupOrMerkleProof");
|
|
164
|
+
requireTypes(message, "message", ["string", "bigint", "number", "uint8array"]);
|
|
165
|
+
requireTypes(scope, "scope", ["string", "bigint", "number", "uint8array"]);
|
|
166
|
+
if (merkleTreeDepth) {
|
|
167
|
+
requireNumber(merkleTreeDepth, "merkleTreeDepth");
|
|
168
|
+
}
|
|
169
|
+
if (snarkArtifacts) {
|
|
170
|
+
requireObject(snarkArtifacts, "snarkArtifacts");
|
|
171
|
+
}
|
|
172
|
+
// Message and scope can be strings, numbers or buffers (i.e. Uint8Array).
|
|
173
|
+
// They will be converted to bigints anyway.
|
|
149
174
|
message = toBigInt(message);
|
|
150
175
|
scope = toBigInt(scope);
|
|
176
|
+
// The second parameter can be either a Merkle proof or a group.
|
|
177
|
+
// If it is a group the Merkle proof will be calculated here.
|
|
151
178
|
if ("siblings" in groupOrMerkleProof) {
|
|
152
179
|
merkleProof = groupOrMerkleProof;
|
|
153
180
|
}
|
|
@@ -194,31 +221,13 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
194
221
|
nullifier: publicSignals[1],
|
|
195
222
|
message: message.toString(),
|
|
196
223
|
scope: scope.toString(),
|
|
197
|
-
points:
|
|
224
|
+
points: packGroth16Proof(proof)
|
|
198
225
|
}];
|
|
199
226
|
}
|
|
200
227
|
});
|
|
201
228
|
});
|
|
202
229
|
}
|
|
203
230
|
|
|
204
|
-
/**
|
|
205
|
-
* Unpacks the Groth16 proof points into their original form.
|
|
206
|
-
* @param proof The proof points compatible with Semaphore.
|
|
207
|
-
* @returns The proof points compatible with SnarkJS.
|
|
208
|
-
*/
|
|
209
|
-
function unpackPoints(proof) {
|
|
210
|
-
return {
|
|
211
|
-
pi_a: [proof[0], proof[1]],
|
|
212
|
-
pi_b: [
|
|
213
|
-
[proof[3], proof[2]],
|
|
214
|
-
[proof[5], proof[4]]
|
|
215
|
-
],
|
|
216
|
-
pi_c: [proof[6], proof[7]],
|
|
217
|
-
protocol: "groth16",
|
|
218
|
-
curve: "bn128"
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
231
|
var protocol = "groth16";
|
|
223
232
|
var curve = "bn128";
|
|
224
233
|
var nPublic = 4;
|
|
@@ -794,23 +803,32 @@ var verificationKeys = {
|
|
|
794
803
|
};
|
|
795
804
|
|
|
796
805
|
/**
|
|
797
|
-
* Verifies a Semaphore proof.
|
|
806
|
+
* Verifies whether a Semaphore proof is valid. Depending on the depth of the tree used to
|
|
807
|
+
* generate the proof, a different verification key will be used.
|
|
798
808
|
* @param proof The Semaphore proof.
|
|
799
809
|
* @returns True if the proof is valid, false otherwise.
|
|
800
810
|
*/
|
|
801
|
-
function verifyProof(
|
|
802
|
-
var merkleTreeDepth = _a.merkleTreeDepth, merkleTreeRoot = _a.merkleTreeRoot, nullifier = _a.nullifier, message = _a.message, scope = _a.scope, points = _a.points;
|
|
811
|
+
function verifyProof(proof) {
|
|
803
812
|
return __awaiter(this, void 0, void 0, function () {
|
|
804
|
-
var verificationKey;
|
|
805
|
-
return __generator(this, function (
|
|
813
|
+
var merkleTreeDepth, merkleTreeRoot, nullifier, message, scope, points, verificationKey;
|
|
814
|
+
return __generator(this, function (_a) {
|
|
815
|
+
requireDefined(proof, "proof");
|
|
816
|
+
requireObject(proof, "proof");
|
|
817
|
+
merkleTreeDepth = proof.merkleTreeDepth, merkleTreeRoot = proof.merkleTreeRoot, nullifier = proof.nullifier, message = proof.message, scope = proof.scope, points = proof.points;
|
|
818
|
+
requireNumber(merkleTreeDepth, "proof.merkleTreeDepth");
|
|
819
|
+
requireString(merkleTreeRoot, "proof.merkleTreeRoot");
|
|
820
|
+
requireString(nullifier, "proof.nullifier");
|
|
821
|
+
requireString(message, "proof.message");
|
|
822
|
+
requireString(scope, "proof.scope");
|
|
823
|
+
requireArray(points, "proof.points");
|
|
806
824
|
// TODO: support all tree depths after trusted-setup.
|
|
807
825
|
if (merkleTreeDepth < 1 || merkleTreeDepth > 12) {
|
|
808
826
|
throw new TypeError("The tree depth must be a number between 1 and 12");
|
|
809
827
|
}
|
|
810
828
|
verificationKey = __assign(__assign({}, verificationKeys), { vk_delta_2: verificationKeys.vk_delta_2[merkleTreeDepth - 1], IC: verificationKeys.IC[merkleTreeDepth - 1] });
|
|
811
|
-
return [2 /*return*/, groth16.verify(verificationKey, [merkleTreeRoot, nullifier, hash(message), hash(scope)],
|
|
829
|
+
return [2 /*return*/, groth16.verify(verificationKey, [merkleTreeRoot, nullifier, hash(message), hash(scope)], unpackGroth16Proof(points))];
|
|
812
830
|
});
|
|
813
831
|
});
|
|
814
832
|
}
|
|
815
833
|
|
|
816
|
-
export { generateProof,
|
|
834
|
+
export { generateProof, verifyProof };
|
package/dist/index.node.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @semaphore-protocol/proof
|
|
3
|
-
* @version 4.0.0-
|
|
3
|
+
* @version 4.0.0-beta
|
|
4
4
|
* @file A library to generate and verify Semaphore proofs.
|
|
5
5
|
* @copyright Ethereum Foundation 2024
|
|
6
6
|
* @license MIT
|
|
@@ -8,11 +8,18 @@
|
|
|
8
8
|
*/
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
+
var utils = require('@zk-kit/utils');
|
|
12
|
+
var errors = require('@semaphore-protocol/utils/errors');
|
|
11
13
|
var snarkjs = require('snarkjs');
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
var
|
|
14
|
+
var node_fs = require('node:fs');
|
|
15
|
+
var promises$1 = require('node:fs/promises');
|
|
16
|
+
var os = require('node:os');
|
|
17
|
+
var node_path = require('node:path');
|
|
18
|
+
var node_stream = require('node:stream');
|
|
19
|
+
var promises = require('node:stream/promises');
|
|
20
|
+
var crypto = require('ethers/crypto');
|
|
21
|
+
var utils$1 = require('ethers/utils');
|
|
22
|
+
var abi = require('ethers/abi');
|
|
16
23
|
|
|
17
24
|
/******************************************************************************
|
|
18
25
|
Copyright (c) Microsoft Corporation.
|
|
@@ -85,19 +92,56 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
85
92
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
86
93
|
};
|
|
87
94
|
|
|
95
|
+
/**
|
|
96
|
+
* A utility function to download the zero-knowledge artifacts from an external server.
|
|
97
|
+
* @param url The URL from which to download the artifacts.
|
|
98
|
+
* @param outputPath The path in which to save the artifacts.
|
|
99
|
+
*/
|
|
100
|
+
function download(url, outputPath) {
|
|
101
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
102
|
+
var response, dir, fileStream;
|
|
103
|
+
return __generator(this, function (_a) {
|
|
104
|
+
switch (_a.label) {
|
|
105
|
+
case 0: return [4 /*yield*/, fetch(url)];
|
|
106
|
+
case 1:
|
|
107
|
+
response = _a.sent();
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error("Failed to fetch ".concat(url, ": ").concat(response.statusText));
|
|
110
|
+
}
|
|
111
|
+
dir = node_path.dirname(outputPath);
|
|
112
|
+
return [4 /*yield*/, promises$1.mkdir(dir, { recursive: true })];
|
|
113
|
+
case 2:
|
|
114
|
+
_a.sent();
|
|
115
|
+
fileStream = node_fs.createWriteStream(outputPath);
|
|
116
|
+
return [4 /*yield*/, promises.finished(node_stream.Readable.fromWeb(response.body).pipe(fileStream))];
|
|
117
|
+
case 3:
|
|
118
|
+
_a.sent();
|
|
119
|
+
return [2 /*return*/];
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Downloads the zero-knowledge artifacts and returns their paths.
|
|
126
|
+
* Artifacts exist for each tree depth supported by Semaphore, and
|
|
127
|
+
* they were generated in a trusted-setup.
|
|
128
|
+
* @param treeDepth The depth of the tree.
|
|
129
|
+
* @returns The zero-knowledge artifacts paths.
|
|
130
|
+
*/
|
|
88
131
|
function getSnarkArtifacts(treeDepth) {
|
|
89
132
|
return __awaiter(this, void 0, void 0, function () {
|
|
90
133
|
var tmpDir, tmpPath;
|
|
91
134
|
return __generator(this, function (_a) {
|
|
92
135
|
switch (_a.label) {
|
|
93
136
|
case 0:
|
|
137
|
+
errors.requireNumber(treeDepth, "treeDepth");
|
|
94
138
|
tmpDir = "semaphore-proof";
|
|
95
|
-
tmpPath = "".concat(
|
|
96
|
-
if (!(!
|
|
97
|
-
return [4 /*yield*/, download("https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.wasm"), tmpPath)];
|
|
139
|
+
tmpPath = "".concat(os.tmpdir(), "/").concat(tmpDir, "-").concat(treeDepth);
|
|
140
|
+
if (!(!node_fs.existsSync(tmpPath) || node_fs.readdirSync(tmpPath).length !== 2)) return [3 /*break*/, 3];
|
|
141
|
+
return [4 /*yield*/, download("https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.wasm"), "".concat(tmpPath, "/semaphore.wasm"))];
|
|
98
142
|
case 1:
|
|
99
143
|
_a.sent();
|
|
100
|
-
return [4 /*yield*/, download("https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.zkey"), tmpPath)];
|
|
144
|
+
return [4 /*yield*/, download("https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.zkey"), "".concat(tmpPath, "/semaphore.zkey"))];
|
|
101
145
|
case 2:
|
|
102
146
|
_a.sent();
|
|
103
147
|
_a.label = 3;
|
|
@@ -116,41 +160,38 @@ function getSnarkArtifacts(treeDepth) {
|
|
|
116
160
|
* @returns The message digest.
|
|
117
161
|
*/
|
|
118
162
|
function hash(message) {
|
|
119
|
-
return (BigInt(
|
|
163
|
+
return (BigInt(crypto.keccak256(utils$1.toBeHex(message, 32))) >> BigInt(8)).toString();
|
|
120
164
|
}
|
|
121
165
|
|
|
122
166
|
/**
|
|
123
|
-
*
|
|
124
|
-
* @param
|
|
125
|
-
* @
|
|
167
|
+
* Converts a bignumberish to a bigint.
|
|
168
|
+
* @param value The value to be converted to bigint.
|
|
169
|
+
* @return The value converted to bigint.
|
|
126
170
|
*/
|
|
127
|
-
function packPoints(proof) {
|
|
128
|
-
return [
|
|
129
|
-
proof.pi_a[0],
|
|
130
|
-
proof.pi_a[1],
|
|
131
|
-
proof.pi_b[0][1],
|
|
132
|
-
proof.pi_b[0][0],
|
|
133
|
-
proof.pi_b[1][1],
|
|
134
|
-
proof.pi_b[1][0],
|
|
135
|
-
proof.pi_c[0],
|
|
136
|
-
proof.pi_c[1]
|
|
137
|
-
];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
171
|
function toBigInt(value) {
|
|
141
172
|
try {
|
|
142
|
-
return
|
|
173
|
+
return utils$1.toBigInt(value);
|
|
143
174
|
}
|
|
144
175
|
catch (error) {
|
|
145
176
|
if (typeof value === "string") {
|
|
146
|
-
return
|
|
177
|
+
return utils$1.toBigInt(abi.encodeBytes32String(value));
|
|
147
178
|
}
|
|
148
179
|
throw TypeError(error.message);
|
|
149
180
|
}
|
|
150
181
|
}
|
|
151
182
|
|
|
152
183
|
/**
|
|
153
|
-
*
|
|
184
|
+
* It generates a Semaphore proof, i.e. a zero-knowledge proof that an identity that
|
|
185
|
+
* is part of a group has shared an anonymous message.
|
|
186
|
+
* The message may be any arbitrary user-defined value (e.g. a vote), or the hash of that value.
|
|
187
|
+
* The scope is a value used like a topic on which users can generate a valid proof only once,
|
|
188
|
+
* for example the id of an election in which voters can only vote once.
|
|
189
|
+
* The hash of the identity's scope and secret scalar is called a nullifier and can be
|
|
190
|
+
* used to verify whether that identity has already generated a valid proof in that scope.
|
|
191
|
+
* The depth of the tree determines which zero-knowledge artifacts to use to generate the proof.
|
|
192
|
+
* If it is not defined, it will be inferred from the group or Merkle proof passed as the second parameter.
|
|
193
|
+
* Finally, the artifacts themselves can be passed manually with file paths,
|
|
194
|
+
* or they will be automatically fetched.
|
|
154
195
|
* @param identity The Semaphore identity.
|
|
155
196
|
* @param groupOrMerkleProof The Semaphore group or its Merkle proof.
|
|
156
197
|
* @param scope The Semaphore scope.
|
|
@@ -165,8 +206,26 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
165
206
|
return __generator(this, function (_b) {
|
|
166
207
|
switch (_b.label) {
|
|
167
208
|
case 0:
|
|
209
|
+
errors.requireDefined(identity, "identity");
|
|
210
|
+
errors.requireDefined(groupOrMerkleProof, "groupOrMerkleProof");
|
|
211
|
+
errors.requireDefined(message, "message");
|
|
212
|
+
errors.requireDefined(scope, "scope");
|
|
213
|
+
errors.requireObject(identity, "identity");
|
|
214
|
+
errors.requireObject(groupOrMerkleProof, "groupOrMerkleProof");
|
|
215
|
+
errors.requireTypes(message, "message", ["string", "bigint", "number", "uint8array"]);
|
|
216
|
+
errors.requireTypes(scope, "scope", ["string", "bigint", "number", "uint8array"]);
|
|
217
|
+
if (merkleTreeDepth) {
|
|
218
|
+
errors.requireNumber(merkleTreeDepth, "merkleTreeDepth");
|
|
219
|
+
}
|
|
220
|
+
if (snarkArtifacts) {
|
|
221
|
+
errors.requireObject(snarkArtifacts, "snarkArtifacts");
|
|
222
|
+
}
|
|
223
|
+
// Message and scope can be strings, numbers or buffers (i.e. Uint8Array).
|
|
224
|
+
// They will be converted to bigints anyway.
|
|
168
225
|
message = toBigInt(message);
|
|
169
226
|
scope = toBigInt(scope);
|
|
227
|
+
// The second parameter can be either a Merkle proof or a group.
|
|
228
|
+
// If it is a group the Merkle proof will be calculated here.
|
|
170
229
|
if ("siblings" in groupOrMerkleProof) {
|
|
171
230
|
merkleProof = groupOrMerkleProof;
|
|
172
231
|
}
|
|
@@ -213,31 +272,13 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
213
272
|
nullifier: publicSignals[1],
|
|
214
273
|
message: message.toString(),
|
|
215
274
|
scope: scope.toString(),
|
|
216
|
-
points:
|
|
275
|
+
points: utils.packGroth16Proof(proof)
|
|
217
276
|
}];
|
|
218
277
|
}
|
|
219
278
|
});
|
|
220
279
|
});
|
|
221
280
|
}
|
|
222
281
|
|
|
223
|
-
/**
|
|
224
|
-
* Unpacks the Groth16 proof points into their original form.
|
|
225
|
-
* @param proof The proof points compatible with Semaphore.
|
|
226
|
-
* @returns The proof points compatible with SnarkJS.
|
|
227
|
-
*/
|
|
228
|
-
function unpackPoints(proof) {
|
|
229
|
-
return {
|
|
230
|
-
pi_a: [proof[0], proof[1]],
|
|
231
|
-
pi_b: [
|
|
232
|
-
[proof[3], proof[2]],
|
|
233
|
-
[proof[5], proof[4]]
|
|
234
|
-
],
|
|
235
|
-
pi_c: [proof[6], proof[7]],
|
|
236
|
-
protocol: "groth16",
|
|
237
|
-
curve: "bn128"
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
282
|
var protocol = "groth16";
|
|
242
283
|
var curve = "bn128";
|
|
243
284
|
var nPublic = 4;
|
|
@@ -813,27 +854,41 @@ var verificationKeys = {
|
|
|
813
854
|
};
|
|
814
855
|
|
|
815
856
|
/**
|
|
816
|
-
* Verifies a Semaphore proof.
|
|
857
|
+
* Verifies whether a Semaphore proof is valid. Depending on the depth of the tree used to
|
|
858
|
+
* generate the proof, a different verification key will be used.
|
|
817
859
|
* @param proof The Semaphore proof.
|
|
818
860
|
* @returns True if the proof is valid, false otherwise.
|
|
819
861
|
*/
|
|
820
|
-
function verifyProof(
|
|
821
|
-
var merkleTreeDepth = _a.merkleTreeDepth, merkleTreeRoot = _a.merkleTreeRoot, nullifier = _a.nullifier, message = _a.message, scope = _a.scope, points = _a.points;
|
|
862
|
+
function verifyProof(proof) {
|
|
822
863
|
return __awaiter(this, void 0, void 0, function () {
|
|
823
|
-
var verificationKey;
|
|
824
|
-
return __generator(this, function (
|
|
864
|
+
var merkleTreeDepth, merkleTreeRoot, nullifier, message, scope, points, verificationKey;
|
|
865
|
+
return __generator(this, function (_a) {
|
|
866
|
+
errors.requireDefined(proof, "proof");
|
|
867
|
+
errors.requireObject(proof, "proof");
|
|
868
|
+
merkleTreeDepth = proof.merkleTreeDepth, merkleTreeRoot = proof.merkleTreeRoot, nullifier = proof.nullifier, message = proof.message, scope = proof.scope, points = proof.points;
|
|
869
|
+
errors.requireNumber(merkleTreeDepth, "proof.merkleTreeDepth");
|
|
870
|
+
errors.requireString(merkleTreeRoot, "proof.merkleTreeRoot");
|
|
871
|
+
errors.requireString(nullifier, "proof.nullifier");
|
|
872
|
+
errors.requireString(message, "proof.message");
|
|
873
|
+
errors.requireString(scope, "proof.scope");
|
|
874
|
+
errors.requireArray(points, "proof.points");
|
|
825
875
|
// TODO: support all tree depths after trusted-setup.
|
|
826
876
|
if (merkleTreeDepth < 1 || merkleTreeDepth > 12) {
|
|
827
877
|
throw new TypeError("The tree depth must be a number between 1 and 12");
|
|
828
878
|
}
|
|
829
879
|
verificationKey = __assign(__assign({}, verificationKeys), { vk_delta_2: verificationKeys.vk_delta_2[merkleTreeDepth - 1], IC: verificationKeys.IC[merkleTreeDepth - 1] });
|
|
830
|
-
return [2 /*return*/, snarkjs.groth16.verify(verificationKey, [merkleTreeRoot, nullifier, hash(message), hash(scope)],
|
|
880
|
+
return [2 /*return*/, snarkjs.groth16.verify(verificationKey, [merkleTreeRoot, nullifier, hash(message), hash(scope)], utils.unpackGroth16Proof(points))];
|
|
831
881
|
});
|
|
832
882
|
});
|
|
833
883
|
}
|
|
834
884
|
|
|
885
|
+
Object.defineProperty(exports, "packGroth16Proof", {
|
|
886
|
+
enumerable: true,
|
|
887
|
+
get: function () { return utils.packGroth16Proof; }
|
|
888
|
+
});
|
|
889
|
+
Object.defineProperty(exports, "unpackGroth16Proof", {
|
|
890
|
+
enumerable: true,
|
|
891
|
+
get: function () { return utils.unpackGroth16Proof; }
|
|
892
|
+
});
|
|
835
893
|
exports.generateProof = generateProof;
|
|
836
|
-
exports.getSnarkArtifacts = getSnarkArtifacts;
|
|
837
|
-
exports.packPoints = packPoints;
|
|
838
|
-
exports.unpackPoints = unpackPoints;
|
|
839
894
|
exports.verifyProof = verifyProof;
|
package/dist/index.node.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @semaphore-protocol/proof
|
|
3
|
-
* @version 4.0.0-
|
|
3
|
+
* @version 4.0.0-beta
|
|
4
4
|
* @file A library to generate and verify Semaphore proofs.
|
|
5
5
|
* @copyright Ethereum Foundation 2024
|
|
6
6
|
* @license MIT
|
|
7
7
|
* @see [Github]{@link https://github.com/semaphore-protocol/semaphore/tree/main/packages/proof}
|
|
8
8
|
*/
|
|
9
|
+
import { packGroth16Proof, unpackGroth16Proof } from '@zk-kit/utils';
|
|
10
|
+
export { packGroth16Proof, unpackGroth16Proof } from '@zk-kit/utils';
|
|
11
|
+
import { requireNumber, requireDefined, requireObject, requireTypes, requireString, requireArray } from '@semaphore-protocol/utils/errors';
|
|
9
12
|
import { groth16 } from 'snarkjs';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
13
|
+
import { existsSync, readdirSync, createWriteStream } from 'node:fs';
|
|
14
|
+
import { mkdir } from 'node:fs/promises';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import { dirname } from 'node:path';
|
|
17
|
+
import { Readable } from 'node:stream';
|
|
18
|
+
import { finished } from 'node:stream/promises';
|
|
19
|
+
import { keccak256 } from 'ethers/crypto';
|
|
20
|
+
import { toBeHex, toBigInt as toBigInt$1 } from 'ethers/utils';
|
|
21
|
+
import { encodeBytes32String } from 'ethers/abi';
|
|
14
22
|
|
|
15
23
|
/******************************************************************************
|
|
16
24
|
Copyright (c) Microsoft Corporation.
|
|
@@ -83,19 +91,56 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
83
91
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
84
92
|
};
|
|
85
93
|
|
|
94
|
+
/**
|
|
95
|
+
* A utility function to download the zero-knowledge artifacts from an external server.
|
|
96
|
+
* @param url The URL from which to download the artifacts.
|
|
97
|
+
* @param outputPath The path in which to save the artifacts.
|
|
98
|
+
*/
|
|
99
|
+
function download(url, outputPath) {
|
|
100
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
101
|
+
var response, dir, fileStream;
|
|
102
|
+
return __generator(this, function (_a) {
|
|
103
|
+
switch (_a.label) {
|
|
104
|
+
case 0: return [4 /*yield*/, fetch(url)];
|
|
105
|
+
case 1:
|
|
106
|
+
response = _a.sent();
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
throw new Error("Failed to fetch ".concat(url, ": ").concat(response.statusText));
|
|
109
|
+
}
|
|
110
|
+
dir = dirname(outputPath);
|
|
111
|
+
return [4 /*yield*/, mkdir(dir, { recursive: true })];
|
|
112
|
+
case 2:
|
|
113
|
+
_a.sent();
|
|
114
|
+
fileStream = createWriteStream(outputPath);
|
|
115
|
+
return [4 /*yield*/, finished(Readable.fromWeb(response.body).pipe(fileStream))];
|
|
116
|
+
case 3:
|
|
117
|
+
_a.sent();
|
|
118
|
+
return [2 /*return*/];
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Downloads the zero-knowledge artifacts and returns their paths.
|
|
125
|
+
* Artifacts exist for each tree depth supported by Semaphore, and
|
|
126
|
+
* they were generated in a trusted-setup.
|
|
127
|
+
* @param treeDepth The depth of the tree.
|
|
128
|
+
* @returns The zero-knowledge artifacts paths.
|
|
129
|
+
*/
|
|
86
130
|
function getSnarkArtifacts(treeDepth) {
|
|
87
131
|
return __awaiter(this, void 0, void 0, function () {
|
|
88
132
|
var tmpDir, tmpPath;
|
|
89
133
|
return __generator(this, function (_a) {
|
|
90
134
|
switch (_a.label) {
|
|
91
135
|
case 0:
|
|
136
|
+
requireNumber(treeDepth, "treeDepth");
|
|
92
137
|
tmpDir = "semaphore-proof";
|
|
93
|
-
tmpPath = "".concat(
|
|
94
|
-
if (!(!
|
|
95
|
-
return [4 /*yield*/, download("https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.wasm"), tmpPath)];
|
|
138
|
+
tmpPath = "".concat(os.tmpdir(), "/").concat(tmpDir, "-").concat(treeDepth);
|
|
139
|
+
if (!(!existsSync(tmpPath) || readdirSync(tmpPath).length !== 2)) return [3 /*break*/, 3];
|
|
140
|
+
return [4 /*yield*/, download("https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.wasm"), "".concat(tmpPath, "/semaphore.wasm"))];
|
|
96
141
|
case 1:
|
|
97
142
|
_a.sent();
|
|
98
|
-
return [4 /*yield*/, download("https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.zkey"), tmpPath)];
|
|
143
|
+
return [4 /*yield*/, download("https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.zkey"), "".concat(tmpPath, "/semaphore.zkey"))];
|
|
99
144
|
case 2:
|
|
100
145
|
_a.sent();
|
|
101
146
|
_a.label = 3;
|
|
@@ -118,23 +163,10 @@ function hash(message) {
|
|
|
118
163
|
}
|
|
119
164
|
|
|
120
165
|
/**
|
|
121
|
-
*
|
|
122
|
-
* @param
|
|
123
|
-
* @
|
|
166
|
+
* Converts a bignumberish to a bigint.
|
|
167
|
+
* @param value The value to be converted to bigint.
|
|
168
|
+
* @return The value converted to bigint.
|
|
124
169
|
*/
|
|
125
|
-
function packPoints(proof) {
|
|
126
|
-
return [
|
|
127
|
-
proof.pi_a[0],
|
|
128
|
-
proof.pi_a[1],
|
|
129
|
-
proof.pi_b[0][1],
|
|
130
|
-
proof.pi_b[0][0],
|
|
131
|
-
proof.pi_b[1][1],
|
|
132
|
-
proof.pi_b[1][0],
|
|
133
|
-
proof.pi_c[0],
|
|
134
|
-
proof.pi_c[1]
|
|
135
|
-
];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
170
|
function toBigInt(value) {
|
|
139
171
|
try {
|
|
140
172
|
return toBigInt$1(value);
|
|
@@ -148,7 +180,17 @@ function toBigInt(value) {
|
|
|
148
180
|
}
|
|
149
181
|
|
|
150
182
|
/**
|
|
151
|
-
*
|
|
183
|
+
* It generates a Semaphore proof, i.e. a zero-knowledge proof that an identity that
|
|
184
|
+
* is part of a group has shared an anonymous message.
|
|
185
|
+
* The message may be any arbitrary user-defined value (e.g. a vote), or the hash of that value.
|
|
186
|
+
* The scope is a value used like a topic on which users can generate a valid proof only once,
|
|
187
|
+
* for example the id of an election in which voters can only vote once.
|
|
188
|
+
* The hash of the identity's scope and secret scalar is called a nullifier and can be
|
|
189
|
+
* used to verify whether that identity has already generated a valid proof in that scope.
|
|
190
|
+
* The depth of the tree determines which zero-knowledge artifacts to use to generate the proof.
|
|
191
|
+
* If it is not defined, it will be inferred from the group or Merkle proof passed as the second parameter.
|
|
192
|
+
* Finally, the artifacts themselves can be passed manually with file paths,
|
|
193
|
+
* or they will be automatically fetched.
|
|
152
194
|
* @param identity The Semaphore identity.
|
|
153
195
|
* @param groupOrMerkleProof The Semaphore group or its Merkle proof.
|
|
154
196
|
* @param scope The Semaphore scope.
|
|
@@ -163,8 +205,26 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
163
205
|
return __generator(this, function (_b) {
|
|
164
206
|
switch (_b.label) {
|
|
165
207
|
case 0:
|
|
208
|
+
requireDefined(identity, "identity");
|
|
209
|
+
requireDefined(groupOrMerkleProof, "groupOrMerkleProof");
|
|
210
|
+
requireDefined(message, "message");
|
|
211
|
+
requireDefined(scope, "scope");
|
|
212
|
+
requireObject(identity, "identity");
|
|
213
|
+
requireObject(groupOrMerkleProof, "groupOrMerkleProof");
|
|
214
|
+
requireTypes(message, "message", ["string", "bigint", "number", "uint8array"]);
|
|
215
|
+
requireTypes(scope, "scope", ["string", "bigint", "number", "uint8array"]);
|
|
216
|
+
if (merkleTreeDepth) {
|
|
217
|
+
requireNumber(merkleTreeDepth, "merkleTreeDepth");
|
|
218
|
+
}
|
|
219
|
+
if (snarkArtifacts) {
|
|
220
|
+
requireObject(snarkArtifacts, "snarkArtifacts");
|
|
221
|
+
}
|
|
222
|
+
// Message and scope can be strings, numbers or buffers (i.e. Uint8Array).
|
|
223
|
+
// They will be converted to bigints anyway.
|
|
166
224
|
message = toBigInt(message);
|
|
167
225
|
scope = toBigInt(scope);
|
|
226
|
+
// The second parameter can be either a Merkle proof or a group.
|
|
227
|
+
// If it is a group the Merkle proof will be calculated here.
|
|
168
228
|
if ("siblings" in groupOrMerkleProof) {
|
|
169
229
|
merkleProof = groupOrMerkleProof;
|
|
170
230
|
}
|
|
@@ -211,31 +271,13 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
211
271
|
nullifier: publicSignals[1],
|
|
212
272
|
message: message.toString(),
|
|
213
273
|
scope: scope.toString(),
|
|
214
|
-
points:
|
|
274
|
+
points: packGroth16Proof(proof)
|
|
215
275
|
}];
|
|
216
276
|
}
|
|
217
277
|
});
|
|
218
278
|
});
|
|
219
279
|
}
|
|
220
280
|
|
|
221
|
-
/**
|
|
222
|
-
* Unpacks the Groth16 proof points into their original form.
|
|
223
|
-
* @param proof The proof points compatible with Semaphore.
|
|
224
|
-
* @returns The proof points compatible with SnarkJS.
|
|
225
|
-
*/
|
|
226
|
-
function unpackPoints(proof) {
|
|
227
|
-
return {
|
|
228
|
-
pi_a: [proof[0], proof[1]],
|
|
229
|
-
pi_b: [
|
|
230
|
-
[proof[3], proof[2]],
|
|
231
|
-
[proof[5], proof[4]]
|
|
232
|
-
],
|
|
233
|
-
pi_c: [proof[6], proof[7]],
|
|
234
|
-
protocol: "groth16",
|
|
235
|
-
curve: "bn128"
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
281
|
var protocol = "groth16";
|
|
240
282
|
var curve = "bn128";
|
|
241
283
|
var nPublic = 4;
|
|
@@ -811,23 +853,32 @@ var verificationKeys = {
|
|
|
811
853
|
};
|
|
812
854
|
|
|
813
855
|
/**
|
|
814
|
-
* Verifies a Semaphore proof.
|
|
856
|
+
* Verifies whether a Semaphore proof is valid. Depending on the depth of the tree used to
|
|
857
|
+
* generate the proof, a different verification key will be used.
|
|
815
858
|
* @param proof The Semaphore proof.
|
|
816
859
|
* @returns True if the proof is valid, false otherwise.
|
|
817
860
|
*/
|
|
818
|
-
function verifyProof(
|
|
819
|
-
var merkleTreeDepth = _a.merkleTreeDepth, merkleTreeRoot = _a.merkleTreeRoot, nullifier = _a.nullifier, message = _a.message, scope = _a.scope, points = _a.points;
|
|
861
|
+
function verifyProof(proof) {
|
|
820
862
|
return __awaiter(this, void 0, void 0, function () {
|
|
821
|
-
var verificationKey;
|
|
822
|
-
return __generator(this, function (
|
|
863
|
+
var merkleTreeDepth, merkleTreeRoot, nullifier, message, scope, points, verificationKey;
|
|
864
|
+
return __generator(this, function (_a) {
|
|
865
|
+
requireDefined(proof, "proof");
|
|
866
|
+
requireObject(proof, "proof");
|
|
867
|
+
merkleTreeDepth = proof.merkleTreeDepth, merkleTreeRoot = proof.merkleTreeRoot, nullifier = proof.nullifier, message = proof.message, scope = proof.scope, points = proof.points;
|
|
868
|
+
requireNumber(merkleTreeDepth, "proof.merkleTreeDepth");
|
|
869
|
+
requireString(merkleTreeRoot, "proof.merkleTreeRoot");
|
|
870
|
+
requireString(nullifier, "proof.nullifier");
|
|
871
|
+
requireString(message, "proof.message");
|
|
872
|
+
requireString(scope, "proof.scope");
|
|
873
|
+
requireArray(points, "proof.points");
|
|
823
874
|
// TODO: support all tree depths after trusted-setup.
|
|
824
875
|
if (merkleTreeDepth < 1 || merkleTreeDepth > 12) {
|
|
825
876
|
throw new TypeError("The tree depth must be a number between 1 and 12");
|
|
826
877
|
}
|
|
827
878
|
verificationKey = __assign(__assign({}, verificationKeys), { vk_delta_2: verificationKeys.vk_delta_2[merkleTreeDepth - 1], IC: verificationKeys.IC[merkleTreeDepth - 1] });
|
|
828
|
-
return [2 /*return*/, groth16.verify(verificationKey, [merkleTreeRoot, nullifier, hash(message), hash(scope)],
|
|
879
|
+
return [2 /*return*/, groth16.verify(verificationKey, [merkleTreeRoot, nullifier, hash(message), hash(scope)], unpackGroth16Proof(points))];
|
|
829
880
|
});
|
|
830
881
|
});
|
|
831
882
|
}
|
|
832
883
|
|
|
833
|
-
export { generateProof,
|
|
884
|
+
export { generateProof, verifyProof };
|
|
@@ -2,7 +2,17 @@ import type { Group, MerkleProof } from "@semaphore-protocol/group";
|
|
|
2
2
|
import type { Identity } from "@semaphore-protocol/identity";
|
|
3
3
|
import { BigNumberish, SemaphoreProof, SnarkArtifacts } from "./types";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* It generates a Semaphore proof, i.e. a zero-knowledge proof that an identity that
|
|
6
|
+
* is part of a group has shared an anonymous message.
|
|
7
|
+
* The message may be any arbitrary user-defined value (e.g. a vote), or the hash of that value.
|
|
8
|
+
* The scope is a value used like a topic on which users can generate a valid proof only once,
|
|
9
|
+
* for example the id of an election in which voters can only vote once.
|
|
10
|
+
* The hash of the identity's scope and secret scalar is called a nullifier and can be
|
|
11
|
+
* used to verify whether that identity has already generated a valid proof in that scope.
|
|
12
|
+
* The depth of the tree determines which zero-knowledge artifacts to use to generate the proof.
|
|
13
|
+
* If it is not defined, it will be inferred from the group or Merkle proof passed as the second parameter.
|
|
14
|
+
* Finally, the artifacts themselves can be passed manually with file paths,
|
|
15
|
+
* or they will be automatically fetched.
|
|
6
16
|
* @param identity The Semaphore identity.
|
|
7
17
|
* @param groupOrMerkleProof The Semaphore group or its Merkle proof.
|
|
8
18
|
* @param scope The Semaphore scope.
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { SnarkArtifacts } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Returns the zero-knowledge artifact paths. Artifacts exist for each tree
|
|
4
|
+
* depth supported by Semaphore, and they were generated in a trusted-setup.
|
|
5
|
+
* @param treeDepth The depth of the tree.
|
|
6
|
+
* @returns The zero-knowledge artifacts paths.
|
|
7
|
+
*/
|
|
2
8
|
export default function getSnarkArtifacts(treeDepth: number): Promise<SnarkArtifacts>;
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import { SnarkArtifacts } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Downloads the zero-knowledge artifacts and returns their paths.
|
|
4
|
+
* Artifacts exist for each tree depth supported by Semaphore, and
|
|
5
|
+
* they were generated in a trusted-setup.
|
|
6
|
+
* @param treeDepth The depth of the tree.
|
|
7
|
+
* @returns The zero-knowledge artifacts paths.
|
|
8
|
+
*/
|
|
2
9
|
export default function getSnarkArtifacts(treeDepth: number): Promise<SnarkArtifacts>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
+
import { packGroth16Proof, unpackGroth16Proof } from "@zk-kit/utils";
|
|
1
2
|
import generateProof from "./generate-proof";
|
|
2
|
-
import getSnarkArtifacts from "./get-snark-artifacts.node";
|
|
3
|
-
import packPoints from "./pack-points";
|
|
4
|
-
import unpackPoints from "./unpack-points";
|
|
5
3
|
import verifyProof from "./verify-proof";
|
|
6
4
|
export * from "./types";
|
|
7
|
-
export { generateProof,
|
|
5
|
+
export { generateProof, packGroth16Proof, unpackGroth16Proof, verifyProof };
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import { BigNumberish } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Converts a bignumberish to a bigint.
|
|
4
|
+
* @param value The value to be converted to bigint.
|
|
5
|
+
* @return The value converted to bigint.
|
|
6
|
+
*/
|
|
2
7
|
export default function toBigInt(value: BigNumberish | Uint8Array | string): bigint;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { SemaphoreProof } from "./types";
|
|
2
2
|
/**
|
|
3
|
-
* Verifies a Semaphore proof.
|
|
3
|
+
* Verifies whether a Semaphore proof is valid. Depending on the depth of the tree used to
|
|
4
|
+
* generate the proof, a different verification key will be used.
|
|
4
5
|
* @param proof The Semaphore proof.
|
|
5
6
|
* @returns True if the proof is valid, false otherwise.
|
|
6
7
|
*/
|
|
7
|
-
export default function verifyProof(
|
|
8
|
+
export default function verifyProof(proof: SemaphoreProof): Promise<boolean>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@semaphore-protocol/proof",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-beta",
|
|
4
4
|
"description": "A library to generate and verify Semaphore proofs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,26 +38,25 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@ethersproject/strings": "^5.7.0",
|
|
42
41
|
"@rollup/plugin-alias": "^5.1.0",
|
|
43
42
|
"@rollup/plugin-json": "^6.1.0",
|
|
44
43
|
"@types/download": "^8.0.5",
|
|
44
|
+
"@types/snarkjs": "0.7.8",
|
|
45
45
|
"@types/tmp": "^0.2.6",
|
|
46
46
|
"poseidon-lite": "^0.2.0",
|
|
47
47
|
"rimraf": "^5.0.5",
|
|
48
|
-
"rollup": "^4.
|
|
48
|
+
"rollup": "^4.9.6",
|
|
49
49
|
"rollup-plugin-cleanup": "^3.2.1",
|
|
50
50
|
"rollup-plugin-typescript2": "^0.36.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@semaphore-protocol/group": "4.0.0-
|
|
54
|
-
"@semaphore-protocol/identity": "4.0.0-
|
|
53
|
+
"@semaphore-protocol/group": "4.0.0-beta",
|
|
54
|
+
"@semaphore-protocol/identity": "4.0.0-beta"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@
|
|
58
|
-
"
|
|
57
|
+
"@semaphore-protocol/utils": "4.0.0-beta",
|
|
58
|
+
"@zk-kit/utils": "0.2.0",
|
|
59
59
|
"ethers": "6.10.0",
|
|
60
|
-
"snarkjs": "0.7.3"
|
|
61
|
-
"tmp": "0.2.1"
|
|
60
|
+
"snarkjs": "0.7.3"
|
|
62
61
|
}
|
|
63
62
|
}
|
package/src/generate-proof.ts
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import type { Group, MerkleProof } from "@semaphore-protocol/group"
|
|
2
2
|
import type { Identity } from "@semaphore-protocol/identity"
|
|
3
|
+
import { requireDefined, requireNumber, requireObject, requireTypes } from "@semaphore-protocol/utils/errors"
|
|
3
4
|
import { NumericString, groth16 } from "snarkjs"
|
|
5
|
+
import { packGroth16Proof } from "@zk-kit/utils"
|
|
4
6
|
import getSnarkArtifacts from "./get-snark-artifacts.node"
|
|
5
7
|
import hash from "./hash"
|
|
6
|
-
import packPoints from "./pack-points"
|
|
7
|
-
import { BigNumberish, SemaphoreProof, SnarkArtifacts } from "./types"
|
|
8
8
|
import toBigInt from "./to-bigint"
|
|
9
|
+
import { BigNumberish, SemaphoreProof, SnarkArtifacts } from "./types"
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* It generates a Semaphore proof, i.e. a zero-knowledge proof that an identity that
|
|
13
|
+
* is part of a group has shared an anonymous message.
|
|
14
|
+
* The message may be any arbitrary user-defined value (e.g. a vote), or the hash of that value.
|
|
15
|
+
* The scope is a value used like a topic on which users can generate a valid proof only once,
|
|
16
|
+
* for example the id of an election in which voters can only vote once.
|
|
17
|
+
* The hash of the identity's scope and secret scalar is called a nullifier and can be
|
|
18
|
+
* used to verify whether that identity has already generated a valid proof in that scope.
|
|
19
|
+
* The depth of the tree determines which zero-knowledge artifacts to use to generate the proof.
|
|
20
|
+
* If it is not defined, it will be inferred from the group or Merkle proof passed as the second parameter.
|
|
21
|
+
* Finally, the artifacts themselves can be passed manually with file paths,
|
|
22
|
+
* or they will be automatically fetched.
|
|
12
23
|
* @param identity The Semaphore identity.
|
|
13
24
|
* @param groupOrMerkleProof The Semaphore group or its Merkle proof.
|
|
14
25
|
* @param scope The Semaphore scope.
|
|
@@ -25,11 +36,33 @@ export default async function generateProof(
|
|
|
25
36
|
merkleTreeDepth?: number,
|
|
26
37
|
snarkArtifacts?: SnarkArtifacts
|
|
27
38
|
): Promise<SemaphoreProof> {
|
|
39
|
+
requireDefined(identity, "identity")
|
|
40
|
+
requireDefined(groupOrMerkleProof, "groupOrMerkleProof")
|
|
41
|
+
requireDefined(message, "message")
|
|
42
|
+
requireDefined(scope, "scope")
|
|
43
|
+
|
|
44
|
+
requireObject(identity, "identity")
|
|
45
|
+
requireObject(groupOrMerkleProof, "groupOrMerkleProof")
|
|
46
|
+
requireTypes(message, "message", ["string", "bigint", "number", "uint8array"])
|
|
47
|
+
requireTypes(scope, "scope", ["string", "bigint", "number", "uint8array"])
|
|
48
|
+
|
|
49
|
+
if (merkleTreeDepth) {
|
|
50
|
+
requireNumber(merkleTreeDepth, "merkleTreeDepth")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (snarkArtifacts) {
|
|
54
|
+
requireObject(snarkArtifacts, "snarkArtifacts")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Message and scope can be strings, numbers or buffers (i.e. Uint8Array).
|
|
58
|
+
// They will be converted to bigints anyway.
|
|
28
59
|
message = toBigInt(message)
|
|
29
60
|
scope = toBigInt(scope)
|
|
30
61
|
|
|
31
62
|
let merkleProof
|
|
32
63
|
|
|
64
|
+
// The second parameter can be either a Merkle proof or a group.
|
|
65
|
+
// If it is a group the Merkle proof will be calculated here.
|
|
33
66
|
if ("siblings" in groupOrMerkleProof) {
|
|
34
67
|
merkleProof = groupOrMerkleProof
|
|
35
68
|
} else {
|
|
@@ -48,7 +81,6 @@ export default async function generateProof(
|
|
|
48
81
|
}
|
|
49
82
|
|
|
50
83
|
// If the Snark artifacts are not defined they will be automatically downloaded.
|
|
51
|
-
/* istanbul ignore next */
|
|
52
84
|
if (!snarkArtifacts) {
|
|
53
85
|
snarkArtifacts = await getSnarkArtifacts(merkleTreeDepth)
|
|
54
86
|
}
|
|
@@ -85,6 +117,6 @@ export default async function generateProof(
|
|
|
85
117
|
nullifier: publicSignals[1],
|
|
86
118
|
message: message.toString() as NumericString,
|
|
87
119
|
scope: scope.toString() as NumericString,
|
|
88
|
-
points:
|
|
120
|
+
points: packGroth16Proof(proof)
|
|
89
121
|
}
|
|
90
122
|
}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
/* istanbul ignore file */
|
|
2
|
+
import { requireNumber } from "@semaphore-protocol/utils/errors"
|
|
2
3
|
import { SnarkArtifacts } from "./types"
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Returns the zero-knowledge artifact paths. Artifacts exist for each tree
|
|
7
|
+
* depth supported by Semaphore, and they were generated in a trusted-setup.
|
|
8
|
+
* @param treeDepth The depth of the tree.
|
|
9
|
+
* @returns The zero-knowledge artifacts paths.
|
|
10
|
+
*/
|
|
4
11
|
export default async function getSnarkArtifacts(treeDepth: number): Promise<SnarkArtifacts> {
|
|
12
|
+
requireNumber(treeDepth, "treeDepth")
|
|
13
|
+
|
|
5
14
|
return {
|
|
6
15
|
wasmFilePath: `https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.wasm`,
|
|
7
16
|
zkeyFilePath: `https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.zkey`
|
|
@@ -1,16 +1,55 @@
|
|
|
1
1
|
/* istanbul ignore file */
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { requireNumber } from "@semaphore-protocol/utils/errors"
|
|
3
|
+
import { createWriteStream, existsSync, readdirSync } from "node:fs"
|
|
4
|
+
import { mkdir } from "node:fs/promises"
|
|
5
|
+
import os from "node:os"
|
|
6
|
+
import { dirname } from "node:path"
|
|
7
|
+
import { Readable } from "node:stream"
|
|
8
|
+
import { finished } from "node:stream/promises"
|
|
5
9
|
import { SnarkArtifacts } from "./types"
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* A utility function to download the zero-knowledge artifacts from an external server.
|
|
13
|
+
* @param url The URL from which to download the artifacts.
|
|
14
|
+
* @param outputPath The path in which to save the artifacts.
|
|
15
|
+
*/
|
|
16
|
+
async function download(url: string, outputPath: string) {
|
|
17
|
+
const response = await fetch(url)
|
|
18
|
+
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new Error(`Failed to fetch ${url}: ${response.statusText}`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Ensure the directory exists.
|
|
24
|
+
const dir = dirname(outputPath)
|
|
25
|
+
await mkdir(dir, { recursive: true })
|
|
26
|
+
|
|
27
|
+
const fileStream = createWriteStream(outputPath)
|
|
28
|
+
await finished(Readable.fromWeb(response.body as any).pipe(fileStream))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Downloads the zero-knowledge artifacts and returns their paths.
|
|
33
|
+
* Artifacts exist for each tree depth supported by Semaphore, and
|
|
34
|
+
* they were generated in a trusted-setup.
|
|
35
|
+
* @param treeDepth The depth of the tree.
|
|
36
|
+
* @returns The zero-knowledge artifacts paths.
|
|
37
|
+
*/
|
|
7
38
|
export default async function getSnarkArtifacts(treeDepth: number): Promise<SnarkArtifacts> {
|
|
39
|
+
requireNumber(treeDepth, "treeDepth")
|
|
40
|
+
|
|
8
41
|
const tmpDir = "semaphore-proof"
|
|
9
|
-
const tmpPath = `${
|
|
42
|
+
const tmpPath = `${os.tmpdir()}/${tmpDir}-${treeDepth}`
|
|
10
43
|
|
|
11
|
-
if (!
|
|
12
|
-
await download(
|
|
13
|
-
|
|
44
|
+
if (!existsSync(tmpPath) || readdirSync(tmpPath).length !== 2) {
|
|
45
|
+
await download(
|
|
46
|
+
`https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.wasm`,
|
|
47
|
+
`${tmpPath}/semaphore.wasm`
|
|
48
|
+
)
|
|
49
|
+
await download(
|
|
50
|
+
`https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.zkey`,
|
|
51
|
+
`${tmpPath}/semaphore.zkey`
|
|
52
|
+
)
|
|
14
53
|
}
|
|
15
54
|
|
|
16
55
|
return {
|
package/src/hash.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
import { packGroth16Proof, unpackGroth16Proof } from "@zk-kit/utils"
|
|
1
2
|
import generateProof from "./generate-proof"
|
|
2
|
-
import getSnarkArtifacts from "./get-snark-artifacts.node"
|
|
3
|
-
import packPoints from "./pack-points"
|
|
4
|
-
import unpackPoints from "./unpack-points"
|
|
5
3
|
import verifyProof from "./verify-proof"
|
|
6
4
|
|
|
7
5
|
export * from "./types"
|
|
8
|
-
export { generateProof,
|
|
6
|
+
export { generateProof, packGroth16Proof, unpackGroth16Proof, verifyProof }
|
package/src/to-bigint.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { encodeBytes32String
|
|
1
|
+
import { encodeBytes32String } from "ethers/abi"
|
|
2
|
+
import { toBigInt as _toBigInt } from "ethers/utils"
|
|
2
3
|
import { BigNumberish } from "./types"
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Converts a bignumberish to a bigint.
|
|
7
|
+
* @param value The value to be converted to bigint.
|
|
8
|
+
* @return The value converted to bigint.
|
|
9
|
+
*/
|
|
4
10
|
export default function toBigInt(value: BigNumberish | Uint8Array | string): bigint {
|
|
5
11
|
try {
|
|
6
12
|
return _toBigInt(value)
|
package/src/verify-proof.ts
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
requireArray,
|
|
3
|
+
requireDefined,
|
|
4
|
+
requireNumber,
|
|
5
|
+
requireObject,
|
|
6
|
+
requireString
|
|
7
|
+
} from "@semaphore-protocol/utils/errors"
|
|
1
8
|
import { groth16 } from "snarkjs"
|
|
9
|
+
import { unpackGroth16Proof } from "@zk-kit/utils"
|
|
2
10
|
import hash from "./hash"
|
|
3
11
|
import { SemaphoreProof } from "./types"
|
|
4
|
-
import unpackPoints from "./unpack-points"
|
|
5
12
|
import verificationKeys from "./verification-keys.json"
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
|
-
* Verifies a Semaphore proof.
|
|
15
|
+
* Verifies whether a Semaphore proof is valid. Depending on the depth of the tree used to
|
|
16
|
+
* generate the proof, a different verification key will be used.
|
|
9
17
|
* @param proof The Semaphore proof.
|
|
10
18
|
* @returns True if the proof is valid, false otherwise.
|
|
11
19
|
*/
|
|
12
|
-
export default async function verifyProof({
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
message,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
export default async function verifyProof(proof: SemaphoreProof): Promise<boolean> {
|
|
21
|
+
requireDefined(proof, "proof")
|
|
22
|
+
requireObject(proof, "proof")
|
|
23
|
+
|
|
24
|
+
const { merkleTreeDepth, merkleTreeRoot, nullifier, message, scope, points } = proof
|
|
25
|
+
|
|
26
|
+
requireNumber(merkleTreeDepth, "proof.merkleTreeDepth")
|
|
27
|
+
requireString(merkleTreeRoot, "proof.merkleTreeRoot")
|
|
28
|
+
requireString(nullifier, "proof.nullifier")
|
|
29
|
+
requireString(message, "proof.message")
|
|
30
|
+
requireString(scope, "proof.scope")
|
|
31
|
+
requireArray(points, "proof.points")
|
|
32
|
+
|
|
20
33
|
// TODO: support all tree depths after trusted-setup.
|
|
21
34
|
if (merkleTreeDepth < 1 || merkleTreeDepth > 12) {
|
|
22
35
|
throw new TypeError("The tree depth must be a number between 1 and 12")
|
|
@@ -31,6 +44,6 @@ export default async function verifyProof({
|
|
|
31
44
|
return groth16.verify(
|
|
32
45
|
verificationKey,
|
|
33
46
|
[merkleTreeRoot, nullifier, hash(message), hash(scope)],
|
|
34
|
-
|
|
47
|
+
unpackGroth16Proof(points)
|
|
35
48
|
)
|
|
36
49
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Groth16Proof } from "snarkjs";
|
|
2
|
-
import { PackedPoints } from "./types";
|
|
3
|
-
/**
|
|
4
|
-
* Packs the Groth16 proof points into a format compatible with Semaphore.
|
|
5
|
-
* @param proof The proof points generated with SnarkJS.
|
|
6
|
-
* @returns The proof points compatible with Semaphore.
|
|
7
|
-
*/
|
|
8
|
-
export default function packPoints(proof: Groth16Proof): PackedPoints;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Groth16Proof } from "snarkjs";
|
|
2
|
-
import { PackedPoints } from "./types";
|
|
3
|
-
/**
|
|
4
|
-
* Unpacks the Groth16 proof points into their original form.
|
|
5
|
-
* @param proof The proof points compatible with Semaphore.
|
|
6
|
-
* @returns The proof points compatible with SnarkJS.
|
|
7
|
-
*/
|
|
8
|
-
export default function unpackPoints(proof: PackedPoints): Groth16Proof;
|
package/src/pack-points.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Groth16Proof } from "snarkjs"
|
|
2
|
-
import { PackedPoints } from "./types"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Packs the Groth16 proof points into a format compatible with Semaphore.
|
|
6
|
-
* @param proof The proof points generated with SnarkJS.
|
|
7
|
-
* @returns The proof points compatible with Semaphore.
|
|
8
|
-
*/
|
|
9
|
-
export default function packPoints(proof: Groth16Proof): PackedPoints {
|
|
10
|
-
return [
|
|
11
|
-
proof.pi_a[0],
|
|
12
|
-
proof.pi_a[1],
|
|
13
|
-
proof.pi_b[0][1],
|
|
14
|
-
proof.pi_b[0][0],
|
|
15
|
-
proof.pi_b[1][1],
|
|
16
|
-
proof.pi_b[1][0],
|
|
17
|
-
proof.pi_c[0],
|
|
18
|
-
proof.pi_c[1]
|
|
19
|
-
]
|
|
20
|
-
}
|
package/src/unpack-points.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Groth16Proof } from "snarkjs"
|
|
2
|
-
import { PackedPoints } from "./types"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Unpacks the Groth16 proof points into their original form.
|
|
6
|
-
* @param proof The proof points compatible with Semaphore.
|
|
7
|
-
* @returns The proof points compatible with SnarkJS.
|
|
8
|
-
*/
|
|
9
|
-
export default function unpackPoints(proof: PackedPoints): Groth16Proof {
|
|
10
|
-
return {
|
|
11
|
-
pi_a: [proof[0], proof[1]],
|
|
12
|
-
pi_b: [
|
|
13
|
-
[proof[3], proof[2]],
|
|
14
|
-
[proof[5], proof[4]]
|
|
15
|
-
],
|
|
16
|
-
pi_c: [proof[6], proof[7]],
|
|
17
|
-
protocol: "groth16",
|
|
18
|
-
curve: "bn128"
|
|
19
|
-
}
|
|
20
|
-
}
|