@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 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/proof">
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 [trusted-setup ceremony](https://storage.googleapis.com/trustedsetup-a86f4.appspot.com/semaphore/semaphore_top_index.html). For a complete list of ready-to-use files visit [trusted-setup-pse.org](http://www.trusted-setup-pse.org/). |
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_,
@@ -1,13 +1,18 @@
1
1
  /**
2
2
  * @module @semaphore-protocol/proof
3
- * @version 4.0.0-alpha.7
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, toBeHex, toBigInt as toBigInt$1, encodeBytes32String } from 'ethers';
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
- * Packs the Groth16 proof points into a format compatible with Semaphore.
105
- * @param proof The proof points generated with SnarkJS.
106
- * @returns The proof points compatible with Semaphore.
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
- * Generates a Semaphore proof.
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: packPoints(proof)
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(_a) {
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 (_b) {
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)], unpackPoints(points))];
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, getSnarkArtifacts, packPoints, unpackPoints, verifyProof };
834
+ export { generateProof, verifyProof };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @module @semaphore-protocol/proof
3
- * @version 4.0.0-alpha.7
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 download = require('download');
13
- var fs = require('fs');
14
- var tmp = require('tmp');
15
- var ethers = require('ethers');
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(tmp.tmpdir, "/").concat(tmpDir, "-").concat(treeDepth);
96
- if (!(!fs.existsSync(tmpPath) || fs.readdirSync(tmpPath).length !== 2)) return [3 /*break*/, 3];
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(ethers.keccak256(ethers.toBeHex(message, 32))) >> BigInt(8)).toString();
163
+ return (BigInt(crypto.keccak256(utils$1.toBeHex(message, 32))) >> BigInt(8)).toString();
120
164
  }
121
165
 
122
166
  /**
123
- * Packs the Groth16 proof points into a format compatible with Semaphore.
124
- * @param proof The proof points generated with SnarkJS.
125
- * @returns The proof points compatible with Semaphore.
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 ethers.toBigInt(value);
173
+ return utils$1.toBigInt(value);
143
174
  }
144
175
  catch (error) {
145
176
  if (typeof value === "string") {
146
- return ethers.toBigInt(ethers.encodeBytes32String(value));
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
- * Generates a Semaphore proof.
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: packPoints(proof)
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(_a) {
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 (_b) {
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)], unpackPoints(points))];
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;
@@ -1,16 +1,24 @@
1
1
  /**
2
2
  * @module @semaphore-protocol/proof
3
- * @version 4.0.0-alpha.7
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 download from 'download';
11
- import fs from 'fs';
12
- import tmp from 'tmp';
13
- import { keccak256, toBeHex, toBigInt as toBigInt$1, encodeBytes32String } from 'ethers';
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(tmp.tmpdir, "/").concat(tmpDir, "-").concat(treeDepth);
94
- if (!(!fs.existsSync(tmpPath) || fs.readdirSync(tmpPath).length !== 2)) return [3 /*break*/, 3];
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
- * Packs the Groth16 proof points into a format compatible with Semaphore.
122
- * @param proof The proof points generated with SnarkJS.
123
- * @returns The proof points compatible with Semaphore.
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
- * Generates a Semaphore proof.
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: packPoints(proof)
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(_a) {
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 (_b) {
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)], unpackPoints(points))];
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, getSnarkArtifacts, packPoints, unpackPoints, verifyProof };
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
- * Generates a Semaphore proof.
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>;
@@ -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, getSnarkArtifacts, packPoints, unpackPoints, verifyProof };
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({ merkleTreeDepth, merkleTreeRoot, nullifier, message, scope, points }: SemaphoreProof): Promise<boolean>;
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-alpha.7",
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.0.2",
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-alpha.7",
54
- "@semaphore-protocol/identity": "4.0.0-alpha.7"
53
+ "@semaphore-protocol/group": "4.0.0-beta",
54
+ "@semaphore-protocol/identity": "4.0.0-beta"
55
55
  },
56
56
  "dependencies": {
57
- "@types/snarkjs": "0.7.8",
58
- "download": "8.0.0",
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
  }
@@ -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
- * Generates a Semaphore proof.
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: packPoints(proof)
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 download from "download"
3
- import fs from "fs"
4
- import tmp from "tmp"
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 = `${tmp.tmpdir}/${tmpDir}-${treeDepth}`
42
+ const tmpPath = `${os.tmpdir()}/${tmpDir}-${treeDepth}`
10
43
 
11
- if (!fs.existsSync(tmpPath) || fs.readdirSync(tmpPath).length !== 2) {
12
- await download(`https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.wasm`, tmpPath)
13
- await download(`https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.zkey`, tmpPath)
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
@@ -1,4 +1,5 @@
1
- import { keccak256, toBeHex } from "ethers"
1
+ import { keccak256 } from "ethers/crypto"
2
+ import { toBeHex } from "ethers/utils"
2
3
  import { NumericString } from "snarkjs"
3
4
  import { BigNumberish } from "./types"
4
5
 
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, getSnarkArtifacts, packPoints, unpackPoints, verifyProof }
6
+ export { generateProof, packGroth16Proof, unpackGroth16Proof, verifyProof }
package/src/to-bigint.ts CHANGED
@@ -1,6 +1,12 @@
1
- import { encodeBytes32String, toBigInt as _toBigInt } from "ethers"
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)
@@ -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
- merkleTreeDepth,
14
- merkleTreeRoot,
15
- nullifier,
16
- message,
17
- scope,
18
- points
19
- }: SemaphoreProof): Promise<boolean> {
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
- unpackPoints(points)
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;
@@ -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
- }
@@ -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
- }