@semaphore-protocol/proof 4.0.0-alpha.8 → 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 +60 -44
- package/dist/index.node.cjs +76 -50
- package/dist/index.node.js +66 -44
- 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 +7 -5
- package/src/generate-proof.ts +37 -4
- package/src/get-snark-artifacts.browser.ts +9 -0
- package/src/get-snark-artifacts.node.ts +15 -0
- package/src/index.ts +2 -4
- package/src/to-bigint.ts +5 -0
- 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,11 +1,14 @@
|
|
|
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
13
|
import { keccak256 } from 'ethers/crypto';
|
|
11
14
|
import { toBeHex, toBigInt as toBigInt$1 } from 'ethers/utils';
|
|
@@ -82,9 +85,16 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
82
85
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
83
86
|
};
|
|
84
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
|
+
*/
|
|
85
94
|
function getSnarkArtifacts(treeDepth) {
|
|
86
95
|
return __awaiter(this, void 0, void 0, function () {
|
|
87
96
|
return __generator(this, function (_a) {
|
|
97
|
+
requireNumber(treeDepth, "treeDepth");
|
|
88
98
|
return [2 /*return*/, {
|
|
89
99
|
wasmFilePath: "https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.wasm"),
|
|
90
100
|
zkeyFilePath: "https://semaphore.cedoor.dev/artifacts/".concat(treeDepth, "/semaphore.zkey")
|
|
@@ -103,23 +113,10 @@ function hash(message) {
|
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
/**
|
|
106
|
-
*
|
|
107
|
-
* @param
|
|
108
|
-
* @
|
|
116
|
+
* Converts a bignumberish to a bigint.
|
|
117
|
+
* @param value The value to be converted to bigint.
|
|
118
|
+
* @return The value converted to bigint.
|
|
109
119
|
*/
|
|
110
|
-
function packPoints(proof) {
|
|
111
|
-
return [
|
|
112
|
-
proof.pi_a[0],
|
|
113
|
-
proof.pi_a[1],
|
|
114
|
-
proof.pi_b[0][1],
|
|
115
|
-
proof.pi_b[0][0],
|
|
116
|
-
proof.pi_b[1][1],
|
|
117
|
-
proof.pi_b[1][0],
|
|
118
|
-
proof.pi_c[0],
|
|
119
|
-
proof.pi_c[1]
|
|
120
|
-
];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
120
|
function toBigInt(value) {
|
|
124
121
|
try {
|
|
125
122
|
return toBigInt$1(value);
|
|
@@ -133,7 +130,17 @@ function toBigInt(value) {
|
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
/**
|
|
136
|
-
*
|
|
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.
|
|
137
144
|
* @param identity The Semaphore identity.
|
|
138
145
|
* @param groupOrMerkleProof The Semaphore group or its Merkle proof.
|
|
139
146
|
* @param scope The Semaphore scope.
|
|
@@ -148,8 +155,26 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
148
155
|
return __generator(this, function (_b) {
|
|
149
156
|
switch (_b.label) {
|
|
150
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.
|
|
151
174
|
message = toBigInt(message);
|
|
152
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.
|
|
153
178
|
if ("siblings" in groupOrMerkleProof) {
|
|
154
179
|
merkleProof = groupOrMerkleProof;
|
|
155
180
|
}
|
|
@@ -196,31 +221,13 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
196
221
|
nullifier: publicSignals[1],
|
|
197
222
|
message: message.toString(),
|
|
198
223
|
scope: scope.toString(),
|
|
199
|
-
points:
|
|
224
|
+
points: packGroth16Proof(proof)
|
|
200
225
|
}];
|
|
201
226
|
}
|
|
202
227
|
});
|
|
203
228
|
});
|
|
204
229
|
}
|
|
205
230
|
|
|
206
|
-
/**
|
|
207
|
-
* Unpacks the Groth16 proof points into their original form.
|
|
208
|
-
* @param proof The proof points compatible with Semaphore.
|
|
209
|
-
* @returns The proof points compatible with SnarkJS.
|
|
210
|
-
*/
|
|
211
|
-
function unpackPoints(proof) {
|
|
212
|
-
return {
|
|
213
|
-
pi_a: [proof[0], proof[1]],
|
|
214
|
-
pi_b: [
|
|
215
|
-
[proof[3], proof[2]],
|
|
216
|
-
[proof[5], proof[4]]
|
|
217
|
-
],
|
|
218
|
-
pi_c: [proof[6], proof[7]],
|
|
219
|
-
protocol: "groth16",
|
|
220
|
-
curve: "bn128"
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
231
|
var protocol = "groth16";
|
|
225
232
|
var curve = "bn128";
|
|
226
233
|
var nPublic = 4;
|
|
@@ -796,23 +803,32 @@ var verificationKeys = {
|
|
|
796
803
|
};
|
|
797
804
|
|
|
798
805
|
/**
|
|
799
|
-
* 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.
|
|
800
808
|
* @param proof The Semaphore proof.
|
|
801
809
|
* @returns True if the proof is valid, false otherwise.
|
|
802
810
|
*/
|
|
803
|
-
function verifyProof(
|
|
804
|
-
var merkleTreeDepth = _a.merkleTreeDepth, merkleTreeRoot = _a.merkleTreeRoot, nullifier = _a.nullifier, message = _a.message, scope = _a.scope, points = _a.points;
|
|
811
|
+
function verifyProof(proof) {
|
|
805
812
|
return __awaiter(this, void 0, void 0, function () {
|
|
806
|
-
var verificationKey;
|
|
807
|
-
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");
|
|
808
824
|
// TODO: support all tree depths after trusted-setup.
|
|
809
825
|
if (merkleTreeDepth < 1 || merkleTreeDepth > 12) {
|
|
810
826
|
throw new TypeError("The tree depth must be a number between 1 and 12");
|
|
811
827
|
}
|
|
812
828
|
verificationKey = __assign(__assign({}, verificationKeys), { vk_delta_2: verificationKeys.vk_delta_2[merkleTreeDepth - 1], IC: verificationKeys.IC[merkleTreeDepth - 1] });
|
|
813
|
-
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))];
|
|
814
830
|
});
|
|
815
831
|
});
|
|
816
832
|
}
|
|
817
833
|
|
|
818
|
-
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,6 +8,8 @@
|
|
|
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
14
|
var node_fs = require('node:fs');
|
|
13
15
|
var promises$1 = require('node:fs/promises');
|
|
@@ -16,7 +18,7 @@ var node_path = require('node:path');
|
|
|
16
18
|
var node_stream = require('node:stream');
|
|
17
19
|
var promises = require('node:stream/promises');
|
|
18
20
|
var crypto = require('ethers/crypto');
|
|
19
|
-
var utils = require('ethers/utils');
|
|
21
|
+
var utils$1 = require('ethers/utils');
|
|
20
22
|
var abi = require('ethers/abi');
|
|
21
23
|
|
|
22
24
|
/******************************************************************************
|
|
@@ -90,6 +92,11 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
90
92
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
91
93
|
};
|
|
92
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
|
+
*/
|
|
93
100
|
function download(url, outputPath) {
|
|
94
101
|
return __awaiter(this, void 0, void 0, function () {
|
|
95
102
|
var response, dir, fileStream;
|
|
@@ -114,12 +121,20 @@ function download(url, outputPath) {
|
|
|
114
121
|
});
|
|
115
122
|
});
|
|
116
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
|
+
*/
|
|
117
131
|
function getSnarkArtifacts(treeDepth) {
|
|
118
132
|
return __awaiter(this, void 0, void 0, function () {
|
|
119
133
|
var tmpDir, tmpPath;
|
|
120
134
|
return __generator(this, function (_a) {
|
|
121
135
|
switch (_a.label) {
|
|
122
136
|
case 0:
|
|
137
|
+
errors.requireNumber(treeDepth, "treeDepth");
|
|
123
138
|
tmpDir = "semaphore-proof";
|
|
124
139
|
tmpPath = "".concat(os.tmpdir(), "/").concat(tmpDir, "-").concat(treeDepth);
|
|
125
140
|
if (!(!node_fs.existsSync(tmpPath) || node_fs.readdirSync(tmpPath).length !== 2)) return [3 /*break*/, 3];
|
|
@@ -145,41 +160,38 @@ function getSnarkArtifacts(treeDepth) {
|
|
|
145
160
|
* @returns The message digest.
|
|
146
161
|
*/
|
|
147
162
|
function hash(message) {
|
|
148
|
-
return (BigInt(crypto.keccak256(utils.toBeHex(message, 32))) >> BigInt(8)).toString();
|
|
163
|
+
return (BigInt(crypto.keccak256(utils$1.toBeHex(message, 32))) >> BigInt(8)).toString();
|
|
149
164
|
}
|
|
150
165
|
|
|
151
166
|
/**
|
|
152
|
-
*
|
|
153
|
-
* @param
|
|
154
|
-
* @
|
|
167
|
+
* Converts a bignumberish to a bigint.
|
|
168
|
+
* @param value The value to be converted to bigint.
|
|
169
|
+
* @return The value converted to bigint.
|
|
155
170
|
*/
|
|
156
|
-
function packPoints(proof) {
|
|
157
|
-
return [
|
|
158
|
-
proof.pi_a[0],
|
|
159
|
-
proof.pi_a[1],
|
|
160
|
-
proof.pi_b[0][1],
|
|
161
|
-
proof.pi_b[0][0],
|
|
162
|
-
proof.pi_b[1][1],
|
|
163
|
-
proof.pi_b[1][0],
|
|
164
|
-
proof.pi_c[0],
|
|
165
|
-
proof.pi_c[1]
|
|
166
|
-
];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
171
|
function toBigInt(value) {
|
|
170
172
|
try {
|
|
171
|
-
return utils.toBigInt(value);
|
|
173
|
+
return utils$1.toBigInt(value);
|
|
172
174
|
}
|
|
173
175
|
catch (error) {
|
|
174
176
|
if (typeof value === "string") {
|
|
175
|
-
return utils.toBigInt(abi.encodeBytes32String(value));
|
|
177
|
+
return utils$1.toBigInt(abi.encodeBytes32String(value));
|
|
176
178
|
}
|
|
177
179
|
throw TypeError(error.message);
|
|
178
180
|
}
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
/**
|
|
182
|
-
*
|
|
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.
|
|
183
195
|
* @param identity The Semaphore identity.
|
|
184
196
|
* @param groupOrMerkleProof The Semaphore group or its Merkle proof.
|
|
185
197
|
* @param scope The Semaphore scope.
|
|
@@ -194,8 +206,26 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
194
206
|
return __generator(this, function (_b) {
|
|
195
207
|
switch (_b.label) {
|
|
196
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.
|
|
197
225
|
message = toBigInt(message);
|
|
198
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.
|
|
199
229
|
if ("siblings" in groupOrMerkleProof) {
|
|
200
230
|
merkleProof = groupOrMerkleProof;
|
|
201
231
|
}
|
|
@@ -242,31 +272,13 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
242
272
|
nullifier: publicSignals[1],
|
|
243
273
|
message: message.toString(),
|
|
244
274
|
scope: scope.toString(),
|
|
245
|
-
points:
|
|
275
|
+
points: utils.packGroth16Proof(proof)
|
|
246
276
|
}];
|
|
247
277
|
}
|
|
248
278
|
});
|
|
249
279
|
});
|
|
250
280
|
}
|
|
251
281
|
|
|
252
|
-
/**
|
|
253
|
-
* Unpacks the Groth16 proof points into their original form.
|
|
254
|
-
* @param proof The proof points compatible with Semaphore.
|
|
255
|
-
* @returns The proof points compatible with SnarkJS.
|
|
256
|
-
*/
|
|
257
|
-
function unpackPoints(proof) {
|
|
258
|
-
return {
|
|
259
|
-
pi_a: [proof[0], proof[1]],
|
|
260
|
-
pi_b: [
|
|
261
|
-
[proof[3], proof[2]],
|
|
262
|
-
[proof[5], proof[4]]
|
|
263
|
-
],
|
|
264
|
-
pi_c: [proof[6], proof[7]],
|
|
265
|
-
protocol: "groth16",
|
|
266
|
-
curve: "bn128"
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
282
|
var protocol = "groth16";
|
|
271
283
|
var curve = "bn128";
|
|
272
284
|
var nPublic = 4;
|
|
@@ -842,27 +854,41 @@ var verificationKeys = {
|
|
|
842
854
|
};
|
|
843
855
|
|
|
844
856
|
/**
|
|
845
|
-
* 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.
|
|
846
859
|
* @param proof The Semaphore proof.
|
|
847
860
|
* @returns True if the proof is valid, false otherwise.
|
|
848
861
|
*/
|
|
849
|
-
function verifyProof(
|
|
850
|
-
var merkleTreeDepth = _a.merkleTreeDepth, merkleTreeRoot = _a.merkleTreeRoot, nullifier = _a.nullifier, message = _a.message, scope = _a.scope, points = _a.points;
|
|
862
|
+
function verifyProof(proof) {
|
|
851
863
|
return __awaiter(this, void 0, void 0, function () {
|
|
852
|
-
var verificationKey;
|
|
853
|
-
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");
|
|
854
875
|
// TODO: support all tree depths after trusted-setup.
|
|
855
876
|
if (merkleTreeDepth < 1 || merkleTreeDepth > 12) {
|
|
856
877
|
throw new TypeError("The tree depth must be a number between 1 and 12");
|
|
857
878
|
}
|
|
858
879
|
verificationKey = __assign(__assign({}, verificationKeys), { vk_delta_2: verificationKeys.vk_delta_2[merkleTreeDepth - 1], IC: verificationKeys.IC[merkleTreeDepth - 1] });
|
|
859
|
-
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))];
|
|
860
881
|
});
|
|
861
882
|
});
|
|
862
883
|
}
|
|
863
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
|
+
});
|
|
864
893
|
exports.generateProof = generateProof;
|
|
865
|
-
exports.getSnarkArtifacts = getSnarkArtifacts;
|
|
866
|
-
exports.packPoints = packPoints;
|
|
867
|
-
exports.unpackPoints = unpackPoints;
|
|
868
894
|
exports.verifyProof = verifyProof;
|
package/dist/index.node.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
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
13
|
import { existsSync, readdirSync, createWriteStream } from 'node:fs';
|
|
11
14
|
import { mkdir } from 'node:fs/promises';
|
|
@@ -88,6 +91,11 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
88
91
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
89
92
|
};
|
|
90
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
|
+
*/
|
|
91
99
|
function download(url, outputPath) {
|
|
92
100
|
return __awaiter(this, void 0, void 0, function () {
|
|
93
101
|
var response, dir, fileStream;
|
|
@@ -112,12 +120,20 @@ function download(url, outputPath) {
|
|
|
112
120
|
});
|
|
113
121
|
});
|
|
114
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
|
+
*/
|
|
115
130
|
function getSnarkArtifacts(treeDepth) {
|
|
116
131
|
return __awaiter(this, void 0, void 0, function () {
|
|
117
132
|
var tmpDir, tmpPath;
|
|
118
133
|
return __generator(this, function (_a) {
|
|
119
134
|
switch (_a.label) {
|
|
120
135
|
case 0:
|
|
136
|
+
requireNumber(treeDepth, "treeDepth");
|
|
121
137
|
tmpDir = "semaphore-proof";
|
|
122
138
|
tmpPath = "".concat(os.tmpdir(), "/").concat(tmpDir, "-").concat(treeDepth);
|
|
123
139
|
if (!(!existsSync(tmpPath) || readdirSync(tmpPath).length !== 2)) return [3 /*break*/, 3];
|
|
@@ -147,23 +163,10 @@ function hash(message) {
|
|
|
147
163
|
}
|
|
148
164
|
|
|
149
165
|
/**
|
|
150
|
-
*
|
|
151
|
-
* @param
|
|
152
|
-
* @
|
|
166
|
+
* Converts a bignumberish to a bigint.
|
|
167
|
+
* @param value The value to be converted to bigint.
|
|
168
|
+
* @return The value converted to bigint.
|
|
153
169
|
*/
|
|
154
|
-
function packPoints(proof) {
|
|
155
|
-
return [
|
|
156
|
-
proof.pi_a[0],
|
|
157
|
-
proof.pi_a[1],
|
|
158
|
-
proof.pi_b[0][1],
|
|
159
|
-
proof.pi_b[0][0],
|
|
160
|
-
proof.pi_b[1][1],
|
|
161
|
-
proof.pi_b[1][0],
|
|
162
|
-
proof.pi_c[0],
|
|
163
|
-
proof.pi_c[1]
|
|
164
|
-
];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
170
|
function toBigInt(value) {
|
|
168
171
|
try {
|
|
169
172
|
return toBigInt$1(value);
|
|
@@ -177,7 +180,17 @@ function toBigInt(value) {
|
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
/**
|
|
180
|
-
*
|
|
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.
|
|
181
194
|
* @param identity The Semaphore identity.
|
|
182
195
|
* @param groupOrMerkleProof The Semaphore group or its Merkle proof.
|
|
183
196
|
* @param scope The Semaphore scope.
|
|
@@ -192,8 +205,26 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
192
205
|
return __generator(this, function (_b) {
|
|
193
206
|
switch (_b.label) {
|
|
194
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.
|
|
195
224
|
message = toBigInt(message);
|
|
196
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.
|
|
197
228
|
if ("siblings" in groupOrMerkleProof) {
|
|
198
229
|
merkleProof = groupOrMerkleProof;
|
|
199
230
|
}
|
|
@@ -240,31 +271,13 @@ function generateProof(identity, groupOrMerkleProof, message, scope, merkleTreeD
|
|
|
240
271
|
nullifier: publicSignals[1],
|
|
241
272
|
message: message.toString(),
|
|
242
273
|
scope: scope.toString(),
|
|
243
|
-
points:
|
|
274
|
+
points: packGroth16Proof(proof)
|
|
244
275
|
}];
|
|
245
276
|
}
|
|
246
277
|
});
|
|
247
278
|
});
|
|
248
279
|
}
|
|
249
280
|
|
|
250
|
-
/**
|
|
251
|
-
* Unpacks the Groth16 proof points into their original form.
|
|
252
|
-
* @param proof The proof points compatible with Semaphore.
|
|
253
|
-
* @returns The proof points compatible with SnarkJS.
|
|
254
|
-
*/
|
|
255
|
-
function unpackPoints(proof) {
|
|
256
|
-
return {
|
|
257
|
-
pi_a: [proof[0], proof[1]],
|
|
258
|
-
pi_b: [
|
|
259
|
-
[proof[3], proof[2]],
|
|
260
|
-
[proof[5], proof[4]]
|
|
261
|
-
],
|
|
262
|
-
pi_c: [proof[6], proof[7]],
|
|
263
|
-
protocol: "groth16",
|
|
264
|
-
curve: "bn128"
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
281
|
var protocol = "groth16";
|
|
269
282
|
var curve = "bn128";
|
|
270
283
|
var nPublic = 4;
|
|
@@ -840,23 +853,32 @@ var verificationKeys = {
|
|
|
840
853
|
};
|
|
841
854
|
|
|
842
855
|
/**
|
|
843
|
-
* 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.
|
|
844
858
|
* @param proof The Semaphore proof.
|
|
845
859
|
* @returns True if the proof is valid, false otherwise.
|
|
846
860
|
*/
|
|
847
|
-
function verifyProof(
|
|
848
|
-
var merkleTreeDepth = _a.merkleTreeDepth, merkleTreeRoot = _a.merkleTreeRoot, nullifier = _a.nullifier, message = _a.message, scope = _a.scope, points = _a.points;
|
|
861
|
+
function verifyProof(proof) {
|
|
849
862
|
return __awaiter(this, void 0, void 0, function () {
|
|
850
|
-
var verificationKey;
|
|
851
|
-
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");
|
|
852
874
|
// TODO: support all tree depths after trusted-setup.
|
|
853
875
|
if (merkleTreeDepth < 1 || merkleTreeDepth > 12) {
|
|
854
876
|
throw new TypeError("The tree depth must be a number between 1 and 12");
|
|
855
877
|
}
|
|
856
878
|
verificationKey = __assign(__assign({}, verificationKeys), { vk_delta_2: verificationKeys.vk_delta_2[merkleTreeDepth - 1], IC: verificationKeys.IC[merkleTreeDepth - 1] });
|
|
857
|
-
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))];
|
|
858
880
|
});
|
|
859
881
|
});
|
|
860
882
|
}
|
|
861
883
|
|
|
862
|
-
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",
|
|
@@ -41,19 +41,21 @@
|
|
|
41
41
|
"@rollup/plugin-alias": "^5.1.0",
|
|
42
42
|
"@rollup/plugin-json": "^6.1.0",
|
|
43
43
|
"@types/download": "^8.0.5",
|
|
44
|
+
"@types/snarkjs": "0.7.8",
|
|
44
45
|
"@types/tmp": "^0.2.6",
|
|
45
46
|
"poseidon-lite": "^0.2.0",
|
|
46
47
|
"rimraf": "^5.0.5",
|
|
47
|
-
"rollup": "^4.
|
|
48
|
+
"rollup": "^4.9.6",
|
|
48
49
|
"rollup-plugin-cleanup": "^3.2.1",
|
|
49
50
|
"rollup-plugin-typescript2": "^0.36.0"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
|
-
"@semaphore-protocol/group": "4.0.0-
|
|
53
|
-
"@semaphore-protocol/identity": "4.0.0-
|
|
53
|
+
"@semaphore-protocol/group": "4.0.0-beta",
|
|
54
|
+
"@semaphore-protocol/identity": "4.0.0-beta"
|
|
54
55
|
},
|
|
55
56
|
"dependencies": {
|
|
56
|
-
"@
|
|
57
|
+
"@semaphore-protocol/utils": "4.0.0-beta",
|
|
58
|
+
"@zk-kit/utils": "0.2.0",
|
|
57
59
|
"ethers": "6.10.0",
|
|
58
60
|
"snarkjs": "0.7.3"
|
|
59
61
|
}
|
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 {
|
|
@@ -84,6 +117,6 @@ export default async function generateProof(
|
|
|
84
117
|
nullifier: publicSignals[1],
|
|
85
118
|
message: message.toString() as NumericString,
|
|
86
119
|
scope: scope.toString() as NumericString,
|
|
87
|
-
points:
|
|
120
|
+
points: packGroth16Proof(proof)
|
|
88
121
|
}
|
|
89
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,4 +1,5 @@
|
|
|
1
1
|
/* istanbul ignore file */
|
|
2
|
+
import { requireNumber } from "@semaphore-protocol/utils/errors"
|
|
2
3
|
import { createWriteStream, existsSync, readdirSync } from "node:fs"
|
|
3
4
|
import { mkdir } from "node:fs/promises"
|
|
4
5
|
import os from "node:os"
|
|
@@ -7,6 +8,11 @@ import { Readable } from "node:stream"
|
|
|
7
8
|
import { finished } from "node:stream/promises"
|
|
8
9
|
import { SnarkArtifacts } from "./types"
|
|
9
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
|
+
*/
|
|
10
16
|
async function download(url: string, outputPath: string) {
|
|
11
17
|
const response = await fetch(url)
|
|
12
18
|
|
|
@@ -22,7 +28,16 @@ async function download(url: string, outputPath: string) {
|
|
|
22
28
|
await finished(Readable.fromWeb(response.body as any).pipe(fileStream))
|
|
23
29
|
}
|
|
24
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
|
+
*/
|
|
25
38
|
export default async function getSnarkArtifacts(treeDepth: number): Promise<SnarkArtifacts> {
|
|
39
|
+
requireNumber(treeDepth, "treeDepth")
|
|
40
|
+
|
|
26
41
|
const tmpDir = "semaphore-proof"
|
|
27
42
|
const tmpPath = `${os.tmpdir()}/${tmpDir}-${treeDepth}`
|
|
28
43
|
|
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
|
@@ -2,6 +2,11 @@ import { encodeBytes32String } from "ethers/abi"
|
|
|
2
2
|
import { toBigInt as _toBigInt } from "ethers/utils"
|
|
3
3
|
import { BigNumberish } from "./types"
|
|
4
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
|
+
*/
|
|
5
10
|
export default function toBigInt(value: BigNumberish | Uint8Array | string): bigint {
|
|
6
11
|
try {
|
|
7
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
|
-
}
|