mirror of
https://github.com/semaphore-protocol/semaphore.git
synced 2026-01-13 16:48:00 -05:00
Compare commits
229 Commits
v3.0.0-bet
...
v3.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46fe2fc8f8 | ||
|
|
7bb9554388 | ||
|
|
d88a71ec4d | ||
|
|
2afc4ce1de | ||
|
|
64f7b24c53 | ||
|
|
88ba0af2d2 | ||
|
|
c2e8ba6856 | ||
|
|
f9a8d68641 | ||
|
|
df84100c22 | ||
|
|
b962339203 | ||
|
|
073f5a5772 | ||
|
|
b26f74a453 | ||
|
|
e9cac671f2 | ||
|
|
ecf8dcafb1 | ||
|
|
f90c99193a | ||
|
|
a826708320 | ||
|
|
43370202a7 | ||
|
|
a4d1180d26 | ||
|
|
5c42f9e09c | ||
|
|
c8362e373b | ||
|
|
06765f2f88 | ||
|
|
f26c84445e | ||
|
|
61a2d6adc2 | ||
|
|
eddd6b3dd5 | ||
|
|
fa561f8f00 | ||
|
|
c1842eefc3 | ||
|
|
320187ff89 | ||
|
|
abbf1a1d30 | ||
|
|
59269b067d | ||
|
|
21bd9fe540 | ||
|
|
f60a4c02f1 | ||
|
|
d012310ae1 | ||
|
|
f01cb91472 | ||
|
|
31e6954aff | ||
|
|
7518d938d1 | ||
|
|
083d8a03a4 | ||
|
|
16c9e90ae4 | ||
|
|
1fe2745594 | ||
|
|
18d4b740ca | ||
|
|
8392173370 | ||
|
|
26490304e4 | ||
|
|
1878ce2320 | ||
|
|
98aed04bd9 | ||
|
|
5a0585cec2 | ||
|
|
1a51263e5c | ||
|
|
4d7a976f06 | ||
|
|
fe07103ff2 | ||
|
|
aa230345fb | ||
|
|
d5c24c0f9c | ||
|
|
c1752dcb2e | ||
|
|
b00ff99e5e | ||
|
|
00e3c4a7f5 | ||
|
|
0176a67e3a | ||
|
|
4fa9f41483 | ||
|
|
64b9ef28e8 | ||
|
|
d7ce540f88 | ||
|
|
3411f55403 | ||
|
|
1bc492c39d | ||
|
|
6197a03734 | ||
|
|
6369fdfa71 | ||
|
|
48f779cd90 | ||
|
|
3ff6709a7b | ||
|
|
90bb4ccfa0 | ||
|
|
5f29690641 | ||
|
|
2665a440fa | ||
|
|
cf31913d27 | ||
|
|
103a676455 | ||
|
|
9aa5bc5840 | ||
|
|
894bc87952 | ||
|
|
cd4bd6ad91 | ||
|
|
dca7b67677 | ||
|
|
60d698f6ee | ||
|
|
f05e654f95 | ||
|
|
9a4fe6ba64 | ||
|
|
c8d472a286 | ||
|
|
c9711b9007 | ||
|
|
8afcac50c2 | ||
|
|
75c1c1373c | ||
|
|
2318628ca2 | ||
|
|
2603600716 | ||
|
|
3f0f1e006c | ||
|
|
2a4f2455d7 | ||
|
|
0ea221d9ea | ||
|
|
437674f3bc | ||
|
|
4202186c0b | ||
|
|
383e818833 | ||
|
|
74014a42b5 | ||
|
|
e875fa4aa3 | ||
|
|
186dae79d2 | ||
|
|
195d545a95 | ||
|
|
84245b9b1f | ||
|
|
461e91f53b | ||
|
|
2893feca3c | ||
|
|
4be2973761 | ||
|
|
01a30fd8af | ||
|
|
a70c1b001c | ||
|
|
30ed65d569 | ||
|
|
04a6649f2f | ||
|
|
a337021d8d | ||
|
|
4cd3863d03 | ||
|
|
c88639659d | ||
|
|
0ca17d078b | ||
|
|
f171506891 | ||
|
|
e085ecd0df | ||
|
|
b744ee1e7b | ||
|
|
8d51a6689d | ||
|
|
7bec0a4e42 | ||
|
|
71ed4d605d | ||
|
|
810a5358c0 | ||
|
|
636a9156b4 | ||
|
|
a679d047fa | ||
|
|
6d3e685284 | ||
|
|
3bfcf67b9d | ||
|
|
e16dbe663e | ||
|
|
0aca50ea34 | ||
|
|
54a6abdd2b | ||
|
|
de32db6d2d | ||
|
|
d43f9278e0 | ||
|
|
f3d8c4ee75 | ||
|
|
60fedc4e90 | ||
|
|
34d5743fa4 | ||
|
|
6810fcb5bb | ||
|
|
0bdf064614 | ||
|
|
7c4d2c2007 | ||
|
|
1bb57ca607 | ||
|
|
44860d70b9 | ||
|
|
d286a8f1e8 | ||
|
|
729ba41afd | ||
|
|
972fde32a7 | ||
|
|
c3d9e23e2a | ||
|
|
5fd7435c6d | ||
|
|
07888687a0 | ||
|
|
54c3a54597 | ||
|
|
81dba510c3 | ||
|
|
343cdabe9c | ||
|
|
fe6fbc1a0d | ||
|
|
f854492709 | ||
|
|
30af236547 | ||
|
|
d65b4aae47 | ||
|
|
245fb2cba8 | ||
|
|
f469ccc36a | ||
|
|
8ee1bd9c1b | ||
|
|
348a4327d7 | ||
|
|
faffde79f0 | ||
|
|
89a83c7b6b | ||
|
|
f36e8c1536 | ||
|
|
46ed508de8 | ||
|
|
821cdb7fd3 | ||
|
|
dd2df7ab06 | ||
|
|
71fdabe65c | ||
|
|
cce6c31461 | ||
|
|
f1aced7ee3 | ||
|
|
bd8b06bb7b | ||
|
|
9f692e57e8 | ||
|
|
32aabf81d2 | ||
|
|
a72a57ef63 | ||
|
|
4e5b4495a3 | ||
|
|
578d426d1a | ||
|
|
362b025fac | ||
|
|
84aa8d0eac | ||
|
|
6592a62f25 | ||
|
|
a59a2af50b | ||
|
|
1e68ed1fec | ||
|
|
2abd068cf8 | ||
|
|
8b746ff2bc | ||
|
|
23d9786e16 | ||
|
|
3b20402476 | ||
|
|
91bcc66c17 | ||
|
|
bcf9dad496 | ||
|
|
29195519fb | ||
|
|
17efdb38af | ||
|
|
8713e2b339 | ||
|
|
ef69356dca | ||
|
|
252b866daa | ||
|
|
5627f6eae7 | ||
|
|
45ef560324 | ||
|
|
ee80aa172e | ||
|
|
c0f106edb6 | ||
|
|
3aea38cc9b | ||
|
|
c6fe15cf13 | ||
|
|
6e948d63dc | ||
|
|
c2bd4df1d9 | ||
|
|
06e3063c3e | ||
|
|
cb52b3db20 | ||
|
|
88781ea466 | ||
|
|
2c47ad9f28 | ||
|
|
228ebd941a | ||
|
|
71f31f0910 | ||
|
|
7cf8eb11a1 | ||
|
|
6bbe45fb33 | ||
|
|
7b4e03c7fd | ||
|
|
8e0dd30ccc | ||
|
|
0661320c2a | ||
|
|
b419344297 | ||
|
|
fc636b732d | ||
|
|
5ac1cad96c | ||
|
|
33a6a3601e | ||
|
|
2dc8be161c | ||
|
|
a9dae718d3 | ||
|
|
f3ca0ce3d8 | ||
|
|
310b1a8dcd | ||
|
|
3c3d374c54 | ||
|
|
c6247ace3b | ||
|
|
1e9d56d3da | ||
|
|
a52a3bee1b | ||
|
|
e91bc74ab0 | ||
|
|
2594c4e1c2 | ||
|
|
a199b01a50 | ||
|
|
c97a0cbbb5 | ||
|
|
04fd0a3d91 | ||
|
|
3fb849f076 | ||
|
|
b3c83bac8b | ||
|
|
00c302aefc | ||
|
|
feee90e69d | ||
|
|
ca87f3601d | ||
|
|
a3abb810a9 | ||
|
|
5d19919474 | ||
|
|
5c66f16a5b | ||
|
|
4b2f9e4492 | ||
|
|
b593d1a0e9 | ||
|
|
d3b206ec3a | ||
|
|
adadacb218 | ||
|
|
b7411bfcb7 | ||
|
|
3feeb7f9fd | ||
|
|
05125c11d9 | ||
|
|
3188d3dbff | ||
|
|
04f57db7f0 | ||
|
|
066f38c471 | ||
|
|
37e8784471 |
@@ -9,7 +9,9 @@ coverage
|
||||
coverage.json
|
||||
|
||||
# hardhat
|
||||
artifacts
|
||||
cache
|
||||
typechain-types
|
||||
|
||||
# types
|
||||
types
|
||||
@@ -30,3 +32,6 @@ docs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# packages
|
||||
cli-template-*
|
||||
|
||||
14
.github/workflows/auto-assign.yml
vendored
Normal file
14
.github/workflows/auto-assign.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: auto-assign
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: wow-actions/auto-assign@v3
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
reviewers: org/core-devs
|
||||
43
.github/workflows/release.yml
vendored
Normal file
43
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: release
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
|
||||
|
||||
- name: Restore yarn cache
|
||||
uses: actions/cache@v3
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- run: yarn version:release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -70,6 +70,7 @@ docs/*
|
||||
# Hardhat
|
||||
artifacts
|
||||
cache
|
||||
typechain-types
|
||||
packages/contracts/deployed-contracts/undefined.json
|
||||
packages/contracts/deployed-contracts/hardhat.json
|
||||
packages/contracts/deployed-contracts/localhost.json
|
||||
|
||||
4
.husky/commit-msg
Executable file
4
.husky/commit-msg
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --edit $1
|
||||
@@ -9,14 +9,13 @@ coverage
|
||||
coverage.json
|
||||
|
||||
# hardhat
|
||||
artifacts
|
||||
cache
|
||||
typechain-types
|
||||
packages/contracts/deployed-contracts/undefined.json
|
||||
packages/contracts/deployed-contracts/hardhat.json
|
||||
packages/contracts/deployed-contracts/localhost.json
|
||||
|
||||
# types
|
||||
types
|
||||
|
||||
# circuits
|
||||
circuits
|
||||
|
||||
|
||||
23
.yarn/patches/changelogithub-npm-0.12.7-72f348805d.patch
Normal file
23
.yarn/patches/changelogithub-npm-0.12.7-72f348805d.patch
Normal file
@@ -0,0 +1,23 @@
|
||||
diff --git a/dist/shared/changelogithub.821fab93.mjs b/dist/shared/changelogithub.821fab93.mjs
|
||||
index 5fc2100867613c20f7827eac8715a5fc28bdc39e..97bd8dff878b81c63d2220e496904f6f3933589a 100644
|
||||
--- a/dist/shared/changelogithub.821fab93.mjs
|
||||
+++ b/dist/shared/changelogithub.821fab93.mjs
|
||||
@@ -181,7 +181,7 @@ function formatLine(commit, options) {
|
||||
function formatTitle(name, options) {
|
||||
if (!options.emoji)
|
||||
name = name.replace(emojisRE, "");
|
||||
- return `### ${name.trim()}`;
|
||||
+ return `## ${name.trim()}`;
|
||||
}
|
||||
function formatSection(commits, sectionName, options) {
|
||||
if (!commits.length)
|
||||
@@ -198,7 +198,8 @@ function formatSection(commits, sectionName, options) {
|
||||
Object.keys(scopes).sort().forEach((scope) => {
|
||||
let padding = "";
|
||||
let prefix = "";
|
||||
- const scopeText = `**${options.scopeMap[scope] || scope}**`;
|
||||
+ const url = `https://github.com/${options.github}/tree/main/packages/${scope}`
|
||||
+ const scopeText = `[**@${options.github.split("/")[0]}/${options.scopeMap[scope] || scope}**](${url})`
|
||||
if (scope && useScopeGroup) {
|
||||
lines.push(`- ${scopeText}:`);
|
||||
padding = " ";
|
||||
1
.yarn/plugins/@yarnpkg/plugin-version.cjs.REMOVED.git-id
vendored
Normal file
1
.yarn/plugins/@yarnpkg/plugin-version.cjs.REMOVED.git-id
vendored
Normal file
@@ -0,0 +1 @@
|
||||
87de4f440a77841135f97a187e09140c6d4e6ae2
|
||||
@@ -1,8 +1,11 @@
|
||||
nodeLinker: node-modules
|
||||
checksumBehavior: update
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: "@yarnpkg/plugin-workspace-tools"
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
|
||||
spec: "@yarnpkg/plugin-version"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.1.cjs
|
||||
|
||||
45
README.md
45
README.md
@@ -17,7 +17,7 @@
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/actions?query=workflow%3Aproduction">
|
||||
<img alt="GitHub Workflow test" src="https://img.shields.io/github/workflow/status/semaphore-protocol/semaphore/production?label=test&style=flat-square&logo=github">
|
||||
<img alt="GitHub Workflow test" src="https://img.shields.io/github/actions/workflow/status/semaphore-protocol/semaphore/production.yml?branch=main&label=test&style=flat-square&logo=github">
|
||||
</a>
|
||||
<a href="https://coveralls.io/github/semaphore-protocol/semaphore">
|
||||
<img alt="Coveralls" src="https://img.shields.io/coveralls/github/semaphore-protocol/semaphore?style=flat-square&logo=coveralls">
|
||||
@@ -32,6 +32,10 @@
|
||||
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier">
|
||||
</a>
|
||||
<img alt="Repository top language" src="https://img.shields.io/github/languages/top/semaphore-protocol/semaphore?style=flat-square">
|
||||
<a href="https://www.gitpoap.io/gh/semaphore-protocol/semaphore" target="_blank">
|
||||
<img src="https://public-api.gitpoap.io/v1/repo/semaphore-protocol/semaphore/badge">
|
||||
</a>
|
||||
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
@@ -48,7 +52,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
@@ -57,9 +61,7 @@
|
||||
| Semaphore is a protocol, designed to be a simple and generic privacy layer for Ethereum DApps. Using zero knowledge, Ethereum users can prove their membership of a group and send signals such as votes or endorsements without revealing their original identity. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/packages/contracts) (NPM: `@semaphore-protocol/contracts`) and JavaScript libraries to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org).
|
||||
|
||||
You can find Semaphore V1 on [`version/1.0.0`](https://github.com/semaphore-protocol/semaphore/tree/version/1.0.0).
|
||||
The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/packages/contracts) and JavaScript libraries to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org).
|
||||
|
||||
---
|
||||
|
||||
@@ -157,23 +159,23 @@ You can find Semaphore V1 on [`version/1.0.0`](https://github.com/semaphore-prot
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/subgraph">
|
||||
@semaphore-protocol/subgraph
|
||||
<a href="/packages/data">
|
||||
@semaphore-protocol/data
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/subgraph">
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/data">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/subgraph">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/subgraph.svg?style=flat-square" alt="NPM version" />
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/data">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/data.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/subgraph">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/subgraph.svg?style=flat-square" alt="Downloads" />
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/data">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/data.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -196,6 +198,25 @@ You can find Semaphore V1 on [`version/1.0.0`](https://github.com/semaphore-prot
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/cli">
|
||||
@semaphore-protocol/cli
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/cli">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/cli.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/cli">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/cli.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
|
||||
</table>
|
||||
|
||||
7
changelogithub.config.json
Normal file
7
changelogithub.config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"types": {
|
||||
"feat": { "title": "🚀 Features" },
|
||||
"fix": { "title": "🐞 Bug Fixes" },
|
||||
"refactor": { "title": "♻️ Refactoring" }
|
||||
}
|
||||
}
|
||||
17
package.json
17
package.json
@@ -7,7 +7,7 @@
|
||||
"bugs": "https://github.com/semaphore-protocol/semaphore/issues",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:libraries": "yarn workspaces foreach run build",
|
||||
"build:libraries": "yarn workspaces foreach -t run build",
|
||||
"compile:contracts": "yarn workspace contracts compile",
|
||||
"download:snark-artifacts": "rimraf snark-artifacts && ts-node scripts/download-snark-artifacts.ts",
|
||||
"test": "yarn test:libraries && yarn test:contracts",
|
||||
@@ -17,9 +17,12 @@
|
||||
"prettier": "prettier -c .",
|
||||
"prettier:write": "prettier -w .",
|
||||
"docs": "yarn workspaces foreach run docs",
|
||||
"version:bump": "yarn workspaces foreach --no-private version -d ${0} && yarn version apply --all && git commit -am \"chore: v${0}\" && git tag v${0}",
|
||||
"version:publish": "yarn build:libraries && yarn workspaces foreach --no-private npm publish --tolerate-republish",
|
||||
"version:release": "changelogithub",
|
||||
"commit": "cz",
|
||||
"precommit": "lint-staged",
|
||||
"postinstall": "yarn download:snark-artifacts"
|
||||
"postinstall": "yarn download:snark-artifacts && husky install"
|
||||
},
|
||||
"keywords": [
|
||||
"ethereum",
|
||||
@@ -35,7 +38,8 @@
|
||||
"monorepo"
|
||||
],
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
"packages/*",
|
||||
"packages/contracts/contracts"
|
||||
],
|
||||
"packageManager": "yarn@3.2.1",
|
||||
"devDependencies": {
|
||||
@@ -53,6 +57,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||
"@typescript-eslint/parser": "^5.9.1",
|
||||
"babel-jest": "^27.4.6",
|
||||
"changelogithub": "0.12.7",
|
||||
"commitizen": "^4.2.4",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"dotenv": "^16.0.2",
|
||||
@@ -62,6 +67,7 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-jest": "^25.7.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^27.4.1",
|
||||
"jest-config": "^27.4.7",
|
||||
"lint-staged": "^12.1.7",
|
||||
@@ -70,11 +76,14 @@
|
||||
"rollup": "^2.64.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.5.4"
|
||||
"typescript": "^4.7.0"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "./node_modules/cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"changelogithub@0.12.7": "patch:changelogithub@npm:0.12.7#.yarn/patches/changelogithub-npm-0.12.7-72f348805d.patch"
|
||||
}
|
||||
}
|
||||
|
||||
37
packages/circuits/README.md
Normal file
37
packages/circuits/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Semaphore circuits
|
||||
</h1>
|
||||
<p align="center">Semaphore circuits to create and verify zero-knowledge proofs.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/semaphore-protocol">
|
||||
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h4>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
|
||||
👥 Contributing
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
|
||||
🤝 Code of conduct
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
To learn more about circuits visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org/docs/technical-reference/circuits).
|
||||
@@ -43,7 +43,7 @@ template CalculateNullifierHash() {
|
||||
out <== poseidon.out;
|
||||
}
|
||||
|
||||
// nLevels must be < 32.
|
||||
// The current Semaphore smart contracts require nLevels <= 32 and nLevels >= 16.
|
||||
template Semaphore(nLevels) {
|
||||
signal input identityNullifier;
|
||||
signal input identityTrapdoor;
|
||||
|
||||
4
packages/cli-template-hardhat/.env.example
Normal file
4
packages/cli-template-hardhat/.env.example
Normal file
@@ -0,0 +1,4 @@
|
||||
INFURA_API_KEY=
|
||||
ETHEREUM_PRIVATE_KEY=
|
||||
REPORT_GAS=false
|
||||
COINMARKETCAP_API_KEY=
|
||||
10
packages/cli-template-hardhat/.gitignore
vendored
Normal file
10
packages/cli-template-hardhat/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
.env
|
||||
coverage
|
||||
coverage.json
|
||||
typechain
|
||||
typechain-types
|
||||
|
||||
# Hardhat files
|
||||
cache
|
||||
artifacts
|
||||
54
packages/cli-template-hardhat/README.md
Normal file
54
packages/cli-template-hardhat/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Semaphore Hardhat template
|
||||
|
||||
This project demonstrates a basic Semaphore use case. It comes with a sample contract, a test for that contract and a sample task that deploys that contract.
|
||||
|
||||
## Usage
|
||||
|
||||
### Compile
|
||||
|
||||
```bash
|
||||
yarn compile
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
You can also generate a test coverage report:
|
||||
|
||||
```bash
|
||||
yarn test:coverage
|
||||
```
|
||||
|
||||
Or a test gas report:
|
||||
|
||||
```bash
|
||||
yarn test:report-gas
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
1. Copy the `.env.example` file as `.env`.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. Add your environment variables.
|
||||
|
||||
> **Note**
|
||||
> You should at least set a valid Ethereum URL (e.g. Infura) and a private key with some ethers.
|
||||
|
||||
3. And deploy your contract.
|
||||
|
||||
```bash
|
||||
yarn deploy --semaphore <semaphore-address> --group <group-id> --network goerli
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> Check the Semaphore contract addresses [here](https://semaphore.appliedzkp.org/docs/deployed-contracts#semaphore).
|
||||
|
||||
> **Warning**
|
||||
> The group id is a number!
|
||||
30
packages/cli-template-hardhat/contracts/Feedback.sol
Normal file
30
packages/cli-template-hardhat/contracts/Feedback.sol
Normal file
@@ -0,0 +1,30 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
|
||||
|
||||
contract Feedback {
|
||||
ISemaphore public semaphore;
|
||||
|
||||
uint256 public groupId;
|
||||
|
||||
constructor(address semaphoreAddress, uint256 _groupId) {
|
||||
semaphore = ISemaphore(semaphoreAddress);
|
||||
groupId = _groupId;
|
||||
|
||||
semaphore.createGroup(groupId, 20, address(this));
|
||||
}
|
||||
|
||||
function joinGroup(uint256 identityCommitment) external {
|
||||
semaphore.addMember(groupId, identityCommitment);
|
||||
}
|
||||
|
||||
function sendFeedback(
|
||||
uint256 feedback,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 nullifierHash,
|
||||
uint256[8] calldata proof
|
||||
) external {
|
||||
semaphore.verifyProof(groupId, merkleTreeRoot, feedback, nullifierHash, groupId, proof);
|
||||
}
|
||||
}
|
||||
78
packages/cli-template-hardhat/hardhat.config.ts
Normal file
78
packages/cli-template-hardhat/hardhat.config.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import "@nomiclabs/hardhat-ethers"
|
||||
import "@nomicfoundation/hardhat-chai-matchers"
|
||||
import "@semaphore-protocol/hardhat"
|
||||
import "@typechain/hardhat"
|
||||
import { config as dotenvConfig } from "dotenv"
|
||||
import "hardhat-gas-reporter"
|
||||
import { HardhatUserConfig } from "hardhat/config"
|
||||
import { NetworksUserConfig } from "hardhat/types"
|
||||
import { resolve } from "path"
|
||||
import "solidity-coverage"
|
||||
import { config } from "./package.json"
|
||||
import "./tasks/deploy"
|
||||
|
||||
dotenvConfig({ path: resolve(__dirname, "../../.env") })
|
||||
|
||||
function getNetworks(): NetworksUserConfig {
|
||||
if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`]
|
||||
const infuraApiKey = process.env.INFURA_API_KEY
|
||||
|
||||
return {
|
||||
goerli: {
|
||||
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
sepolia: {
|
||||
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 11155111,
|
||||
accounts
|
||||
},
|
||||
mumbai: {
|
||||
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 80001,
|
||||
accounts
|
||||
},
|
||||
"optimism-goerli": {
|
||||
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 42161,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hardhatConfig: HardhatUserConfig = {
|
||||
solidity: config.solidity,
|
||||
paths: {
|
||||
sources: config.paths.contracts,
|
||||
tests: config.paths.tests,
|
||||
cache: config.paths.cache,
|
||||
artifacts: config.paths.build.contracts
|
||||
},
|
||||
networks: {
|
||||
hardhat: {
|
||||
chainId: 1337
|
||||
},
|
||||
...getNetworks()
|
||||
},
|
||||
gasReporter: {
|
||||
currency: "USD",
|
||||
enabled: process.env.REPORT_GAS === "true",
|
||||
coinmarketcap: process.env.COINMARKETCAP_API_KEY
|
||||
},
|
||||
typechain: {
|
||||
outDir: config.paths.build.typechain,
|
||||
target: "ethers-v5"
|
||||
}
|
||||
}
|
||||
|
||||
export default hardhatConfig
|
||||
78
packages/cli-template-hardhat/package.json
Normal file
78
packages/cli-template-hardhat/package.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/cli-template-hardhat",
|
||||
"version": "3.3.0",
|
||||
"description": "Semaphore Hardhat template.",
|
||||
"license": "Unlicense",
|
||||
"files": [
|
||||
".gitignore",
|
||||
".env.example",
|
||||
"contracts/",
|
||||
"scripts/",
|
||||
"tasks/",
|
||||
"test/",
|
||||
"hardhat.config.ts",
|
||||
"tsconfig.json",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "hardhat node & yarn compile && yarn deploy --network localhost",
|
||||
"compile": "hardhat compile",
|
||||
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
|
||||
"deploy": "yarn compile && hardhat deploy",
|
||||
"test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test",
|
||||
"test:report-gas": "REPORT_GAS=true hardhat test",
|
||||
"test:coverage": "hardhat coverage",
|
||||
"typechain": "hardhat typechain"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ethersproject/abi": "^5.4.7",
|
||||
"@ethersproject/providers": "^5.4.7",
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
|
||||
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
|
||||
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.0",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.0.0",
|
||||
"@semaphore-protocol/group": "3.3.0",
|
||||
"@semaphore-protocol/hardhat": "3.3.0",
|
||||
"@semaphore-protocol/identity": "3.3.0",
|
||||
"@semaphore-protocol/proof": "3.3.0",
|
||||
"@typechain/ethers-v5": "^10.1.0",
|
||||
"@typechain/hardhat": "^6.1.2",
|
||||
"@types/chai": "^4.2.0",
|
||||
"@types/download": "^8.0.1",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@types/node": ">=12.0.0",
|
||||
"chai": "^4.2.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"download": "^8.0.0",
|
||||
"ethers": "^5.4.7",
|
||||
"hardhat": "^2.11.0",
|
||||
"hardhat-gas-reporter": "^1.0.8",
|
||||
"solidity-coverage": "^0.8.1",
|
||||
"ts-node": ">=8.0.0",
|
||||
"typechain": "^8.1.0",
|
||||
"typescript": ">=4.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/contracts": "3.3.0"
|
||||
},
|
||||
"config": {
|
||||
"solidity": {
|
||||
"version": "0.8.4"
|
||||
},
|
||||
"paths": {
|
||||
"contracts": "./contracts",
|
||||
"tests": "./test",
|
||||
"cache": "./cache",
|
||||
"build": {
|
||||
"snark-artifacts": "./build/snark-artifacts",
|
||||
"contracts": "./build/contracts",
|
||||
"typechain": "./build/typechain"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import download from "download"
|
||||
import fs from "fs"
|
||||
import { config } from "../package.json"
|
||||
|
||||
async function main() {
|
||||
const snarkArtifactsPath = config.paths.build["snark-artifacts"]
|
||||
const url = `http://www.trusted-setup-pse.org/semaphore/${20}`
|
||||
|
||||
if (!fs.existsSync(snarkArtifactsPath)) {
|
||||
fs.mkdirSync(snarkArtifactsPath, { recursive: true })
|
||||
}
|
||||
|
||||
if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) {
|
||||
await download(`${url}/semaphore.wasm`, snarkArtifactsPath)
|
||||
await download(`${url}/semaphore.zkey`, snarkArtifactsPath)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
31
packages/cli-template-hardhat/tasks/deploy.ts
Normal file
31
packages/cli-template-hardhat/tasks/deploy.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy", "Deploy a Feedback contract")
|
||||
.addOptionalParam("semaphore", "Semaphore contract address", undefined, types.string)
|
||||
.addOptionalParam("group", "Group id", "42", types.string)
|
||||
.addOptionalParam("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs, semaphore: semaphoreAddress, group: groupId }, { ethers, run }) => {
|
||||
if (!semaphoreAddress) {
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs
|
||||
})
|
||||
|
||||
semaphoreAddress = semaphore.address
|
||||
}
|
||||
|
||||
if (!groupId) {
|
||||
groupId = process.env.GROUP_ID
|
||||
}
|
||||
|
||||
const FeedbackFactory = await ethers.getContractFactory("Feedback")
|
||||
|
||||
const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId)
|
||||
|
||||
await feedbackContract.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`)
|
||||
}
|
||||
|
||||
return feedbackContract
|
||||
})
|
||||
69
packages/cli-template-hardhat/test/Feedback.ts
Normal file
69
packages/cli-template-hardhat/test/Feedback.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import { formatBytes32String } from "ethers/lib/utils"
|
||||
import { run } from "hardhat"
|
||||
// @ts-ignore: typechain folder will be generated after contracts compilation
|
||||
import { Feedback } from "../build/typechain"
|
||||
import { config } from "../package.json"
|
||||
|
||||
describe("Feedback", () => {
|
||||
let feedbackContract: Feedback
|
||||
let semaphoreContract: string
|
||||
|
||||
const groupId = "42"
|
||||
const group = new Group(groupId)
|
||||
const users: Identity[] = []
|
||||
|
||||
before(async () => {
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: semaphore.address })
|
||||
semaphoreContract = semaphore
|
||||
|
||||
users.push(new Identity())
|
||||
users.push(new Identity())
|
||||
})
|
||||
|
||||
describe("# joinGroup", () => {
|
||||
it("Should allow users to join the group", async () => {
|
||||
for await (const [i, user] of users.entries()) {
|
||||
const transaction = feedbackContract.joinGroup(user.commitment)
|
||||
|
||||
group.addMember(user.commitment)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "MemberAdded")
|
||||
.withArgs(groupId, i, user.commitment, group.root)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("# sendFeedback", () => {
|
||||
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
|
||||
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
|
||||
|
||||
it("Should allow users to send feedback anonymously", async () => {
|
||||
const feedback = formatBytes32String("Hello World")
|
||||
|
||||
const fullProof = await generateProof(users[1], group, groupId, feedback, {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
|
||||
const transaction = feedbackContract.sendFeedback(
|
||||
feedback,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "ProofVerified")
|
||||
.withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal)
|
||||
})
|
||||
})
|
||||
})
|
||||
15
packages/cli-template-hardhat/tsconfig.json
Normal file
15
packages/cli-template-hardhat/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "Node",
|
||||
"noImplicitAny": true,
|
||||
"resolveJsonModule": true,
|
||||
"target": "ES2018",
|
||||
"module": "CommonJS",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"typeRoots": ["node_modules/@types", "types"]
|
||||
},
|
||||
"include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"],
|
||||
"files": ["./hardhat.config.ts"]
|
||||
}
|
||||
82
packages/cli/README.md
Normal file
82
packages/cli/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Semaphore CLI
|
||||
</h1>
|
||||
<p align="center">A command line tool to set up your Semaphore project and get group data.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/semaphore-protocol">
|
||||
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@semaphore-protocol/cli">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/cli?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/cli">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/cli.svg?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://eslint.org/">
|
||||
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
|
||||
</a>
|
||||
<a href="https://prettier.io/">
|
||||
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h4>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
|
||||
👥 Contributing
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
|
||||
🤝 Code of conduct
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| Setting up a project, although not particularly complex, can be a lengthy process for some people. The Semaphore CLI reduces the set-up time from a few minutes to a few seconds. In addition, it can also be used to obtain on-chain group data. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
Install the `@semaphore-protocol/cli` package globally:
|
||||
|
||||
```bash
|
||||
npm i -g @semaphore-protocol/cli
|
||||
```
|
||||
|
||||
or run specific commands with `npx`:
|
||||
|
||||
```bash
|
||||
npx @semaphore-protocol/cli create my-app
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
```
|
||||
Usage: semaphore [options] [command]
|
||||
|
||||
A command line tool to set up your Semaphore project and get group data.
|
||||
|
||||
Options:
|
||||
-v, --version Show Semaphore CLI version.
|
||||
-h, --help Display this help.
|
||||
|
||||
Commands:
|
||||
create <project-directory> Create a Semaphore project with a supported template.
|
||||
get-groups [options] Get the list of groups from a supported network (Goerli or Arbitrum).
|
||||
get-group [options] <group-id> Get the data of a group from a supported network (Goerli or Arbitrum).
|
||||
help [command] Display help for a specific command.
|
||||
```
|
||||
9
packages/cli/build.tsconfig.json
Normal file
9
packages/cli/build.tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "es2020",
|
||||
"module": "ESNext",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
56
packages/cli/package.json
Normal file
56
packages/cli/package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/cli",
|
||||
"type": "module",
|
||||
"version": "3.3.0",
|
||||
"description": "A command line tool to set up your Semaphore project and get group data.",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"semaphore": "dist/index.js"
|
||||
},
|
||||
"types": "dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist/",
|
||||
"src/",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"repository": "https://github.com/semaphore-protocol/semaphore",
|
||||
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli",
|
||||
"bugs": {
|
||||
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "ts-node --esm src/index.ts",
|
||||
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
|
||||
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/clear": "^0.1.2",
|
||||
"@types/figlet": "^1.5.5",
|
||||
"@types/semver": "^7.3.13",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/data": "3.3.0",
|
||||
"axios": "^1.3.2",
|
||||
"boxen": "^7.0.1",
|
||||
"chalk": "^5.1.2",
|
||||
"commander": "^9.4.1",
|
||||
"download": "^8.0.0",
|
||||
"figlet": "^1.5.2",
|
||||
"inquirer": "^9.1.4",
|
||||
"log-symbols": "^5.1.0",
|
||||
"ora": "^6.1.2",
|
||||
"semver": "^7.3.8"
|
||||
}
|
||||
}
|
||||
29
packages/cli/rollup.config.ts
Normal file
29
packages/cli/rollup.config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
import fs from "fs"
|
||||
import cleanup from "rollup-plugin-cleanup"
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
|
||||
const banner = `#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* @module ${pkg.name}
|
||||
* @version ${pkg.version}
|
||||
* @file ${pkg.description}
|
||||
* @copyright Ethereum Foundation 2022
|
||||
* @license ${pkg.license}
|
||||
* @see [Github]{@link ${pkg.homepage}}
|
||||
*/
|
||||
`
|
||||
|
||||
export default {
|
||||
input: "src/index.ts",
|
||||
output: [{ file: pkg.bin.semaphore, format: "es", banner }],
|
||||
external: ["url", "fs", "path", ...Object.keys(pkg.dependencies)],
|
||||
plugins: [
|
||||
(typescript as any)({
|
||||
tsconfig: "./build.tsconfig.json",
|
||||
useTsconfigDeclarationDir: true
|
||||
}),
|
||||
cleanup({ comments: "jsdoc" })
|
||||
]
|
||||
}
|
||||
48
packages/cli/src/checkLatestVersion.ts
Normal file
48
packages/cli/src/checkLatestVersion.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import axios from "axios"
|
||||
import boxen from "boxen"
|
||||
import chalk from "chalk"
|
||||
import { execSync } from "child_process"
|
||||
import { lt as semverLt } from "semver"
|
||||
|
||||
const cliRegistryURL = "https://registry.npmjs.org/-/package/@semaphore-protocol/cli/dist-tags"
|
||||
|
||||
/**
|
||||
* Checks the registry directly via the API, if that fails, tries the slower `npm view [package] version` command.
|
||||
* This is important for users in environments where direct access to npm is blocked by a firewall, and packages are
|
||||
* provided exclusively via a private registry.
|
||||
* @param currentVersion The current version of the CLI.
|
||||
*/
|
||||
export default async function checkLatestVersion(currentVersion: string) {
|
||||
let latestVersion: string
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(cliRegistryURL)
|
||||
|
||||
latestVersion = data.latest
|
||||
} catch {
|
||||
try {
|
||||
latestVersion = execSync("npm view @semaphore-protocol/cli version").toString().trim()
|
||||
} catch {
|
||||
latestVersion = null
|
||||
}
|
||||
}
|
||||
|
||||
if (latestVersion && semverLt(currentVersion, latestVersion)) {
|
||||
console.info("\n")
|
||||
console.info(
|
||||
boxen(
|
||||
chalk.white(
|
||||
`Update available ${chalk.gray(currentVersion)} -> ${chalk.green(
|
||||
latestVersion
|
||||
)} \n\n You are currently using @semaphore-protocol/cli ${chalk.gray(
|
||||
currentVersion
|
||||
)} which is behind the latest release ${chalk.green(latestVersion)} \n\n Run ${chalk.cyan(
|
||||
"npm install -g @semaphore-protocol/cli@latest"
|
||||
)} to get the latest version`
|
||||
),
|
||||
{ padding: 1, borderColor: "yellow", textAlignment: "center" }
|
||||
)
|
||||
)
|
||||
console.info("")
|
||||
}
|
||||
}
|
||||
304
packages/cli/src/index.ts
Normal file
304
packages/cli/src/index.ts
Normal file
@@ -0,0 +1,304 @@
|
||||
import { SemaphoreSubgraph, SemaphoreEthers, GroupResponse } from "@semaphore-protocol/data"
|
||||
import chalk from "chalk"
|
||||
import { program } from "commander"
|
||||
import download from "download"
|
||||
import figlet from "figlet"
|
||||
import { existsSync, readFileSync, renameSync } from "fs"
|
||||
import inquirer from "inquirer"
|
||||
import logSymbols from "log-symbols"
|
||||
import { dirname } from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import checkLatestVersion from "./checkLatestVersion.js"
|
||||
import Spinner from "./spinner.js"
|
||||
|
||||
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`
|
||||
const { description, version } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"))
|
||||
|
||||
const supportedNetworks = ["sepolia", "goerli", "mumbai", "optimism-goerli", "arbitrum"]
|
||||
|
||||
program
|
||||
.name("semaphore")
|
||||
.description(description)
|
||||
.version(version, "-v, --version", "Show Semaphore CLI version.")
|
||||
.addHelpText("before", `${figlet.textSync("Semaphore")}\n`)
|
||||
.addHelpText("after", "\r")
|
||||
.helpOption(undefined, "Display this help.")
|
||||
.addHelpCommand("help [command]", "Display help for a specific command.")
|
||||
.configureOutput({
|
||||
outputError: (message) => {
|
||||
console.info(`\n ${logSymbols.error}`, message)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command("create")
|
||||
.description("Create a Semaphore project with a supported template.")
|
||||
.argument("[project-directory]", "Directory of the project.")
|
||||
// .option("-t, --template <template-name>", "Supported Semaphore template.", "hardhat")
|
||||
.allowExcessArguments(false)
|
||||
.action(async (projectDirectory) => {
|
||||
if (!projectDirectory) {
|
||||
const { projectName } = await inquirer.prompt({
|
||||
name: "projectName",
|
||||
type: "input",
|
||||
message: "What is your project name?",
|
||||
default: "my-app"
|
||||
})
|
||||
projectDirectory = projectName
|
||||
}
|
||||
|
||||
const currentDirectory = process.cwd()
|
||||
const spinner = new Spinner(`Creating your project in ${chalk.green(`./${projectDirectory}`)}`)
|
||||
const templateURL = `https://registry.npmjs.org/@semaphore-protocol/cli-template-hardhat/-/cli-template-hardhat-${version}.tgz`
|
||||
|
||||
if (existsSync(projectDirectory)) {
|
||||
console.info(`\n ${logSymbols.error}`, `error: the '${projectDirectory}' folder already exists\n`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
spinner.start()
|
||||
|
||||
await checkLatestVersion(version)
|
||||
|
||||
await download(templateURL, currentDirectory, { extract: true })
|
||||
|
||||
renameSync(`${currentDirectory}/package`, `${currentDirectory}/${projectDirectory}`)
|
||||
|
||||
spinner.stop()
|
||||
|
||||
console.info(`\n ${logSymbols.success}`, `Your project is ready!\n`)
|
||||
console.info(` Please, install your dependencies by running:\n`)
|
||||
console.info(` ${chalk.cyan("cd")} ${projectDirectory}`)
|
||||
console.info(` ${chalk.cyan("npm i")}\n`)
|
||||
|
||||
const { scripts } = JSON.parse(readFileSync(`${currentDirectory}/${projectDirectory}/package.json`, "utf8"))
|
||||
|
||||
if (scripts) {
|
||||
console.info(` Available scripts:\n`)
|
||||
|
||||
console.info(
|
||||
`${Object.keys(scripts)
|
||||
.map((s) => ` ${chalk.cyan(`npm run ${s}`)}`)
|
||||
.join("\n")}\n`
|
||||
)
|
||||
|
||||
console.info(` See the README.md file to understand how to use them!\n`)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command("get-groups")
|
||||
.description("Get the list of groups from a supported network (e.g. goerli or arbitrum).")
|
||||
.option("-n, --network <network-name>", "Supported Ethereum network.")
|
||||
.allowExcessArguments(false)
|
||||
.action(async ({ network }) => {
|
||||
if (!network) {
|
||||
const { selectedNetwork } = await inquirer.prompt({
|
||||
name: "selectedNetwork",
|
||||
type: "list",
|
||||
message: "Select one of our supported networks:",
|
||||
default: supportedNetworks[0],
|
||||
choices: supportedNetworks
|
||||
})
|
||||
network = selectedNetwork
|
||||
}
|
||||
|
||||
if (!supportedNetworks.includes(network)) {
|
||||
console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let groupIds: string[]
|
||||
|
||||
const spinner = new Spinner("Fetching groups")
|
||||
|
||||
spinner.start()
|
||||
|
||||
try {
|
||||
const semaphoreSubgraph = new SemaphoreSubgraph(network)
|
||||
|
||||
groupIds = await semaphoreSubgraph.getGroupIds()
|
||||
|
||||
spinner.stop()
|
||||
} catch {
|
||||
try {
|
||||
const semaphoreEthers = new SemaphoreEthers(network)
|
||||
|
||||
groupIds = await semaphoreEthers.getGroupIds()
|
||||
|
||||
spinner.stop()
|
||||
} catch {
|
||||
spinner.stop()
|
||||
|
||||
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package")
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
if (groupIds.length === 0) {
|
||||
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const content = `\n${groupIds.map((id: any) => ` - ${id}`).join("\n")}`
|
||||
|
||||
console.info(`${content}\n`)
|
||||
})
|
||||
|
||||
program
|
||||
.command("get-group")
|
||||
.description("Get the data of a group from a supported network (e.g. goerli or arbitrum).")
|
||||
.argument("[group-id]", "Identifier of the group.")
|
||||
.option("-n, --network <network-name>", "Supported Ethereum network.")
|
||||
.option("-m, --members", "Show group members.")
|
||||
.option("-s, --signals", "Show group signals.")
|
||||
.allowExcessArguments(false)
|
||||
.action(async (groupId, { network, members, signals }) => {
|
||||
if (!network) {
|
||||
const { selectedNetwork } = await inquirer.prompt({
|
||||
name: "selectedNetwork",
|
||||
type: "list",
|
||||
message: "Select one of our supported networks:",
|
||||
default: supportedNetworks[0],
|
||||
choices: supportedNetworks
|
||||
})
|
||||
|
||||
network = selectedNetwork
|
||||
}
|
||||
|
||||
if (!supportedNetworks.includes(network)) {
|
||||
console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!groupId) {
|
||||
let groupIds: string[]
|
||||
|
||||
const spinnerGroups = new Spinner("Fetching groups")
|
||||
|
||||
spinnerGroups.start()
|
||||
|
||||
try {
|
||||
const semaphoreSubgraphGroups = new SemaphoreSubgraph(network)
|
||||
|
||||
groupIds = await semaphoreSubgraphGroups.getGroupIds()
|
||||
|
||||
spinnerGroups.stop()
|
||||
} catch {
|
||||
try {
|
||||
const semaphoreEthersGroups = new SemaphoreEthers(network)
|
||||
|
||||
groupIds = await semaphoreEthersGroups.getGroupIds()
|
||||
|
||||
spinnerGroups.stop()
|
||||
} catch {
|
||||
spinnerGroups.stop()
|
||||
|
||||
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package")
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (groupIds.length === 0) {
|
||||
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const { selectedGroupId } = await inquirer.prompt({
|
||||
name: "selectedGroupId",
|
||||
type: "list",
|
||||
message: "Select one of the following existing group ids:",
|
||||
choices: groupIds
|
||||
})
|
||||
|
||||
groupId = selectedGroupId
|
||||
}
|
||||
|
||||
if (!members && !signals) {
|
||||
const { showMembers } = await inquirer.prompt({
|
||||
name: "showMembers",
|
||||
type: "confirm",
|
||||
message: "Do you want to show members?",
|
||||
default: false
|
||||
})
|
||||
|
||||
members = showMembers
|
||||
|
||||
const { showSignals } = await inquirer.prompt({
|
||||
name: "showSignals",
|
||||
type: "confirm",
|
||||
message: "Do you want to show signals?",
|
||||
default: false
|
||||
})
|
||||
|
||||
signals = showSignals
|
||||
}
|
||||
|
||||
let group: GroupResponse
|
||||
|
||||
const spinner = new Spinner(`Fetching group ${groupId}`)
|
||||
|
||||
spinner.start()
|
||||
|
||||
try {
|
||||
const semaphoreSubgraph = new SemaphoreSubgraph(network)
|
||||
|
||||
group = await semaphoreSubgraph.getGroup(groupId, { members, verifiedProofs: signals })
|
||||
|
||||
spinner.stop()
|
||||
} catch {
|
||||
try {
|
||||
const semaphoreEthers = new SemaphoreEthers(network)
|
||||
|
||||
group = await semaphoreEthers.getGroup(groupId)
|
||||
|
||||
if (members) {
|
||||
group.members = await semaphoreEthers.getGroupMembers(groupId)
|
||||
}
|
||||
|
||||
if (signals) {
|
||||
group.verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs(groupId)
|
||||
}
|
||||
|
||||
group.admin = await semaphoreEthers.getGroupAdmin(groupId)
|
||||
|
||||
spinner.stop()
|
||||
} catch {
|
||||
spinner.stop()
|
||||
|
||||
console.info(`\n ${logSymbols.error}`, "error: the group does not exist\n")
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let content = ` ${chalk.bold("Id")}: ${group.id}\n`
|
||||
content += ` ${chalk.bold("Admin")}: ${group.admin}\n`
|
||||
content += ` ${chalk.bold("Merkle tree")}:\n`
|
||||
content += ` Root: ${group.merkleTree.root}\n`
|
||||
content += ` Depth: ${group.merkleTree.depth}\n`
|
||||
content += ` Zero value: ${group.merkleTree.zeroValue}\n`
|
||||
content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}`
|
||||
|
||||
if (members) {
|
||||
content += `\n\n ${chalk.bold("Members")}: \n${group.members
|
||||
.map((member: string, i: number) => ` ${i}. ${member}`)
|
||||
.join("\n")}`
|
||||
}
|
||||
|
||||
if (signals) {
|
||||
content += `\n\n ${chalk.bold("Signals")}: \n${group.verifiedProofs
|
||||
.map(({ signal }: any) => ` - ${signal}`)
|
||||
.join("\n")}`
|
||||
}
|
||||
|
||||
console.info(`\n${content}\n`)
|
||||
})
|
||||
|
||||
program.parse(process.argv)
|
||||
25
packages/cli/src/spinner.ts
Normal file
25
packages/cli/src/spinner.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import ora, { Ora } from "ora"
|
||||
|
||||
export default class Spinner {
|
||||
private text: string
|
||||
private ora: Ora
|
||||
|
||||
constructor(text: string) {
|
||||
this.text = text
|
||||
}
|
||||
|
||||
start() {
|
||||
console.info("")
|
||||
|
||||
this.ora = ora({
|
||||
text: this.text,
|
||||
indent: 1
|
||||
}).start()
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.ora.stop()
|
||||
|
||||
process.stdout.moveCursor(0, -1)
|
||||
}
|
||||
}
|
||||
10
packages/cli/tsconfig.json
Normal file
10
packages/cli/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "es2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node16",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src", "rollup.config.ts"]
|
||||
}
|
||||
21
packages/contracts/contracts/LICENSE
Normal file
21
packages/contracts/contracts/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Ethereum Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -34,7 +34,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
@@ -92,29 +92,29 @@ yarn test:report-gas
|
||||
|
||||
### Deploy contracts
|
||||
|
||||
Deploy a verifier contract with depth = 20:
|
||||
Deploy the `Semaphore.sol` contract without any parameter:
|
||||
|
||||
```bash
|
||||
yarn deploy:verifier --depth 20
|
||||
yarn deploy:semaphore
|
||||
```
|
||||
|
||||
Deploy the `Semaphore.sol` contract with one verifier:
|
||||
or deploy it by providing the addresses of the contracts/libraries on which it depends:
|
||||
|
||||
```bash
|
||||
yarn deploy:semaphore --verifiers '[{"merkleTreeDepth": 20, "contractAddress": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1"}]'
|
||||
yarn deploy:semaphore --semaphoreVerifier <address>
|
||||
```
|
||||
|
||||
Deploy all verifiers and Semaphore contract:
|
||||
> **Note**
|
||||
> Run `yarn deploy:semaphore --help` to see the complete list.
|
||||
|
||||
If you want to deploy your contract in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as an option:
|
||||
|
||||
```bash
|
||||
yarn deploy:all
|
||||
```
|
||||
|
||||
If you want to deploy contracts in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as option:
|
||||
|
||||
```bash
|
||||
yarn deploy:all --network goerli
|
||||
yarn deploy:all --network localhost
|
||||
yarn deploy:semaphore --network goerli
|
||||
yarn deploy:semaphore --network sepolia
|
||||
yarn deploy:semaphore --network mumbai
|
||||
yarn deploy:semaphore --network optimism-goerli
|
||||
yarn deploy:semaphore --network arbitrum
|
||||
```
|
||||
|
||||
If you want to deploy contracts on Goerli or Arbitrum, remember to provide a valid private key and an Infura API in your `.env` file.
|
||||
|
||||
@@ -6,6 +6,12 @@ import "./interfaces/ISemaphoreVerifier.sol";
|
||||
import "./base/SemaphoreGroups.sol";
|
||||
|
||||
/// @title Semaphore
|
||||
/// @dev This contract uses the Semaphore base contracts to provide a complete service
|
||||
/// to allow admins to create and manage groups and their members to generate Semaphore proofs
|
||||
/// and verify them. Group admins can add, update or remove group members, and can be
|
||||
/// an Ethereum account or a smart contract. This contract also assigns each new Merkle tree
|
||||
/// generated with a new root a duration (or an expiry) within which the proofs generated with that root
|
||||
/// can be validated.
|
||||
contract Semaphore is ISemaphore, SemaphoreGroups {
|
||||
ISemaphoreVerifier public verifier;
|
||||
|
||||
@@ -40,13 +46,12 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
|
||||
function createGroup(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeDepth,
|
||||
uint256 zeroValue,
|
||||
address admin
|
||||
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
|
||||
_createGroup(groupId, merkleTreeDepth, zeroValue);
|
||||
_createGroup(groupId, merkleTreeDepth);
|
||||
|
||||
groups[groupId].admin = admin;
|
||||
groups[groupId].merkleRootDuration = 1 hours;
|
||||
groups[groupId].merkleTreeDuration = 1 hours;
|
||||
|
||||
emit GroupAdminUpdated(groupId, address(0), admin);
|
||||
}
|
||||
@@ -55,14 +60,13 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
|
||||
function createGroup(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeDepth,
|
||||
uint256 zeroValue,
|
||||
address admin,
|
||||
uint256 merkleTreeRootDuration
|
||||
uint256 merkleTreeDuration
|
||||
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
|
||||
_createGroup(groupId, merkleTreeDepth, zeroValue);
|
||||
_createGroup(groupId, merkleTreeDepth);
|
||||
|
||||
groups[groupId].admin = admin;
|
||||
groups[groupId].merkleRootDuration = merkleTreeRootDuration;
|
||||
groups[groupId].merkleTreeDuration = merkleTreeDuration;
|
||||
|
||||
emit GroupAdminUpdated(groupId, address(0), admin);
|
||||
}
|
||||
@@ -74,6 +78,19 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
|
||||
emit GroupAdminUpdated(groupId, _msgSender(), newAdmin);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-updateGroupMerkleTreeDuration}.
|
||||
function updateGroupMerkleTreeDuration(uint256 groupId, uint256 newMerkleTreeDuration)
|
||||
external
|
||||
override
|
||||
onlyGroupAdmin(groupId)
|
||||
{
|
||||
uint256 oldMerkleTreeDuration = groups[groupId].merkleTreeDuration;
|
||||
|
||||
groups[groupId].merkleTreeDuration = newMerkleTreeDuration;
|
||||
|
||||
emit GroupMerkleTreeDurationUpdated(groupId, oldMerkleTreeDuration, newMerkleTreeDuration);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-addMember}.
|
||||
function addMember(uint256 groupId, uint256 identityCommitment) external override onlyGroupAdmin(groupId) {
|
||||
_addMember(groupId, identityCommitment);
|
||||
@@ -89,7 +106,7 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
|
||||
override
|
||||
onlyGroupAdmin(groupId)
|
||||
{
|
||||
for (uint8 i = 0; i < identityCommitments.length; ) {
|
||||
for (uint256 i = 0; i < identityCommitments.length; ) {
|
||||
_addMember(groupId, identityCommitments[i]);
|
||||
|
||||
unchecked {
|
||||
@@ -140,21 +157,25 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
|
||||
uint256 externalNullifier,
|
||||
uint256[8] calldata proof
|
||||
) external override {
|
||||
uint256 currentMerkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
uint256 merkleTreeDepth = getMerkleTreeDepth(groupId);
|
||||
|
||||
if (currentMerkleTreeRoot == 0) {
|
||||
if (merkleTreeDepth == 0) {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
uint256 currentMerkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
|
||||
// A proof could have used an old Merkle tree root.
|
||||
// https://github.com/semaphore-protocol/semaphore/issues/98
|
||||
if (merkleTreeRoot != currentMerkleTreeRoot) {
|
||||
uint256 merkleRootCreationDate = groups[groupId].merkleRootCreationDates[merkleTreeRoot];
|
||||
uint256 merkleRootDuration = groups[groupId].merkleRootDuration;
|
||||
uint256 merkleTreeDuration = groups[groupId].merkleTreeDuration;
|
||||
|
||||
if (merkleRootCreationDate == 0) {
|
||||
revert Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
|
||||
}
|
||||
|
||||
if (block.timestamp > merkleRootCreationDate + merkleRootDuration) {
|
||||
if (block.timestamp > merkleRootCreationDate + merkleTreeDuration) {
|
||||
revert Semaphore__MerkleTreeRootIsExpired();
|
||||
}
|
||||
}
|
||||
@@ -163,8 +184,6 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
|
||||
revert Semaphore__YouAreUsingTheSameNillifierTwice();
|
||||
}
|
||||
|
||||
uint256 merkleTreeDepth = getMerkleTreeDepth(groupId);
|
||||
|
||||
verifier.verifyProof(merkleTreeRoot, nullifierHash, signal, externalNullifier, proof, merkleTreeDepth);
|
||||
|
||||
groups[groupId].nullifierHashes[nullifierHash] = true;
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
library Pairing {
|
||||
error Semaphore__InvalidProof();
|
||||
error InvalidProof();
|
||||
|
||||
// The prime q in the base field F_q for G1
|
||||
uint256 constant BASE_MODULUS = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||
|
||||
// The prime moludus of the scalar field of G1.
|
||||
// The prime modulus of the scalar field of G1.
|
||||
uint256 constant SCALAR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
|
||||
struct G1Point {
|
||||
@@ -56,7 +56,7 @@ library Pairing {
|
||||
|
||||
// Validate input or revert
|
||||
if (p.X >= BASE_MODULUS || p.Y >= BASE_MODULUS) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
|
||||
// We know p.Y > 0 and p.Y < BASE_MODULUS.
|
||||
@@ -82,7 +82,7 @@ library Pairing {
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ library Pairing {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
if (s >= SCALAR_MODULUS) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
|
||||
uint256[3] memory input;
|
||||
@@ -109,7 +109,7 @@ library Pairing {
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ library Pairing {
|
||||
// By EIP-197 all input is verified to be less than the BASE_MODULUS and form elements in their
|
||||
// respective groups of the right order.
|
||||
if (p1.length != p2.length) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
|
||||
uint256 elements = p1.length;
|
||||
@@ -145,7 +145,7 @@ library Pairing {
|
||||
}
|
||||
|
||||
if (!success || out[0] != 1) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
@@ -1,38 +1,33 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import {SNARK_SCALAR_FIELD} from "./SemaphoreConstants.sol";
|
||||
import "../interfaces/ISemaphoreGroups.sol";
|
||||
import "@zk-kit/incremental-merkle-tree.sol/IncrementalBinaryTree.sol";
|
||||
import "@openzeppelin/contracts/utils/Context.sol";
|
||||
|
||||
/// @title Semaphore groups contract.
|
||||
/// @dev The following code allows you to create groups, add and remove members.
|
||||
/// @dev This contract allows you to create groups, add, remove and update members.
|
||||
/// You can use getters to obtain informations about groups (root, depth, number of leaves).
|
||||
abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
|
||||
using IncrementalBinaryTree for IncrementalTreeData;
|
||||
|
||||
/// @dev Gets a group id and returns the tree data.
|
||||
mapping(uint256 => IncrementalTreeData) internal merkleTree;
|
||||
mapping(uint256 => IncrementalTreeData) internal merkleTrees;
|
||||
|
||||
/// @dev Creates a new group by initializing the associated tree.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param merkleTreeDepth: Depth of the tree.
|
||||
/// @param zeroValue: Zero value of the tree.
|
||||
function _createGroup(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeDepth,
|
||||
uint256 zeroValue
|
||||
) internal virtual {
|
||||
if (groupId >= SNARK_SCALAR_FIELD) {
|
||||
revert Semaphore__GroupIdIsNotLessThanSnarkScalarField();
|
||||
}
|
||||
|
||||
function _createGroup(uint256 groupId, uint256 merkleTreeDepth) internal virtual {
|
||||
if (getMerkleTreeDepth(groupId) != 0) {
|
||||
revert Semaphore__GroupAlreadyExists();
|
||||
}
|
||||
|
||||
merkleTree[groupId].init(merkleTreeDepth, zeroValue);
|
||||
// The zeroValue is an implicit member of the group, or an implicit leaf of the Merkle tree.
|
||||
// Although there is a remote possibility that the preimage of
|
||||
// the hash may be calculated, using this value we aim to minimize the risk.
|
||||
uint256 zeroValue = uint256(keccak256(abi.encodePacked(groupId))) >> 8;
|
||||
|
||||
merkleTrees[groupId].init(merkleTreeDepth, zeroValue);
|
||||
|
||||
emit GroupCreated(groupId, merkleTreeDepth, zeroValue);
|
||||
}
|
||||
@@ -45,7 +40,7 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
merkleTree[groupId].insert(identityCommitment);
|
||||
merkleTrees[groupId].insert(identityCommitment);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
uint256 index = getNumberOfMerkleTreeLeaves(groupId) - 1;
|
||||
@@ -67,11 +62,11 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) internal virtual {
|
||||
if (getMerkleTreeRoot(groupId) == 0) {
|
||||
if (getMerkleTreeDepth(groupId) == 0) {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
merkleTree[groupId].update(identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
|
||||
merkleTrees[groupId].update(identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
|
||||
@@ -91,11 +86,11 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) internal virtual {
|
||||
if (getMerkleTreeRoot(groupId) == 0) {
|
||||
if (getMerkleTreeDepth(groupId) == 0) {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
merkleTree[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);
|
||||
merkleTrees[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
|
||||
@@ -105,17 +100,17 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
|
||||
|
||||
/// @dev See {ISemaphoreGroups-getMerkleTreeRoot}.
|
||||
function getMerkleTreeRoot(uint256 groupId) public view virtual override returns (uint256) {
|
||||
return merkleTree[groupId].root;
|
||||
return merkleTrees[groupId].root;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreGroups-getMerkleTreeDepth}.
|
||||
function getMerkleTreeDepth(uint256 groupId) public view virtual override returns (uint256) {
|
||||
return merkleTree[groupId].depth;
|
||||
return merkleTrees[groupId].depth;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreGroups-getNumberOfMerkleTreeLeaves}.
|
||||
function getNumberOfMerkleTreeLeaves(uint256 groupId) public view virtual override returns (uint256) {
|
||||
return merkleTree[groupId].numberOfLeaves;
|
||||
return merkleTrees[groupId].numberOfLeaves;
|
||||
}
|
||||
|
||||
/// @dev Converts the path indices of a Merkle proof to the identity commitment index in the tree.
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import "../interfaces/ISemaphoreVerifier.sol";
|
||||
import "./SemaphoreConstants.sol";
|
||||
|
||||
/// @title Semaphore verifier contract.
|
||||
/// @notice Minimal code to allow users to verify their Semaphore proofs.
|
||||
/// @dev The following code verifies that the proof is correct and it is a
|
||||
/// modified version of the Verifier Groth16 template of SnarkJS
|
||||
/// @dev This contract allows you to verify whether a Semaphore proof is correct.
|
||||
/// It is a modified version of the Groth16 verifier template of SnarkJS
|
||||
/// (https://github.com/iden3/snarkjs) adapted to Semaphore. The Pairing library
|
||||
/// is extarnal.
|
||||
/// is external.
|
||||
contract SemaphoreVerifier is ISemaphoreVerifier {
|
||||
using Pairing for *;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import "../interfaces/ISemaphoreVerifier.sol";
|
||||
import "../base/SemaphoreGroups.sol";
|
||||
|
||||
/// @title Semaphore voting contract.
|
||||
/// @notice It allows users to vote anonymously in a poll.
|
||||
/// @dev The following code allows you to create polls, add voters and allow them to vote anonymously.
|
||||
contract SemaphoreVoting is ISemaphoreVoting, SemaphoreGroups {
|
||||
ISemaphoreVerifier public verifier;
|
||||
@@ -13,10 +14,6 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreGroups {
|
||||
/// @dev Gets a poll id and returns the poll data.
|
||||
mapping(uint256 => Poll) internal polls;
|
||||
|
||||
/// @dev Gets a nullifier hash and returns true or false.
|
||||
/// It is used to prevent double-voting.
|
||||
mapping(uint256 => bool) internal nullifierHashes;
|
||||
|
||||
/// @dev Checks if the poll coordinator is the transaction sender.
|
||||
/// @param pollId: Id of the poll.
|
||||
modifier onlyCoordinator(uint256 pollId) {
|
||||
@@ -43,13 +40,9 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreGroups {
|
||||
revert Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
}
|
||||
|
||||
_createGroup(pollId, merkleTreeDepth, 0);
|
||||
_createGroup(pollId, merkleTreeDepth);
|
||||
|
||||
Poll memory poll;
|
||||
|
||||
poll.coordinator = coordinator;
|
||||
|
||||
polls[pollId] = poll;
|
||||
polls[pollId].coordinator = coordinator;
|
||||
|
||||
emit PollCreated(pollId, coordinator);
|
||||
}
|
||||
@@ -81,13 +74,11 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreGroups {
|
||||
uint256 pollId,
|
||||
uint256[8] calldata proof
|
||||
) public override {
|
||||
Poll memory poll = polls[pollId];
|
||||
|
||||
if (poll.state != PollState.Ongoing) {
|
||||
if (polls[pollId].state != PollState.Ongoing) {
|
||||
revert Semaphore__PollIsNotOngoing();
|
||||
}
|
||||
|
||||
if (nullifierHashes[nullifierHash]) {
|
||||
if (polls[pollId].nullifierHashes[nullifierHash]) {
|
||||
revert Semaphore__YouAreUsingTheSameNillifierTwice();
|
||||
}
|
||||
|
||||
@@ -96,7 +87,7 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreGroups {
|
||||
|
||||
verifier.verifyProof(merkleTreeRoot, nullifierHash, vote, pollId, proof, merkleTreeDepth);
|
||||
|
||||
nullifierHashes[nullifierHash] = true;
|
||||
polls[pollId].nullifierHashes[nullifierHash] = true;
|
||||
|
||||
emit VoteAdded(pollId, vote);
|
||||
}
|
||||
|
||||
@@ -6,19 +6,20 @@ import "../interfaces/ISemaphoreVerifier.sol";
|
||||
import "../base/SemaphoreGroups.sol";
|
||||
|
||||
/// @title Semaphore whistleblowing contract.
|
||||
/// @notice It allows users to leak information anonymously .
|
||||
/// @dev The following code allows you to create entities for whistleblowers (e.g. non-profit
|
||||
/// organization, newspaper) and to allow them to publish news leaks anonymously.
|
||||
/// Leaks can be IPFS hashes, permanent links or other kinds of reference.
|
||||
/// organization, newspaper) and allow them to leak anonymously.
|
||||
/// Leaks can be IPFS hashes, permanent links or other kinds of references.
|
||||
contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreGroups {
|
||||
ISemaphoreVerifier public verifier;
|
||||
|
||||
/// @dev Gets an editor address and return their entity.
|
||||
mapping(address => uint256) private entities;
|
||||
/// @dev Gets an entity id and return its editor address.
|
||||
mapping(uint256 => address) private entities;
|
||||
|
||||
/// @dev Checks if the editor is the transaction sender.
|
||||
/// @param entityId: Id of the entity.
|
||||
modifier onlyEditor(uint256 entityId) {
|
||||
if (entityId != entities[_msgSender()]) {
|
||||
if (entities[entityId] != _msgSender()) {
|
||||
revert Semaphore__CallerIsNotTheEditor();
|
||||
}
|
||||
|
||||
@@ -41,9 +42,9 @@ contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreGroups {
|
||||
revert Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
}
|
||||
|
||||
_createGroup(entityId, merkleTreeDepth, 0);
|
||||
_createGroup(entityId, merkleTreeDepth);
|
||||
|
||||
entities[editor] = entityId;
|
||||
entities[entityId] = editor;
|
||||
|
||||
emit EntityCreated(entityId, editor);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title Semaphore interface.
|
||||
/// @dev Interface of a Semaphore contract.
|
||||
/// @title Semaphore contract interface.
|
||||
interface ISemaphore {
|
||||
error Semaphore__CallerIsNotTheGroupAdmin();
|
||||
error Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
@@ -10,15 +9,10 @@ interface ISemaphore {
|
||||
error Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
|
||||
error Semaphore__YouAreUsingTheSameNillifierTwice();
|
||||
|
||||
struct Verifier {
|
||||
address contractAddress;
|
||||
uint256 merkleTreeDepth;
|
||||
}
|
||||
|
||||
/// It defines all the group parameters, in addition to those in the Merkle tree.
|
||||
struct Group {
|
||||
address admin;
|
||||
uint256 merkleRootDuration;
|
||||
uint256 merkleTreeDuration;
|
||||
mapping(uint256 => uint256) merkleRootCreationDates;
|
||||
mapping(uint256 => bool) nullifierHashes;
|
||||
}
|
||||
@@ -29,6 +23,16 @@ interface ISemaphore {
|
||||
/// @param newAdmin: New admin of the group.
|
||||
event GroupAdminUpdated(uint256 indexed groupId, address indexed oldAdmin, address indexed newAdmin);
|
||||
|
||||
/// @dev Emitted when the Merkle tree duration of a group is updated.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param oldMerkleTreeDuration: Old Merkle tree duration of the group.
|
||||
/// @param newMerkleTreeDuration: New Merkle tree duration of the group.
|
||||
event GroupMerkleTreeDurationUpdated(
|
||||
uint256 indexed groupId,
|
||||
uint256 oldMerkleTreeDuration,
|
||||
uint256 newMerkleTreeDuration
|
||||
);
|
||||
|
||||
/// @dev Emitted when a Semaphore proof is verified.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param merkleTreeRoot: Root of the Merkle tree.
|
||||
@@ -37,8 +41,8 @@ interface ISemaphore {
|
||||
/// @param signal: Semaphore signal.
|
||||
event ProofVerified(
|
||||
uint256 indexed groupId,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 externalNullifier,
|
||||
uint256 indexed merkleTreeRoot,
|
||||
uint256 indexed externalNullifier,
|
||||
uint256 nullifierHash,
|
||||
uint256 signal
|
||||
);
|
||||
@@ -63,25 +67,21 @@ interface ISemaphore {
|
||||
/// @dev Creates a new group. Only the admin will be able to add or remove members.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param depth: Depth of the tree.
|
||||
/// @param zeroValue: Zero value of the tree.
|
||||
/// @param admin: Admin of the group.
|
||||
function createGroup(
|
||||
uint256 groupId,
|
||||
uint256 depth,
|
||||
uint256 zeroValue,
|
||||
address admin
|
||||
) external;
|
||||
|
||||
/// @dev Creates a new group. Only the admin will be able to add or remove members.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param depth: Depth of the tree.
|
||||
/// @param zeroValue: Zero value of the tree.
|
||||
/// @param admin: Admin of the group.
|
||||
/// @param merkleTreeRootDuration: Time before the validity of a root expires.
|
||||
function createGroup(
|
||||
uint256 groupId,
|
||||
uint256 depth,
|
||||
uint256 zeroValue,
|
||||
address admin,
|
||||
uint256 merkleTreeRootDuration
|
||||
) external;
|
||||
@@ -91,6 +91,11 @@ interface ISemaphore {
|
||||
/// @param newAdmin: New admin of the group.
|
||||
function updateGroupAdmin(uint256 groupId, address newAdmin) external;
|
||||
|
||||
/// @dev Updates the group Merkle tree duration.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param newMerkleTreeDuration: New Merkle tree duration.
|
||||
function updateGroupMerkleTreeDuration(uint256 groupId, uint256 newMerkleTreeDuration) external;
|
||||
|
||||
/// @dev Adds a new member to an existing group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param identityCommitment: New identity commitment.
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreGroups interface.
|
||||
/// @dev Interface of a SemaphoreGroups contract.
|
||||
/// @title SemaphoreGroups contract interface.
|
||||
interface ISemaphoreGroups {
|
||||
error Semaphore__GroupDoesNotExist();
|
||||
error Semaphore__GroupAlreadyExists();
|
||||
error Semaphore__GroupIdIsNotLessThanSnarkScalarField();
|
||||
|
||||
/// @dev Emitted when a new group is created.
|
||||
/// @param groupId: Id of the group.
|
||||
|
||||
@@ -3,8 +3,7 @@ pragma solidity 0.8.4;
|
||||
|
||||
import "../base/Pairing.sol";
|
||||
|
||||
/// @title SemaphoreVerifier interface.
|
||||
/// @dev Interface of SemaphoreVerifier contract.
|
||||
/// @title SemaphoreVerifier contract interface.
|
||||
interface ISemaphoreVerifier {
|
||||
struct VerificationKey {
|
||||
Pairing.G1Point alfa1;
|
||||
@@ -20,7 +19,7 @@ interface ISemaphoreVerifier {
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
/// @dev Verifies that the zero-knowledge proof is valid.
|
||||
/// @dev Verifies whether a Semaphore proof is valid.
|
||||
/// @param merkleTreeRoot: Root of the Merkle tree.
|
||||
/// @param nullifierHash: Nullifier hash.
|
||||
/// @param signal: Semaphore signal.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreVoting interface.
|
||||
/// @dev Interface of SemaphoreVoting contract.
|
||||
/// @title SemaphoreVoting contract interface.
|
||||
interface ISemaphoreVoting {
|
||||
error Semaphore__CallerIsNotThePollCoordinator();
|
||||
error Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
@@ -24,6 +23,7 @@ interface ISemaphoreVoting {
|
||||
struct Poll {
|
||||
address coordinator;
|
||||
PollState state;
|
||||
mapping(uint256 => bool) nullifierHashes;
|
||||
}
|
||||
|
||||
/// @dev Emitted when a new poll is created.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreWhistleblowing interface.
|
||||
/// @dev Interface of SemaphoreWhistleblowing contract.
|
||||
/// @title SemaphoreWhistleblowing contract interface.
|
||||
interface ISemaphoreWhistleblowing {
|
||||
error Semaphore__CallerIsNotTheEditor();
|
||||
error Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/contracts",
|
||||
"version": "2.6.1",
|
||||
"version": "3.3.0",
|
||||
"description": "Semaphore contracts to manage groups and broadcast anonymous signals.",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"Semaphore.sol",
|
||||
"**/*.sol"
|
||||
],
|
||||
"keywords": [
|
||||
@@ -30,6 +31,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "4.7.3",
|
||||
"@zk-kit/incremental-merkle-tree.sol": "1.3.1"
|
||||
"@zk-kit/incremental-merkle-tree.sol": "1.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Pairing": "0xC0ae1a8D3505B2bE9DCe0e826abd722Afd13F1c9",
|
||||
"SemaphoreVerifier": "0x346a936b19071b2f619200848B8ADbb938D72250",
|
||||
"Poseidon": "0xb69aABB5D8d8e4920834761bD0C9DEEfa5D5502F",
|
||||
"IncrementalBinaryTree": "0x9f44be9F69aF1e049dCeCDb2d9296f36C49Ceafb",
|
||||
"Semaphore": "0xbE66454b0Fa9E6b3D53DC1b0f9D21978bb864531"
|
||||
}
|
||||
@@ -1,24 +1,7 @@
|
||||
{
|
||||
"verifiers": {
|
||||
"Verifier16": "0x6143ECd9Fd1A00EDe1046d456f8aab53a7D71609",
|
||||
"Verifier17": "0xAc12fFFE354D6446eb50dd33E683B78FED73Fb02",
|
||||
"Verifier18": "0x610aeF0F2da3CD1C8bDefe4BDB434Ee146E0C701",
|
||||
"Verifier19": "0x5477725177035bbC9d70443eb921D29749D6FCb4",
|
||||
"Verifier20": "0x3fB2C0988a37b76e760c44e6516aF720935f3136",
|
||||
"Verifier21": "0xDc8f6B8A42836d4566256f4c6C53131DFD127DF8",
|
||||
"Verifier22": "0x6962b5e706be5278eeCb01c286b50A48484632f2",
|
||||
"Verifier23": "0x41e4796Bd89B4BF04013b559c93fC32E9a2BdF6B",
|
||||
"Verifier24": "0xD528B1D1408ab3583af4694F92b0aFEbE33d5b60",
|
||||
"Verifier25": "0x1683a27EF9c10c5286dB56412E1272cD0Ca733e7",
|
||||
"Verifier26": "0x78194bB665d1E33b97eE45B1A755c15717E94C00",
|
||||
"Verifier27": "0x997Dac00E6701Ef7F3518280E5a9922801126E42",
|
||||
"Verifier28": "0xDd3C7f4cBA2467aE41c0F614A3c3E24bC80268c6",
|
||||
"Verifier29": "0xe53eF12093933D5df5691EAbA3821bD1c1EB60Cd",
|
||||
"Verifier30": "0x7FeA07c536ABBB0E7FB3c833376EE4EaDc21340e",
|
||||
"Verifier31": "0xe4539a592df18936202480FBe77E47DE012F2178",
|
||||
"Verifier32": "0x98c90845A7870e215cBd7265DDC653E6c07032F4"
|
||||
},
|
||||
"Semaphore": "0x86337c87A56117f8264bbaBA70e5a522C6E8A604",
|
||||
"PoseidonT3": "0xe0c8d1e53D9Bfc9071F6564755FCFf6cC0dB61d0",
|
||||
"IncrementalBinaryTree": "0x91cD2B8573629d00BeC72EA1188d446897BD3948"
|
||||
"Pairing": "0xE3a4C2FE9f025405cA6F60f6E960B4558604A74C",
|
||||
"SemaphoreVerifier": "0xCAbeED6cB96a287000aBd834b0B79c05e6Ea4d07",
|
||||
"Poseidon": "0xe0c8d1e53D9Bfc9071F6564755FCFf6cC0dB61d0",
|
||||
"IncrementalBinaryTree": "0xcDF8efE6334c68aF283C83f2F14648da51fcfFb0",
|
||||
"Semaphore": "0x72dca3c971136bf47BACF16A141f0fcfAC925aeC"
|
||||
}
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
{
|
||||
"verifiers": {
|
||||
"Verifier16": "0xA5253ba39381Aa99c4C2C5A4D5C2deC036d06629",
|
||||
"Verifier17": "0xe0418A5f8fBF051D6cbc41Ff29855Dd2a02201Ab",
|
||||
"Verifier18": "0x7CdB3336d7d7c55Bce0FB1508594C54521656797",
|
||||
"Verifier19": "0xbd870921d8A5398a3314C950d1fc63b8C3AB190B",
|
||||
"Verifier20": "0x2a96c5696F85e3d2aa918496806B5c5a4D93E099",
|
||||
"Verifier21": "0x5Ec7d851a52A2a25CEc528F42a7ACA8EcF4667Cd",
|
||||
"Verifier22": "0x919d3d9c05FA7411e334deA5a763354fC7B6aA5b",
|
||||
"Verifier23": "0x63917b00a6dA7865bEfdd107AfC83CC2e6BDE552",
|
||||
"Verifier24": "0xd05CAd7d940114c1419098EE3cEA0776ab510E7D",
|
||||
"Verifier25": "0x6D9862e6140D94E932d94c8BcE74a0BDD0ea5ACb",
|
||||
"Verifier26": "0x8c29e0b77e32f704F03eeCE01c041192A5EB6c77",
|
||||
"Verifier27": "0x066cC22f8CA2A8D90D7Ff77D8a10A27e629c9c4C",
|
||||
"Verifier28": "0x698F9507f504E2BD238be7da56E8D9fee60C6D15",
|
||||
"Verifier29": "0xbBfC2E201C3c3c6F50063c3Edb4746c6Fcb36346",
|
||||
"Verifier30": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1",
|
||||
"Verifier31": "0x133b69Ce47BF20C49368354914DF47519Ca6cCFE",
|
||||
"Verifier32": "0xe2978F79cb4AF62e5C990EE5c7E12fb22ee22e2D"
|
||||
},
|
||||
"Semaphore": "0x5259d32659F1806ccAfcE593ED5a89eBAb85262f",
|
||||
"Semaphore": "0x89490c95eD199D980Cdb4FF8Bac9977EDb41A7E7",
|
||||
"PoseidonT3": "0xe0A452533853310C371b50Bd91BB9DCC8961350F",
|
||||
"IncrementalBinaryTree": "0x61AE89E372492e53D941DECaaC9821649fa9B236"
|
||||
"IncrementalBinaryTree": "0x9Ed9f58CA9212Ddf0377C8C4Cd089748F9337820",
|
||||
"Pairing": "0xE9c41c912CF750D79Cf304a196d4Bc8Dfd626C86",
|
||||
"SemaphoreVerifier": "0x66e772B0B8Ee1c24E4b6aC99A3A82C77f431792E",
|
||||
"Poseidon": "0xe0A452533853310C371b50Bd91BB9DCC8961350F"
|
||||
}
|
||||
|
||||
7
packages/contracts/deployed-contracts/mumbai.json
Normal file
7
packages/contracts/deployed-contracts/mumbai.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Pairing": "0x3d3df6CFc6BFf68d9693e097F32bF4a9903E77a5",
|
||||
"SemaphoreVerifier": "0x5f4edC58142f4395D1D536e793137A0252dA5a49",
|
||||
"Poseidon": "0x181B7f34538cE3BceC68597d4A212aB3f7881648",
|
||||
"IncrementalBinaryTree": "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F",
|
||||
"Semaphore": "0xF864ABa335073e01234c9a88888BfFfa965650bD"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Pairing": "0xEFD83f827FA5B0496359D817c6CD8a5AA5D2aCe5",
|
||||
"SemaphoreVerifier": "0x3d3df6CFc6BFf68d9693e097F32bF4a9903E77a5",
|
||||
"Poseidon": "0x5f4edC58142f4395D1D536e793137A0252dA5a49",
|
||||
"IncrementalBinaryTree": "0x181B7f34538cE3BceC68597d4A212aB3f7881648",
|
||||
"Semaphore": "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F"
|
||||
}
|
||||
7
packages/contracts/deployed-contracts/sepolia.json
Normal file
7
packages/contracts/deployed-contracts/sepolia.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Pairing": "0xEFD83f827FA5B0496359D817c6CD8a5AA5D2aCe5",
|
||||
"SemaphoreVerifier": "0x3d3df6CFc6BFf68d9693e097F32bF4a9903E77a5",
|
||||
"Poseidon": "0x5f4edC58142f4395D1D536e793137A0252dA5a49",
|
||||
"IncrementalBinaryTree": "0x181B7f34538cE3BceC68597d4A212aB3f7881648",
|
||||
"Semaphore": "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@nomicfoundation/hardhat-chai-matchers"
|
||||
import "@nomiclabs/hardhat-ethers"
|
||||
import "@nomiclabs/hardhat-etherscan"
|
||||
import "@nomiclabs/hardhat-waffle"
|
||||
import "@typechain/hardhat"
|
||||
import { config as dotenvConfig } from "dotenv"
|
||||
import "hardhat-gas-reporter"
|
||||
@@ -30,6 +30,26 @@ function getNetworks(): NetworksUserConfig {
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
sepolia: {
|
||||
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 11155111,
|
||||
accounts
|
||||
},
|
||||
mumbai: {
|
||||
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 80001,
|
||||
accounts
|
||||
},
|
||||
"optimism-goerli": {
|
||||
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
"arbitrum-goerli": {
|
||||
url: "https://goerli-rollup.arbitrum.io/rpc",
|
||||
chainId: 421613,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: "https://arb1.arbitrum.io/rpc",
|
||||
chainId: 42161,
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
"lint": "solhint 'contracts/**/*.sol'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^1.0.5",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.6",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.1.0",
|
||||
"@nomiclabs/hardhat-waffle": "^2.0.3",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.1.7",
|
||||
"@semaphore-protocol/group": "workspace:packages/group",
|
||||
"@semaphore-protocol/identity": "workspace:packages/identity",
|
||||
"@semaphore-protocol/proof": "workspace:packages/proof",
|
||||
@@ -32,7 +32,6 @@
|
||||
"circomlib": "^2.0.2",
|
||||
"circomlibjs": "^0.0.8",
|
||||
"download": "^8.0.0",
|
||||
"ethereum-waffle": "^3.4.4",
|
||||
"ethers": "^5.6.8",
|
||||
"hardhat": "^2.9.7",
|
||||
"hardhat-gas-reporter": "^1.0.8",
|
||||
@@ -64,6 +63,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "4.7.3",
|
||||
"@zk-kit/incremental-merkle-tree.sol": "1.3.1"
|
||||
"@zk-kit/incremental-merkle-tree.sol": "1.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
import { readFileSync, writeFileSync } from "fs"
|
||||
|
||||
type DeployedContracts = {
|
||||
Pairing?: string
|
||||
SemaphoreVerifier?: string
|
||||
Poseidon?: string
|
||||
IncrementalBinaryTree?: string
|
||||
Semaphore?: string
|
||||
Pairing: string
|
||||
SemaphoreVerifier: string
|
||||
Poseidon: string
|
||||
IncrementalBinaryTree: string
|
||||
Semaphore: string
|
||||
}
|
||||
|
||||
export function getDeployedContracts(network: string | undefined): DeployedContracts | null {
|
||||
try {
|
||||
return JSON.parse(readFileSync(`./deployed-contracts/${network}.json`, "utf8"))
|
||||
} catch (error) {
|
||||
return {}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function saveDeployedContracts(network: string | undefined, newDeployedContracts: DeployedContracts) {
|
||||
const deployedContracts = getDeployedContracts(network)
|
||||
|
||||
writeFileSync(
|
||||
`./deployed-contracts/${network}.json`,
|
||||
JSON.stringify(
|
||||
{
|
||||
...deployedContracts,
|
||||
...newDeployedContracts
|
||||
},
|
||||
null,
|
||||
4
|
||||
)
|
||||
)
|
||||
export function saveDeployedContracts(network: string | undefined, deployedContracts: DeployedContracts) {
|
||||
writeFileSync(`./deployed-contracts/${network}.json`, JSON.stringify(deployedContracts, null, 4))
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import { hardhatArguments, run } from "hardhat"
|
||||
import { getDeployedContracts } from "./utils"
|
||||
|
||||
async function verify(address: string, constructorArguments?: any[]): Promise<void> {
|
||||
try {
|
||||
await run("verify:verify", {
|
||||
address,
|
||||
constructorArguments
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const deployedContracts = getDeployedContracts(hardhatArguments.network)
|
||||
|
||||
await run("verify:verify", {
|
||||
address: deployedContracts.IncrementalBinaryTree
|
||||
})
|
||||
|
||||
await run("verify:verify", {
|
||||
address: deployedContracts.Semaphore
|
||||
})
|
||||
|
||||
await run("verify:verify", {
|
||||
address: deployedContracts.Pairing
|
||||
})
|
||||
|
||||
await run("verify:verify", {
|
||||
address: deployedContracts.SemaphoreVerifier
|
||||
})
|
||||
if (deployedContracts) {
|
||||
await verify(deployedContracts.IncrementalBinaryTree)
|
||||
await verify(deployedContracts.Pairing)
|
||||
await verify(deployedContracts.SemaphoreVerifier)
|
||||
await verify(deployedContracts.Semaphore, [deployedContracts.SemaphoreVerifier])
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { poseidon_gencontract as poseidonContract } from "circomlibjs"
|
||||
import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:semaphore-voting", "Deploy a SemaphoreVoting contract")
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs }, { ethers }): Promise<Contract> => {
|
||||
.setAction(async ({ logs }, { ethers }): Promise<any> => {
|
||||
const PairingFactory = await ethers.getContractFactory("Pairing")
|
||||
const pairing = await PairingFactory.deploy()
|
||||
|
||||
@@ -69,5 +68,11 @@ task("deploy:semaphore-voting", "Deploy a SemaphoreVoting contract")
|
||||
console.info(`SemaphoreVoting contract has been deployed to: ${semaphoreVoting.address}`)
|
||||
}
|
||||
|
||||
return semaphoreVoting
|
||||
return {
|
||||
semaphoreVoting,
|
||||
pairingAddress: pairing.address,
|
||||
semaphoreVerifierAddress: semaphoreVerifier.address,
|
||||
poseidonAddress: poseidon.address,
|
||||
incrementalBinaryTreeAddress: incrementalBinaryTree.address
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { poseidon_gencontract as poseidonContract } from "circomlibjs"
|
||||
import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:semaphore-whistleblowing", "Deploy a SemaphoreWhistleblowing contract")
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs }, { ethers }): Promise<Contract> => {
|
||||
.setAction(async ({ logs }, { ethers }): Promise<any> => {
|
||||
const PairingFactory = await ethers.getContractFactory("Pairing")
|
||||
const pairing = await PairingFactory.deploy()
|
||||
|
||||
@@ -69,5 +68,11 @@ task("deploy:semaphore-whistleblowing", "Deploy a SemaphoreWhistleblowing contra
|
||||
console.info(`SemaphoreWhistleblowing contract has been deployed to: ${semaphoreWhistleblowing.address}`)
|
||||
}
|
||||
|
||||
return semaphoreWhistleblowing
|
||||
return {
|
||||
semaphoreWhistleblowing,
|
||||
pairingAddress: pairing.address,
|
||||
semaphoreVerifierAddress: semaphoreVerifier.address,
|
||||
poseidonAddress: poseidon.address,
|
||||
incrementalBinaryTreeAddress: incrementalBinaryTree.address
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { poseidon_gencontract as poseidonContract } from "circomlibjs"
|
||||
import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
import { saveDeployedContracts } from "../scripts/utils"
|
||||
|
||||
@@ -24,7 +23,7 @@ task("deploy:semaphore", "Deploy a Semaphore contract")
|
||||
incrementalBinaryTree: incrementalBinaryTreeAddress
|
||||
},
|
||||
{ ethers, hardhatArguments }
|
||||
): Promise<Contract> => {
|
||||
): Promise<any> => {
|
||||
if (!semaphoreVerifierAddress) {
|
||||
if (!pairingAddress) {
|
||||
const PairingFactory = await ethers.getContractFactory("Pairing")
|
||||
@@ -113,6 +112,12 @@ task("deploy:semaphore", "Deploy a Semaphore contract")
|
||||
Semaphore: semaphore.address
|
||||
})
|
||||
|
||||
return semaphore
|
||||
return {
|
||||
semaphore,
|
||||
pairingAddress,
|
||||
semaphoreVerifierAddress,
|
||||
poseidonAddress,
|
||||
incrementalBinaryTreeAddress
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2,85 +2,121 @@
|
||||
/* eslint-disable jest/valid-expect */
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { FullProof, generateProof, packToSolidityProof, SolidityProof } from "@semaphore-protocol/proof"
|
||||
import { FullProof, generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import { constants, Signer } from "ethers"
|
||||
import { run } from "hardhat"
|
||||
import { Semaphore } from "../build/typechain"
|
||||
import { ethers, run } from "hardhat"
|
||||
import { Pairing, Semaphore } from "../build/typechain"
|
||||
import { createIdentityCommitments } from "./utils"
|
||||
|
||||
describe("Semaphore", () => {
|
||||
let contract: Semaphore
|
||||
let semaphoreContract: Semaphore
|
||||
let pairingContract: Pairing
|
||||
let signers: Signer[]
|
||||
let accounts: string[]
|
||||
|
||||
const treeDepth = Number(process.env.TREE_DEPTH) || 20
|
||||
const groupId = 1
|
||||
const group = new Group(groupId, treeDepth)
|
||||
const members = createIdentityCommitments(3)
|
||||
|
||||
const wasmFilePath = `../../snark-artifacts/${treeDepth}/semaphore.wasm`
|
||||
const zkeyFilePath = `../../snark-artifacts/${treeDepth}/semaphore.zkey`
|
||||
|
||||
before(async () => {
|
||||
contract = await run("deploy:semaphore", {
|
||||
const { semaphore, pairingAddress } = await run("deploy:semaphore", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
semaphoreContract = semaphore
|
||||
pairingContract = await ethers.getContractAt("Pairing", pairingAddress)
|
||||
|
||||
signers = await run("accounts", { logs: false })
|
||||
accounts = await Promise.all(signers.map((signer: Signer) => signer.getAddress()))
|
||||
})
|
||||
|
||||
describe("# createGroup", () => {
|
||||
it("Should not create a group if the tree depth is not supported", async () => {
|
||||
const transaction = contract["createGroup(uint256,uint256,uint256,address)"](groupId, 10, 0, accounts[0])
|
||||
const transaction = semaphoreContract["createGroup(uint256,uint256,address)"](groupId, 10, accounts[0])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeDepthIsNotSupported()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreContract,
|
||||
"Semaphore__MerkleTreeDepthIsNotSupported"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should create a group", async () => {
|
||||
const transaction = contract
|
||||
const transaction = semaphoreContract
|
||||
.connect(signers[1])
|
||||
["createGroup(uint256,uint256,uint256,address)"](groupId, treeDepth, 0, accounts[1])
|
||||
["createGroup(uint256,uint256,address)"](groupId, treeDepth, accounts[1])
|
||||
|
||||
await expect(transaction).to.emit(contract, "GroupCreated").withArgs(groupId, treeDepth, 0)
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "GroupAdminUpdated")
|
||||
.to.emit(semaphoreContract, "GroupCreated")
|
||||
.withArgs(groupId, treeDepth, group.zeroValue)
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "GroupAdminUpdated")
|
||||
.withArgs(groupId, constants.AddressZero, accounts[1])
|
||||
})
|
||||
|
||||
it("Should create a group with a custom Merkle tree root expiration", async () => {
|
||||
const groupId = 2
|
||||
const transaction = await contract
|
||||
const group = new Group(2)
|
||||
const transaction = await semaphoreContract
|
||||
.connect(signers[1])
|
||||
["createGroup(uint256,uint256,uint256,address,uint256)"](
|
||||
["createGroup(uint256,uint256,address,uint256)"](
|
||||
groupId,
|
||||
treeDepth,
|
||||
0,
|
||||
accounts[0],
|
||||
5 // 5 seconds.
|
||||
)
|
||||
await contract.addMember(groupId, members[0])
|
||||
await contract.addMember(groupId, members[1])
|
||||
await contract.addMember(groupId, members[2])
|
||||
await semaphoreContract.addMember(groupId, members[0])
|
||||
await semaphoreContract.addMember(groupId, members[1])
|
||||
await semaphoreContract.addMember(groupId, members[2])
|
||||
|
||||
await expect(transaction).to.emit(contract, "GroupCreated").withArgs(groupId, treeDepth, 0)
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "GroupAdminUpdated")
|
||||
.to.emit(semaphoreContract, "GroupCreated")
|
||||
.withArgs(groupId, treeDepth, group.zeroValue)
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "GroupAdminUpdated")
|
||||
.withArgs(groupId, constants.AddressZero, accounts[0])
|
||||
})
|
||||
})
|
||||
|
||||
describe("# updateGroupMerkleTreeDuration", () => {
|
||||
it("Should not update a group Merkle tree duration if the caller is not the group admin", async () => {
|
||||
const transaction = semaphoreContract.updateGroupMerkleTreeDuration(groupId, 300)
|
||||
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreContract,
|
||||
"Semaphore__CallerIsNotTheGroupAdmin"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should update the group Merkle tree duration", async () => {
|
||||
const transaction = semaphoreContract.connect(signers[1]).updateGroupMerkleTreeDuration(groupId, 300)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "GroupMerkleTreeDurationUpdated")
|
||||
.withArgs(groupId, 3600, 300)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# updateGroupAdmin", () => {
|
||||
it("Should not update a group admin if the caller is not the group admin", async () => {
|
||||
const transaction = contract.updateGroupAdmin(groupId, accounts[0])
|
||||
const transaction = semaphoreContract.updateGroupAdmin(groupId, accounts[0])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheGroupAdmin()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreContract,
|
||||
"Semaphore__CallerIsNotTheGroupAdmin"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should update the group admin", async () => {
|
||||
const transaction = contract.connect(signers[1]).updateGroupAdmin(groupId, accounts[0])
|
||||
const transaction = semaphoreContract.connect(signers[1]).updateGroupAdmin(groupId, accounts[0])
|
||||
|
||||
await expect(transaction).to.emit(contract, "GroupAdminUpdated").withArgs(groupId, accounts[1], accounts[0])
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "GroupAdminUpdated")
|
||||
.withArgs(groupId, accounts[1], accounts[0])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -88,19 +124,24 @@ describe("Semaphore", () => {
|
||||
it("Should not add a member if the caller is not the group admin", async () => {
|
||||
const member = BigInt(2)
|
||||
|
||||
const transaction = contract.connect(signers[1]).addMember(groupId, member)
|
||||
const transaction = semaphoreContract.connect(signers[1]).addMember(groupId, member)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheGroupAdmin()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreContract,
|
||||
"Semaphore__CallerIsNotTheGroupAdmin"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should add a new member in an existing group", async () => {
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(groupId, treeDepth)
|
||||
|
||||
group.addMember(members[0])
|
||||
|
||||
const transaction = contract.addMember(groupId, members[0])
|
||||
const transaction = semaphoreContract.addMember(groupId, members[0])
|
||||
|
||||
await expect(transaction).to.emit(contract, "MemberAdded").withArgs(groupId, 0, members[0], group.root)
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "MemberAdded")
|
||||
.withArgs(groupId, 0, members[0], group.root)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -108,15 +149,17 @@ describe("Semaphore", () => {
|
||||
it("Should add new members to an existing group", async () => {
|
||||
const groupId = 3
|
||||
const members = [BigInt(1), BigInt(2), BigInt(3)]
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(groupId, treeDepth)
|
||||
|
||||
group.addMembers(members)
|
||||
|
||||
await contract["createGroup(uint256,uint256,uint256,address)"](groupId, treeDepth, 0, accounts[0])
|
||||
await semaphoreContract["createGroup(uint256,uint256,address)"](groupId, treeDepth, accounts[0])
|
||||
|
||||
const transaction = contract.addMembers(groupId, members)
|
||||
const transaction = semaphoreContract.addMembers(groupId, members)
|
||||
|
||||
await expect(transaction).to.emit(contract, "MemberAdded").withArgs(groupId, 2, BigInt(3), group.root)
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "MemberAdded")
|
||||
.withArgs(groupId, 2, BigInt(3), group.root)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -124,29 +167,32 @@ describe("Semaphore", () => {
|
||||
it("Should not update a member if the caller is not the group admin", async () => {
|
||||
const member = BigInt(2)
|
||||
|
||||
const transaction = contract.connect(signers[1]).updateMember(groupId, member, 1, [0, 1], [0, 1])
|
||||
const transaction = semaphoreContract.connect(signers[1]).updateMember(groupId, member, 1, [0, 1], [0, 1])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheGroupAdmin()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreContract,
|
||||
"Semaphore__CallerIsNotTheGroupAdmin"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should update a member from an existing group", async () => {
|
||||
const groupId = 4
|
||||
const members = [BigInt(1), BigInt(2), BigInt(3)]
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(groupId, treeDepth)
|
||||
|
||||
group.addMembers(members)
|
||||
|
||||
group.updateMember(0, BigInt(4))
|
||||
|
||||
await contract["createGroup(uint256,uint256,uint256,address)"](groupId, treeDepth, 0, accounts[0])
|
||||
await contract.addMembers(groupId, members)
|
||||
await semaphoreContract["createGroup(uint256,uint256,address)"](groupId, treeDepth, accounts[0])
|
||||
await semaphoreContract.addMembers(groupId, members)
|
||||
|
||||
const { siblings, pathIndices, root } = group.generateMerkleProof(0)
|
||||
|
||||
const transaction = contract.updateMember(groupId, BigInt(1), BigInt(4), siblings, pathIndices)
|
||||
const transaction = semaphoreContract.updateMember(groupId, BigInt(1), BigInt(4), siblings, pathIndices)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "MemberUpdated")
|
||||
.to.emit(semaphoreContract, "MemberUpdated")
|
||||
.withArgs(groupId, 0, BigInt(1), BigInt(4), root)
|
||||
})
|
||||
})
|
||||
@@ -155,28 +201,31 @@ describe("Semaphore", () => {
|
||||
it("Should not remove a member if the caller is not the group admin", async () => {
|
||||
const member = BigInt(2)
|
||||
|
||||
const transaction = contract.connect(signers[1]).removeMember(groupId, member, [0, 1], [0, 1])
|
||||
const transaction = semaphoreContract.connect(signers[1]).removeMember(groupId, member, [0, 1], [0, 1])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheGroupAdmin()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreContract,
|
||||
"Semaphore__CallerIsNotTheGroupAdmin"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should remove a member from an existing group", async () => {
|
||||
const groupId = 5
|
||||
const members = [BigInt(1), BigInt(2), BigInt(3)]
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(groupId, treeDepth)
|
||||
|
||||
group.addMembers(members)
|
||||
|
||||
group.removeMember(2)
|
||||
|
||||
await contract["createGroup(uint256,uint256,uint256,address)"](groupId, treeDepth, 0, accounts[0])
|
||||
await contract.addMembers(groupId, members)
|
||||
await semaphoreContract["createGroup(uint256,uint256,address)"](groupId, treeDepth, accounts[0])
|
||||
await semaphoreContract.addMembers(groupId, members)
|
||||
|
||||
const { siblings, pathIndices, root } = group.generateMerkleProof(2)
|
||||
|
||||
const transaction = contract.removeMember(groupId, BigInt(3), siblings, pathIndices)
|
||||
const transaction = semaphoreContract.removeMember(groupId, BigInt(3), siblings, pathIndices)
|
||||
|
||||
await expect(transaction).to.emit(contract, "MemberRemoved").withArgs(groupId, 2, BigInt(3), root)
|
||||
await expect(transaction).to.emit(semaphoreContract, "MemberRemoved").withArgs(groupId, 2, BigInt(3), root)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -184,85 +233,89 @@ describe("Semaphore", () => {
|
||||
const signal = 2
|
||||
const identity = new Identity("0")
|
||||
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(groupId, treeDepth)
|
||||
|
||||
group.addMembers(members)
|
||||
|
||||
let fullProof: FullProof
|
||||
let solidityProof: SolidityProof
|
||||
|
||||
before(async () => {
|
||||
await contract.addMembers(groupId, [members[1], members[2]])
|
||||
await semaphoreContract.addMembers(groupId, [members[1], members[2]])
|
||||
|
||||
fullProof = await generateProof(identity, group, group.root, signal, {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
solidityProof = packToSolidityProof(fullProof.proof)
|
||||
})
|
||||
|
||||
it("Should not verify a proof if the group does not exist", async () => {
|
||||
const transaction = contract.verifyProof(10, 1, signal, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0])
|
||||
const transaction = semaphoreContract.verifyProof(10, 1, signal, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__GroupDoesNotExist()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(semaphoreContract, "Semaphore__GroupDoesNotExist")
|
||||
})
|
||||
|
||||
it("Should not verify a proof if the Merkle tree root is not part of the group", async () => {
|
||||
const transaction = contract.verifyProof(2, 1, signal, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0])
|
||||
const transaction = semaphoreContract.verifyProof(2, 1, signal, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeRootIsNotPartOfTheGroup()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreContract,
|
||||
"Semaphore__MerkleTreeRootIsNotPartOfTheGroup"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should throw an exception if the proof is not valid", async () => {
|
||||
const transaction = contract.verifyProof(
|
||||
const transaction = semaphoreContract.verifyProof(
|
||||
groupId,
|
||||
group.root,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.signal,
|
||||
fullProof.nullifierHash,
|
||||
0,
|
||||
solidityProof
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__InvalidProof()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(pairingContract, "InvalidProof")
|
||||
})
|
||||
|
||||
it("Should verify a proof for an onchain group correctly", async () => {
|
||||
const transaction = contract.verifyProof(
|
||||
const transaction = semaphoreContract.verifyProof(
|
||||
groupId,
|
||||
group.root,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.merkleTreeRoot,
|
||||
solidityProof
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.signal,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "ProofVerified")
|
||||
.to.emit(semaphoreContract, "ProofVerified")
|
||||
.withArgs(
|
||||
groupId,
|
||||
group.root,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.merkleTreeRoot,
|
||||
signal
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.signal
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not verify the same proof for an onchain group twice", async () => {
|
||||
const transaction = contract.verifyProof(
|
||||
const transaction = semaphoreContract.verifyProof(
|
||||
groupId,
|
||||
group.root,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.merkleTreeRoot,
|
||||
solidityProof
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.signal,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__YouAreUsingTheSameNillifierTwice()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreContract,
|
||||
"Semaphore__YouAreUsingTheSameNillifierTwice"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not verify a proof if the Merkle tree root is expired", async () => {
|
||||
const groupId = 2
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(groupId, treeDepth)
|
||||
|
||||
group.addMembers([members[0], members[1]])
|
||||
|
||||
@@ -270,18 +323,20 @@ describe("Semaphore", () => {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
const solidityProof = packToSolidityProof(fullProof.proof)
|
||||
|
||||
const transaction = contract.verifyProof(
|
||||
const transaction = semaphoreContract.verifyProof(
|
||||
groupId,
|
||||
group.root,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
0,
|
||||
solidityProof
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.signal,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeRootIsExpired()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreContract,
|
||||
"Semaphore__MerkleTreeRootIsExpired"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/* eslint-disable jest/valid-expect */
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof, packToSolidityProof, PublicSignals, SolidityProof } from "@semaphore-protocol/proof"
|
||||
import { FullProof, generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import { Signer } from "ethers"
|
||||
import { ethers, run } from "hardhat"
|
||||
import { SemaphoreVoting } from "../build/typechain"
|
||||
import { Pairing, SemaphoreVoting } from "../build/typechain"
|
||||
|
||||
describe("SemaphoreVoting", () => {
|
||||
let contract: SemaphoreVoting
|
||||
let semaphoreVotingContract: SemaphoreVoting
|
||||
let pairingContract: Pairing
|
||||
let accounts: Signer[]
|
||||
let coordinator: string
|
||||
|
||||
@@ -21,98 +22,113 @@ describe("SemaphoreVoting", () => {
|
||||
const zkeyFilePath = `../../snark-artifacts/${treeDepth}/semaphore.zkey`
|
||||
|
||||
before(async () => {
|
||||
contract = await run("deploy:semaphore-voting", {
|
||||
const { semaphoreVoting, pairingAddress } = await run("deploy:semaphore-voting", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
semaphoreVotingContract = semaphoreVoting
|
||||
pairingContract = await ethers.getContractAt("Pairing", pairingAddress)
|
||||
|
||||
accounts = await ethers.getSigners()
|
||||
coordinator = await accounts[1].getAddress()
|
||||
})
|
||||
|
||||
describe("# createPoll", () => {
|
||||
it("Should not create a poll with a wrong depth", async () => {
|
||||
const transaction = contract.createPoll(pollIds[0], coordinator, 10)
|
||||
const transaction = semaphoreVotingContract.createPoll(pollIds[0], coordinator, 10)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeDepthIsNotSupported()")
|
||||
})
|
||||
|
||||
it("Should not create a poll greater than the snark scalar field", async () => {
|
||||
const transaction = contract.createPoll(
|
||||
BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495618"),
|
||||
coordinator,
|
||||
treeDepth
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreVotingContract,
|
||||
"Semaphore__MerkleTreeDepthIsNotSupported"
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__GroupIdIsNotLessThanSnarkScalarField()")
|
||||
})
|
||||
|
||||
it("Should create a poll", async () => {
|
||||
const transaction = contract.createPoll(pollIds[0], coordinator, treeDepth)
|
||||
const transaction = semaphoreVotingContract.createPoll(pollIds[0], coordinator, treeDepth)
|
||||
|
||||
await expect(transaction).to.emit(contract, "PollCreated").withArgs(pollIds[0], coordinator)
|
||||
await expect(transaction).to.emit(semaphoreVotingContract, "PollCreated").withArgs(pollIds[0], coordinator)
|
||||
})
|
||||
|
||||
it("Should not create a poll if it already exists", async () => {
|
||||
const transaction = contract.createPoll(pollIds[0], coordinator, treeDepth)
|
||||
const transaction = semaphoreVotingContract.createPoll(pollIds[0], coordinator, treeDepth)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__GroupAlreadyExists()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreVotingContract,
|
||||
"Semaphore__GroupAlreadyExists"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# startPoll", () => {
|
||||
it("Should not start the poll if the caller is not the coordinator", async () => {
|
||||
const transaction = contract.startPoll(pollIds[0], encryptionKey)
|
||||
const transaction = semaphoreVotingContract.startPoll(pollIds[0], encryptionKey)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotThePollCoordinator()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreVotingContract,
|
||||
"Semaphore__CallerIsNotThePollCoordinator"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should start the poll", async () => {
|
||||
const transaction = contract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey)
|
||||
const transaction = semaphoreVotingContract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey)
|
||||
|
||||
await expect(transaction).to.emit(contract, "PollStarted").withArgs(pollIds[0], coordinator, encryptionKey)
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreVotingContract, "PollStarted")
|
||||
.withArgs(pollIds[0], coordinator, encryptionKey)
|
||||
})
|
||||
|
||||
it("Should not start a poll if it has already been started", async () => {
|
||||
const transaction = contract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey)
|
||||
const transaction = semaphoreVotingContract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__PollHasAlreadyBeenStarted()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreVotingContract,
|
||||
"Semaphore__PollHasAlreadyBeenStarted"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# addVoter", () => {
|
||||
before(async () => {
|
||||
await contract.createPoll(pollIds[1], coordinator, treeDepth)
|
||||
await semaphoreVotingContract.createPoll(pollIds[1], coordinator, treeDepth)
|
||||
})
|
||||
|
||||
it("Should not add a voter if the caller is not the coordinator", async () => {
|
||||
const { commitment } = new Identity()
|
||||
|
||||
const transaction = contract.addVoter(pollIds[0], commitment)
|
||||
const transaction = semaphoreVotingContract.addVoter(pollIds[0], commitment)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotThePollCoordinator()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreVotingContract,
|
||||
"Semaphore__CallerIsNotThePollCoordinator"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not add a voter if the poll has already been started", async () => {
|
||||
const { commitment } = new Identity()
|
||||
|
||||
const transaction = contract.connect(accounts[1]).addVoter(pollIds[0], commitment)
|
||||
const transaction = semaphoreVotingContract.connect(accounts[1]).addVoter(pollIds[0], commitment)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__PollHasAlreadyBeenStarted()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreVotingContract,
|
||||
"Semaphore__PollHasAlreadyBeenStarted"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should add a voter to an existing poll", async () => {
|
||||
const { commitment } = new Identity("test")
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(pollIds[1], treeDepth)
|
||||
|
||||
group.addMember(commitment)
|
||||
|
||||
const transaction = contract.connect(accounts[1]).addVoter(pollIds[1], commitment)
|
||||
const transaction = semaphoreVotingContract.connect(accounts[1]).addVoter(pollIds[1], commitment)
|
||||
|
||||
await expect(transaction).to.emit(contract, "MemberAdded").withArgs(pollIds[1], 0, commitment, group.root)
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreVotingContract, "MemberAdded")
|
||||
.withArgs(pollIds[1], 0, commitment, group.root)
|
||||
})
|
||||
|
||||
it("Should return the correct number of poll voters", async () => {
|
||||
const size = await contract.getNumberOfMerkleTreeLeaves(pollIds[1])
|
||||
const size = await semaphoreVotingContract.getNumberOfMerkleTreeLeaves(pollIds[1])
|
||||
|
||||
expect(size).to.be.eq(1)
|
||||
})
|
||||
@@ -122,75 +138,87 @@ describe("SemaphoreVoting", () => {
|
||||
const identity = new Identity("test")
|
||||
const vote = 1
|
||||
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(pollIds[1], treeDepth)
|
||||
|
||||
group.addMembers([identity.commitment, BigInt(1)])
|
||||
|
||||
let solidityProof: SolidityProof
|
||||
let publicSignals: PublicSignals
|
||||
let fullProof: FullProof
|
||||
|
||||
before(async () => {
|
||||
await contract.connect(accounts[1]).addVoter(pollIds[1], BigInt(1))
|
||||
await contract.connect(accounts[1]).startPoll(pollIds[1], encryptionKey)
|
||||
await contract.createPoll(pollIds[2], coordinator, treeDepth)
|
||||
await semaphoreVotingContract.connect(accounts[1]).addVoter(pollIds[1], BigInt(1))
|
||||
await semaphoreVotingContract.connect(accounts[1]).startPoll(pollIds[1], encryptionKey)
|
||||
await semaphoreVotingContract.createPoll(pollIds[2], coordinator, treeDepth)
|
||||
|
||||
const fullProof = await generateProof(identity, group, pollIds[1], vote, {
|
||||
fullProof = await generateProof(identity, group, pollIds[1], vote, {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
|
||||
publicSignals = fullProof.publicSignals
|
||||
solidityProof = packToSolidityProof(fullProof.proof)
|
||||
})
|
||||
|
||||
it("Should not cast a vote if the poll is not ongoing", async () => {
|
||||
const transaction = contract
|
||||
const transaction = semaphoreVotingContract
|
||||
.connect(accounts[1])
|
||||
.castVote(vote, publicSignals.nullifierHash, pollIds[2], solidityProof)
|
||||
.castVote(vote, fullProof.nullifierHash, pollIds[2], fullProof.proof)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__PollIsNotOngoing()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreVotingContract,
|
||||
"Semaphore__PollIsNotOngoing"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not cast a vote if the proof is not valid", async () => {
|
||||
const transaction = contract.connect(accounts[1]).castVote(vote, 0, pollIds[1], solidityProof)
|
||||
const transaction = semaphoreVotingContract
|
||||
.connect(accounts[1])
|
||||
.castVote(vote, 0, pollIds[1], fullProof.proof)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__InvalidProof()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(pairingContract, "InvalidProof")
|
||||
})
|
||||
|
||||
it("Should cast a vote", async () => {
|
||||
const transaction = contract
|
||||
const transaction = semaphoreVotingContract
|
||||
.connect(accounts[1])
|
||||
.castVote(vote, publicSignals.nullifierHash, pollIds[1], solidityProof)
|
||||
.castVote(vote, fullProof.nullifierHash, pollIds[1], fullProof.proof)
|
||||
|
||||
await expect(transaction).to.emit(contract, "VoteAdded").withArgs(pollIds[1], vote)
|
||||
await expect(transaction).to.emit(semaphoreVotingContract, "VoteAdded").withArgs(pollIds[1], vote)
|
||||
})
|
||||
|
||||
it("Should not cast a vote twice", async () => {
|
||||
const transaction = contract
|
||||
const transaction = semaphoreVotingContract
|
||||
.connect(accounts[1])
|
||||
.castVote(vote, publicSignals.nullifierHash, pollIds[1], solidityProof)
|
||||
.castVote(vote, fullProof.nullifierHash, pollIds[1], fullProof.proof)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__YouAreUsingTheSameNillifierTwice()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreVotingContract,
|
||||
"Semaphore__YouAreUsingTheSameNillifierTwice"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# endPoll", () => {
|
||||
it("Should not end the poll if the caller is not the coordinator", async () => {
|
||||
const transaction = contract.endPoll(pollIds[1], decryptionKey)
|
||||
const transaction = semaphoreVotingContract.endPoll(pollIds[1], decryptionKey)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotThePollCoordinator()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreVotingContract,
|
||||
"Semaphore__CallerIsNotThePollCoordinator"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should end the poll", async () => {
|
||||
const transaction = contract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey)
|
||||
const transaction = semaphoreVotingContract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey)
|
||||
|
||||
await expect(transaction).to.emit(contract, "PollEnded").withArgs(pollIds[1], coordinator, decryptionKey)
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreVotingContract, "PollEnded")
|
||||
.withArgs(pollIds[1], coordinator, decryptionKey)
|
||||
})
|
||||
|
||||
it("Should not end a poll if it has already been ended", async () => {
|
||||
const transaction = contract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey)
|
||||
const transaction = semaphoreVotingContract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__PollIsNotOngoing()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreVotingContract,
|
||||
"Semaphore__PollIsNotOngoing"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/* eslint-disable jest/valid-expect */
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof, packToSolidityProof, PublicSignals, SolidityProof } from "@semaphore-protocol/proof"
|
||||
import { FullProof, generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import { Signer, utils } from "ethers"
|
||||
import { ethers, run } from "hardhat"
|
||||
import { SemaphoreWhistleblowing } from "../build/typechain"
|
||||
import { Pairing, SemaphoreWhistleblowing } from "../build/typechain"
|
||||
|
||||
describe("SemaphoreWhistleblowing", () => {
|
||||
let contract: SemaphoreWhistleblowing
|
||||
let semaphoreWhistleblowingContract: SemaphoreWhistleblowing
|
||||
let pairingContract: Pairing
|
||||
let accounts: Signer[]
|
||||
let editor: string
|
||||
|
||||
@@ -19,41 +20,42 @@ describe("SemaphoreWhistleblowing", () => {
|
||||
const zkeyFilePath = `../../snark-artifacts/${treeDepth}/semaphore.zkey`
|
||||
|
||||
before(async () => {
|
||||
contract = await run("deploy:semaphore-whistleblowing", {
|
||||
const { semaphoreWhistleblowing, pairingAddress } = await run("deploy:semaphore-whistleblowing", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
semaphoreWhistleblowingContract = semaphoreWhistleblowing
|
||||
pairingContract = await ethers.getContractAt("Pairing", pairingAddress)
|
||||
|
||||
accounts = await ethers.getSigners()
|
||||
editor = await accounts[1].getAddress()
|
||||
})
|
||||
|
||||
describe("# createEntity", () => {
|
||||
it("Should not create an entity with a wrong depth", async () => {
|
||||
const transaction = contract.createEntity(entityIds[0], editor, 10)
|
||||
const transaction = semaphoreWhistleblowingContract.createEntity(entityIds[0], editor, 10)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeDepthIsNotSupported()")
|
||||
})
|
||||
|
||||
it("Should not create an entity greater than the snark scalar field", async () => {
|
||||
const transaction = contract.createEntity(
|
||||
BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495618"),
|
||||
editor,
|
||||
treeDepth
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreWhistleblowingContract,
|
||||
"Semaphore__MerkleTreeDepthIsNotSupported"
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__GroupIdIsNotLessThanSnarkScalarField()")
|
||||
})
|
||||
|
||||
it("Should create an entity", async () => {
|
||||
const transaction = contract.createEntity(entityIds[0], editor, treeDepth)
|
||||
const transaction = semaphoreWhistleblowingContract.createEntity(entityIds[0], editor, treeDepth)
|
||||
|
||||
await expect(transaction).to.emit(contract, "EntityCreated").withArgs(entityIds[0], editor)
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreWhistleblowingContract, "EntityCreated")
|
||||
.withArgs(entityIds[0], editor)
|
||||
})
|
||||
|
||||
it("Should not create a entity if it already exists", async () => {
|
||||
const transaction = contract.createEntity(entityIds[0], editor, treeDepth)
|
||||
const transaction = semaphoreWhistleblowingContract.createEntity(entityIds[0], editor, treeDepth)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__GroupAlreadyExists()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreWhistleblowingContract,
|
||||
"Semaphore__GroupAlreadyExists"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -61,24 +63,31 @@ describe("SemaphoreWhistleblowing", () => {
|
||||
it("Should not add a whistleblower if the caller is not the editor", async () => {
|
||||
const { commitment } = new Identity()
|
||||
|
||||
const transaction = contract.addWhistleblower(entityIds[0], commitment)
|
||||
const transaction = semaphoreWhistleblowingContract.addWhistleblower(entityIds[0], commitment)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheEditor()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreWhistleblowingContract,
|
||||
"Semaphore__CallerIsNotTheEditor"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should add a whistleblower to an existing entity", async () => {
|
||||
const { commitment } = new Identity("test")
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(entityIds[0], treeDepth)
|
||||
|
||||
group.addMember(commitment)
|
||||
|
||||
const transaction = contract.connect(accounts[1]).addWhistleblower(entityIds[0], commitment)
|
||||
const transaction = semaphoreWhistleblowingContract
|
||||
.connect(accounts[1])
|
||||
.addWhistleblower(entityIds[0], commitment)
|
||||
|
||||
await expect(transaction).to.emit(contract, "MemberAdded").withArgs(entityIds[0], 0, commitment, group.root)
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreWhistleblowingContract, "MemberAdded")
|
||||
.withArgs(entityIds[0], 0, commitment, group.root)
|
||||
})
|
||||
|
||||
it("Should return the correct number of whistleblowers of an entity", async () => {
|
||||
const size = await contract.getNumberOfMerkleTreeLeaves(entityIds[0])
|
||||
const size = await semaphoreWhistleblowingContract.getNumberOfMerkleTreeLeaves(entityIds[0])
|
||||
|
||||
expect(size).to.be.eq(1)
|
||||
})
|
||||
@@ -87,20 +96,28 @@ describe("SemaphoreWhistleblowing", () => {
|
||||
describe("# removeWhistleblower", () => {
|
||||
it("Should not remove a whistleblower if the caller is not the editor", async () => {
|
||||
const { commitment } = new Identity()
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(entityIds[0], treeDepth)
|
||||
|
||||
group.addMember(commitment)
|
||||
|
||||
const { siblings, pathIndices } = group.generateMerkleProof(0)
|
||||
|
||||
const transaction = contract.removeWhistleblower(entityIds[0], commitment, siblings, pathIndices)
|
||||
const transaction = semaphoreWhistleblowingContract.removeWhistleblower(
|
||||
entityIds[0],
|
||||
commitment,
|
||||
siblings,
|
||||
pathIndices
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheEditor()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(
|
||||
semaphoreWhistleblowingContract,
|
||||
"Semaphore__CallerIsNotTheEditor"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should remove a whistleblower from an existing entity", async () => {
|
||||
const { commitment } = new Identity("test")
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(entityIds[0], treeDepth)
|
||||
|
||||
group.addMember(commitment)
|
||||
|
||||
@@ -108,12 +125,12 @@ describe("SemaphoreWhistleblowing", () => {
|
||||
|
||||
group.removeMember(0)
|
||||
|
||||
const transaction = contract
|
||||
const transaction = semaphoreWhistleblowingContract
|
||||
.connect(accounts[1])
|
||||
.removeWhistleblower(entityIds[0], commitment, siblings, pathIndices)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "MemberRemoved")
|
||||
.to.emit(semaphoreWhistleblowingContract, "MemberRemoved")
|
||||
.withArgs(entityIds[0], 0, commitment, group.root)
|
||||
})
|
||||
})
|
||||
@@ -122,39 +139,41 @@ describe("SemaphoreWhistleblowing", () => {
|
||||
const identity = new Identity("test")
|
||||
const leak = utils.formatBytes32String("This is a leak")
|
||||
|
||||
const group = new Group(treeDepth)
|
||||
const group = new Group(entityIds[1], treeDepth)
|
||||
|
||||
group.addMembers([identity.commitment, BigInt(1)])
|
||||
|
||||
let solidityProof: SolidityProof
|
||||
let publicSignals: PublicSignals
|
||||
let fullProof: FullProof
|
||||
|
||||
before(async () => {
|
||||
await contract.createEntity(entityIds[1], editor, treeDepth)
|
||||
await contract.connect(accounts[1]).addWhistleblower(entityIds[1], identity.commitment)
|
||||
await contract.connect(accounts[1]).addWhistleblower(entityIds[1], BigInt(1))
|
||||
await semaphoreWhistleblowingContract.createEntity(entityIds[1], editor, treeDepth)
|
||||
await semaphoreWhistleblowingContract
|
||||
.connect(accounts[1])
|
||||
.addWhistleblower(entityIds[1], identity.commitment)
|
||||
await semaphoreWhistleblowingContract.connect(accounts[1]).addWhistleblower(entityIds[1], BigInt(1))
|
||||
|
||||
const fullProof = await generateProof(identity, group, entityIds[1], leak, {
|
||||
fullProof = await generateProof(identity, group, entityIds[1], leak, {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
|
||||
publicSignals = fullProof.publicSignals
|
||||
solidityProof = packToSolidityProof(fullProof.proof)
|
||||
})
|
||||
|
||||
it("Should not publish a leak if the proof is not valid", async () => {
|
||||
const transaction = contract.connect(accounts[1]).publishLeak(leak, 0, entityIds[1], solidityProof)
|
||||
const transaction = semaphoreWhistleblowingContract
|
||||
.connect(accounts[1])
|
||||
.publishLeak(leak, 0, entityIds[1], fullProof.proof)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__InvalidProof()")
|
||||
await expect(transaction).to.be.revertedWithCustomError(pairingContract, "InvalidProof")
|
||||
})
|
||||
|
||||
it("Should publish a leak", async () => {
|
||||
const transaction = contract
|
||||
const transaction = semaphoreWhistleblowingContract
|
||||
.connect(accounts[1])
|
||||
.publishLeak(leak, publicSignals.nullifierHash, entityIds[1], solidityProof)
|
||||
.publishLeak(leak, fullProof.nullifierHash, entityIds[1], fullProof.proof)
|
||||
|
||||
await expect(transaction).to.emit(contract, "LeakPublished").withArgs(entityIds[1], leak)
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreWhistleblowingContract, "LeakPublished")
|
||||
.withArgs(entityIds[1], leak)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
21
packages/data/LICENSE
Normal file
21
packages/data/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Ethereum Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
159
packages/data/README.md
Normal file
159
packages/data/README.md
Normal file
@@ -0,0 +1,159 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Semaphore data
|
||||
</h1>
|
||||
<p align="center">A library to query Semaphore contracts.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/semaphore-protocol">
|
||||
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@semaphore-protocol/data">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/data?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/data">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/data.svg?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://eslint.org/">
|
||||
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
|
||||
</a>
|
||||
<a href="https://prettier.io/">
|
||||
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h4>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
|
||||
👥 Contributing
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
|
||||
🤝 Code of conduct
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| This library allows you to query the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/contracts/Semaphore.sol) contract data (i.e. groups) using the [Semaphore subgraph](https://github.com/semaphore-protocol/subgraph) or Ethers. It can be used on Node.js and browsers. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@semaphore-protocol/data` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @semaphore-protocol/data
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @semaphore-protocol/data
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
\# **new SemaphoreSubgraph**(networkOrSubgraphURL: _Network_ | _string_ = "goerli"): _SemaphoreSubgraph_
|
||||
|
||||
```typescript
|
||||
import { SemaphoreSubgraph } from "@semaphore-protocol/data"
|
||||
|
||||
const semaphoreSubgraph = new SemaphoreSubgraph()
|
||||
|
||||
// or:
|
||||
const semaphoreSubgraph = new SemaphoreSubgraph("arbitrum")
|
||||
|
||||
// or:
|
||||
const semaphoreSubgraph = new SemaphoreSubgraph(
|
||||
"https://api.studio.thegraph.com/query/14377/<your-subgraph>/<your-version>"
|
||||
)
|
||||
```
|
||||
|
||||
\# **getGroupIds**(): _Promise\<string[]>_
|
||||
|
||||
```typescript
|
||||
const groupIds = await semaphoreSubgraph.getGroupIds()
|
||||
```
|
||||
|
||||
\# **getGroups**(options?: _GroupOptions_): _Promise\<GroupResponse[]>_
|
||||
|
||||
```typescript
|
||||
const groups = await semaphoreSubgraph.getGroups()
|
||||
|
||||
// or
|
||||
|
||||
const groups = await semaphoreSubgraph.getGroups({ members: true, verifiedProofs: true })
|
||||
```
|
||||
|
||||
\# **getGroup**(groupId: _string_, options?: _GroupOptions_): _Promise\<GroupResponse>_
|
||||
|
||||
```typescript
|
||||
const group = await semaphoreSubgraph.getGroup("42")
|
||||
|
||||
// or
|
||||
|
||||
const { members, verifiedProofs } = semaphoreSubgraph.getGroup("42", { members: true, verifiedProofs: true })
|
||||
```
|
||||
|
||||
\# **new Ethers**(networkOrEthereumURL: Network | string = "goerli", options: EthersOptions = {}): _SemaphoreEthers_
|
||||
|
||||
```typescript
|
||||
import { SemaphoreEthers } from "@semaphore-protocol/data"
|
||||
|
||||
const semaphoreEthers = new SemaphoreEthers()
|
||||
|
||||
// or:
|
||||
const semaphoreEthers = new SemaphoreEthers("homestead", {
|
||||
address: "semaphore-address",
|
||||
startBlock: 0
|
||||
})
|
||||
|
||||
// or:
|
||||
const semaphoreEthers = new SemaphoreEthers("http://localhost:8545", {
|
||||
address: "semaphore-address"
|
||||
})
|
||||
```
|
||||
|
||||
\# **getGroupIds**(): _Promise\<string[]>_
|
||||
|
||||
```typescript
|
||||
const groupIds = await semaphoreEthers.getGroupIds()
|
||||
```
|
||||
|
||||
\# **getGroup**(groupId: _string_): _Promise\<GroupResponse>_
|
||||
|
||||
```typescript
|
||||
const group = await semaphoreEthers.getGroup("42")
|
||||
```
|
||||
|
||||
\# **getGroupAdmin**(groupId: _string_): _Promise\<string>_
|
||||
|
||||
```typescript
|
||||
const admin = await semaphoreEthers.getGroupAdmin("42")
|
||||
```
|
||||
|
||||
\# **getGroupMembers**(groupId: _string_): _Promise\<string[]>_
|
||||
|
||||
```typescript
|
||||
const members = await semaphoreEthers.getGroupMembers("42")
|
||||
```
|
||||
|
||||
\# **getGroupVerifiedProofs**(groupId: _string_): _Promise\<any[]>_
|
||||
|
||||
```typescript
|
||||
const verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs()
|
||||
```
|
||||
@@ -1,17 +1,14 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/subgraph",
|
||||
"version": "2.6.1",
|
||||
"name": "@semaphore-protocol/data",
|
||||
"version": "3.3.0",
|
||||
"description": "A library to query Semaphore contracts.",
|
||||
"license": "MIT",
|
||||
"iife": "dist/index.js",
|
||||
"unpkg": "dist/index.min.js",
|
||||
"jsdelivr": "dist/index.min.js",
|
||||
"main": "dist/index.node.js",
|
||||
"exports": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.node.js"
|
||||
},
|
||||
"types": "dist/types/index.d.ts",
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist/",
|
||||
"src/",
|
||||
@@ -19,7 +16,7 @@
|
||||
"README.md"
|
||||
],
|
||||
"repository": "https://github.com/semaphore-protocol/semaphore",
|
||||
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/subgraph",
|
||||
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/data",
|
||||
"bugs": {
|
||||
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
|
||||
},
|
||||
@@ -27,18 +24,20 @@
|
||||
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
|
||||
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
|
||||
"prepublishOnly": "yarn build",
|
||||
"docs": "typedoc src/index.ts --out ../../docs/subgraph"
|
||||
"docs": "typedoc src/index.ts --out ../../docs/data"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersproject/contracts": "^5.7.0",
|
||||
"@ethersproject/providers": "^5.7.0",
|
||||
"axios": "^0.27.2"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import json from "@rollup/plugin-json"
|
||||
import * as fs from "fs"
|
||||
import cleanup from "rollup-plugin-cleanup"
|
||||
import { terser } from "rollup-plugin-terser"
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
|
||||
const name = pkg.name.substr(1).replace(/[-/]./g, (x: string) => x.toUpperCase()[1])
|
||||
const banner = `/**
|
||||
* @module ${pkg.name}
|
||||
* @version ${pkg.version}
|
||||
@@ -17,25 +16,12 @@ const banner = `/**
|
||||
export default {
|
||||
input: "src/index.ts",
|
||||
output: [
|
||||
{
|
||||
file: pkg.iife,
|
||||
name,
|
||||
format: "iife",
|
||||
globals: { axios: "axios" },
|
||||
banner
|
||||
},
|
||||
{
|
||||
file: pkg.unpkg,
|
||||
name,
|
||||
format: "iife",
|
||||
globals: { axios: "axios" },
|
||||
plugins: [terser({ output: { preamble: banner } })]
|
||||
},
|
||||
{ file: pkg.exports.require, format: "cjs", banner, exports: "auto" },
|
||||
{ file: pkg.exports.import, format: "es", banner }
|
||||
],
|
||||
external: Object.keys(pkg.dependencies),
|
||||
plugins: [
|
||||
json(),
|
||||
typescript({ tsconfig: "./build.tsconfig.json", useTsconfigDeclarationDir: true }),
|
||||
cleanup({ comments: "jsdoc" })
|
||||
]
|
||||
290
packages/data/src/ethers.test.ts
Normal file
290
packages/data/src/ethers.test.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import SemaphoreEthers from "./ethers"
|
||||
import getEvents from "./getEvents"
|
||||
|
||||
jest.mock("./getEvents", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn()
|
||||
}))
|
||||
|
||||
jest.mock("@ethersproject/contracts", () => ({
|
||||
__esModule: true,
|
||||
Contract: jest.fn(
|
||||
() =>
|
||||
({
|
||||
getMerkleTreeRoot: () => "222",
|
||||
getNumberOfMerkleTreeLeaves: () => ({
|
||||
toNumber: () => 2
|
||||
})
|
||||
} as any)
|
||||
)
|
||||
}))
|
||||
|
||||
const getEventsMocked = getEvents as jest.MockedFunction<typeof getEvents>
|
||||
|
||||
describe("SemaphoreEthers", () => {
|
||||
let semaphore: SemaphoreEthers
|
||||
|
||||
describe("# SemaphoreEthers", () => {
|
||||
it("Should instantiate a SemaphoreEthers object with different networks", () => {
|
||||
semaphore = new SemaphoreEthers()
|
||||
const semaphore1 = new SemaphoreEthers("arbitrum")
|
||||
const semaphore2 = new SemaphoreEthers("matic")
|
||||
const semaphore3 = new SemaphoreEthers("optimism-goerli")
|
||||
const semaphore4 = new SemaphoreEthers("arbitrum-goerli")
|
||||
const semaphore5 = new SemaphoreEthers("homestead", {
|
||||
address: "0x0000000000000000000000000000000000000000",
|
||||
startBlock: 0
|
||||
})
|
||||
|
||||
expect(semaphore.network).toBe("sepolia")
|
||||
expect(semaphore.contract).toBeInstanceOf(Object)
|
||||
expect(semaphore1.network).toBe("arbitrum")
|
||||
expect(semaphore2.network).toBe("maticmum")
|
||||
expect(semaphore3.network).toBe("optimism-goerli")
|
||||
expect(semaphore4.network).toBe("arbitrum-goerli")
|
||||
expect(semaphore5.network).toBe("homestead")
|
||||
expect(semaphore5.options.startBlock).toBe(0)
|
||||
expect(semaphore5.options.address).toContain("0x000000")
|
||||
})
|
||||
|
||||
it("Should instantiate a SemaphoreEthers object with different providers", () => {
|
||||
const semaphore1 = new SemaphoreEthers("homestead", {
|
||||
provider: "infura",
|
||||
address: "0x0000000000000000000000000000000000000000",
|
||||
apiKey: "1234567890"
|
||||
})
|
||||
const semaphore2 = new SemaphoreEthers("homestead", {
|
||||
provider: "etherscan",
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
const semaphore3 = new SemaphoreEthers("homestead", {
|
||||
provider: "alchemy",
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
const semaphore4 = new SemaphoreEthers("homestead", {
|
||||
provider: "cloudflare",
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
const semaphore5 = new SemaphoreEthers("homestead", {
|
||||
provider: "pocket",
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
const semaphore6 = new SemaphoreEthers("homestead", {
|
||||
provider: "ankr",
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
|
||||
expect(semaphore1.options.provider).toBe("infura")
|
||||
expect(semaphore1.options.apiKey).toBe("1234567890")
|
||||
expect(semaphore2.options.provider).toBe("etherscan")
|
||||
expect(semaphore3.options.provider).toBe("alchemy")
|
||||
expect(semaphore4.options.provider).toBe("cloudflare")
|
||||
expect(semaphore5.options.provider).toBe("pocket")
|
||||
expect(semaphore6.options.provider).toBe("ankr")
|
||||
})
|
||||
|
||||
it("Should instantiate a SemaphoreEthers object with a custom URL", () => {
|
||||
const semaphore1 = new SemaphoreEthers("http://localhost:8545", {
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
|
||||
expect(semaphore1.network).toBe("http://localhost:8545")
|
||||
})
|
||||
|
||||
it("Should throw an error if the network is not supported by Semaphore yet and there's no address", () => {
|
||||
const fun = () => new SemaphoreEthers("homestead")
|
||||
|
||||
expect(fun).toThrow("You should provide a Semaphore contract address for this network")
|
||||
})
|
||||
|
||||
it("Should throw an error if the provider is not supported", () => {
|
||||
const fun = () =>
|
||||
new SemaphoreEthers("goerli", {
|
||||
provider: "hello" as any
|
||||
})
|
||||
|
||||
expect(fun).toThrow("Provider 'hello' is not supported")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroupIds", () => {
|
||||
it("Should return all the existing groups", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(Promise.resolve([["32"], ["42"]]))
|
||||
|
||||
const groupIds = await semaphore.getGroupIds()
|
||||
|
||||
expect(groupIds).toContain("42")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroup", () => {
|
||||
it("Should return a specific group", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
merkleTreeDepth: "20",
|
||||
zeroValue: "111"
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
const group = await semaphore.getGroup("42")
|
||||
|
||||
expect(group.merkleTree.depth).toBe("20")
|
||||
expect(group.merkleTree.root).toBe("222")
|
||||
expect(group.merkleTree.zeroValue).toContain("111")
|
||||
})
|
||||
|
||||
it("Should throw an error if the group does not exist", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
|
||||
|
||||
const fun = () => semaphore.getGroup("666")
|
||||
|
||||
await expect(fun).rejects.toThrow("Group '666' not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroupAdmin", () => {
|
||||
it("Should return a group admin", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
newAdmin: "0xA9C2B639a28cDa8b59C4377e980F75A93dD8605F"
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
const admin = await semaphore.getGroupAdmin("42")
|
||||
|
||||
expect(admin).toBe("0xA9C2B639a28cDa8b59C4377e980F75A93dD8605F")
|
||||
})
|
||||
|
||||
it("Should throw an error if the group does not exist", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
|
||||
|
||||
const fun = () => semaphore.getGroupAdmin("666")
|
||||
|
||||
await expect(fun).rejects.toThrow("Group '666' not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroupMembers", () => {
|
||||
it("Should return a list of group members", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
merkleTreeDepth: "20",
|
||||
zeroValue: "0"
|
||||
}
|
||||
])
|
||||
)
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
index: "0",
|
||||
merkleTreeRoot: "223",
|
||||
blockNumber: 3
|
||||
},
|
||||
{
|
||||
index: "2",
|
||||
merkleTreeRoot: "224",
|
||||
blockNumber: 4
|
||||
}
|
||||
])
|
||||
)
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
index: "1",
|
||||
newIdentityCommitment: "113",
|
||||
merkleTreeRoot: "225",
|
||||
blockNumber: 3
|
||||
},
|
||||
{
|
||||
index: "2",
|
||||
newIdentityCommitment: "114",
|
||||
merkleTreeRoot: "226",
|
||||
blockNumber: 3
|
||||
}
|
||||
])
|
||||
)
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
index: "0",
|
||||
identityCommitment: "110",
|
||||
merkleTreeRoot: "220",
|
||||
blockNumber: 0
|
||||
},
|
||||
{
|
||||
index: "1",
|
||||
identityCommitment: "111",
|
||||
merkleTreeRoot: "221",
|
||||
blockNumber: 1
|
||||
},
|
||||
{
|
||||
index: "2",
|
||||
identityCommitment: "112",
|
||||
merkleTreeRoot: "222",
|
||||
blockNumber: 2
|
||||
},
|
||||
{
|
||||
index: "3",
|
||||
identityCommitment: "113",
|
||||
merkleTreeRoot: "223",
|
||||
blockNumber: 3
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
const members = await semaphore.getGroupMembers("42")
|
||||
|
||||
expect(members[0]).toBe("0")
|
||||
expect(members[1]).toBe("113")
|
||||
expect(members[2]).toBe("0")
|
||||
})
|
||||
|
||||
it("Should throw an error if the group does not exist", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
|
||||
|
||||
const fun = () => semaphore.getGroupMembers("666")
|
||||
|
||||
await expect(fun).rejects.toThrow("Group '666' not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroupVerifiedProofs", () => {
|
||||
it("Should return a list of group verified proofs", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
merkleTreeDepth: "20",
|
||||
zeroValue: "0"
|
||||
}
|
||||
])
|
||||
)
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
signal: "111",
|
||||
merkleTreeRoot: "112",
|
||||
externalNullifier: "113",
|
||||
nullifierHash: "114"
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
const [verifiedProof] = await semaphore.getGroupVerifiedProofs("42")
|
||||
|
||||
expect(verifiedProof.signal).toContain("111")
|
||||
})
|
||||
|
||||
it("Should throw an error if the group does not exist", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
|
||||
|
||||
const fun = () => semaphore.getGroupVerifiedProofs("666")
|
||||
|
||||
await expect(fun).rejects.toThrow("Group '666' not found")
|
||||
})
|
||||
})
|
||||
})
|
||||
281
packages/data/src/ethers.ts
Normal file
281
packages/data/src/ethers.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
import { Contract } from "@ethersproject/contracts"
|
||||
import {
|
||||
AlchemyProvider,
|
||||
AnkrProvider,
|
||||
CloudflareProvider,
|
||||
EtherscanProvider,
|
||||
InfuraProvider,
|
||||
JsonRpcProvider,
|
||||
PocketProvider,
|
||||
Provider
|
||||
} from "@ethersproject/providers"
|
||||
import checkParameter from "./checkParameter"
|
||||
import getEvents from "./getEvents"
|
||||
import SemaphoreABI from "./semaphoreABI.json"
|
||||
import { EthersOptions, GroupResponse, Network } from "./types"
|
||||
|
||||
export default class SemaphoreEthers {
|
||||
private _network: Network | string
|
||||
private _options: EthersOptions
|
||||
private _contract: Contract
|
||||
|
||||
/**
|
||||
* Initializes the Ethers object with an Ethereum network or custom URL.
|
||||
* @param networkOrEthereumURL Ethereum network or custom URL.
|
||||
* @param options Ethers options.
|
||||
*/
|
||||
constructor(networkOrEthereumURL: Network | string = "sepolia", options: EthersOptions = {}) {
|
||||
checkParameter(networkOrEthereumURL, "networkOrSubgraphURL", "string")
|
||||
|
||||
if (options.provider) {
|
||||
checkParameter(options.provider, "provider", "string")
|
||||
} else if (!networkOrEthereumURL.startsWith("http")) {
|
||||
options.provider = "infura"
|
||||
}
|
||||
|
||||
if (options.apiKey) {
|
||||
checkParameter(options.apiKey, "apiKey", "string")
|
||||
}
|
||||
|
||||
if (networkOrEthereumURL === "matic") {
|
||||
networkOrEthereumURL = "maticmum"
|
||||
}
|
||||
|
||||
switch (networkOrEthereumURL) {
|
||||
case "arbitrum":
|
||||
options.address = "0x72dca3c971136bf47BACF16A141f0fcfAC925aeC"
|
||||
options.startBlock = 54934350
|
||||
break
|
||||
case "arbitrum-goerli":
|
||||
options.address = "0xbE66454b0Fa9E6b3D53DC1b0f9D21978bb864531"
|
||||
options.startBlock = 11902029
|
||||
break
|
||||
case "maticmum":
|
||||
options.address = "0xF864ABa335073e01234c9a88888BfFfa965650bD"
|
||||
options.startBlock = 32902215
|
||||
break
|
||||
case "goerli":
|
||||
options.address = "0x89490c95eD199D980Cdb4FF8Bac9977EDb41A7E7"
|
||||
options.startBlock = 8255063
|
||||
break
|
||||
case "sepolia":
|
||||
options.address = "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F"
|
||||
options.startBlock = 3053948
|
||||
break
|
||||
case "optimism-goerli":
|
||||
options.address = "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F"
|
||||
options.startBlock = 6477953
|
||||
break
|
||||
default:
|
||||
if (options.address === undefined) {
|
||||
throw new Error(`You should provide a Semaphore contract address for this network`)
|
||||
}
|
||||
|
||||
if (options.startBlock === undefined) {
|
||||
options.startBlock = 0
|
||||
}
|
||||
}
|
||||
|
||||
let provider: Provider
|
||||
|
||||
switch (options.provider) {
|
||||
case "infura":
|
||||
provider = new InfuraProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
case "alchemy":
|
||||
provider = new AlchemyProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
case "cloudflare":
|
||||
provider = new CloudflareProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
case "etherscan":
|
||||
provider = new EtherscanProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
case "pocket":
|
||||
provider = new PocketProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
case "ankr":
|
||||
provider = new AnkrProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
default:
|
||||
if (!networkOrEthereumURL.startsWith("http")) {
|
||||
throw new Error(`Provider '${options.provider}' is not supported`)
|
||||
}
|
||||
|
||||
provider = new JsonRpcProvider(networkOrEthereumURL)
|
||||
}
|
||||
|
||||
this._network = networkOrEthereumURL
|
||||
this._options = options
|
||||
this._contract = new Contract(options.address, SemaphoreABI, provider)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Ethereum network or custom URL.
|
||||
* @returns Ethereum network or custom URL.
|
||||
*/
|
||||
get network(): Network | string {
|
||||
return this._network
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Ethers options.
|
||||
* @returns Ethers options.
|
||||
*/
|
||||
get options(): EthersOptions {
|
||||
return this._options
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contract object.
|
||||
* @returns Contract object.
|
||||
*/
|
||||
get contract(): Contract {
|
||||
return this._contract
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of group ids.
|
||||
* @returns List of group ids.
|
||||
*/
|
||||
async getGroupIds(): Promise<string[]> {
|
||||
const groups = await getEvents(this._contract, "GroupCreated", [], this._options.startBlock)
|
||||
|
||||
return groups.map((event: any) => event[0].toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific group.
|
||||
* @param groupId Group id.
|
||||
* @returns Specific group.
|
||||
*/
|
||||
async getGroup(groupId: string): Promise<GroupResponse> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
|
||||
const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock)
|
||||
|
||||
if (!groupCreatedEvent) {
|
||||
throw new Error(`Group '${groupId}' not found`)
|
||||
}
|
||||
|
||||
const merkleTreeRoot = await this._contract.getMerkleTreeRoot(groupId)
|
||||
const numberOfLeaves = await this._contract.getNumberOfMerkleTreeLeaves(groupId)
|
||||
|
||||
const group: GroupResponse = {
|
||||
id: groupId,
|
||||
merkleTree: {
|
||||
depth: groupCreatedEvent.merkleTreeDepth.toString(),
|
||||
zeroValue: groupCreatedEvent.zeroValue.toString(),
|
||||
numberOfLeaves: numberOfLeaves.toNumber(),
|
||||
root: merkleTreeRoot.toString()
|
||||
}
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a group admin.
|
||||
* @param groupId Group id.
|
||||
* @returns Group admin.
|
||||
*/
|
||||
async getGroupAdmin(groupId: string): Promise<string> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
|
||||
const groupAdminUpdatedEvents = await getEvents(
|
||||
this._contract,
|
||||
"GroupAdminUpdated",
|
||||
[groupId],
|
||||
this._options.startBlock
|
||||
)
|
||||
|
||||
if (groupAdminUpdatedEvents.length === 0) {
|
||||
throw new Error(`Group '${groupId}' not found`)
|
||||
}
|
||||
|
||||
return groupAdminUpdatedEvents[groupAdminUpdatedEvents.length - 1].newAdmin.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of group members.
|
||||
* @param groupId Group id.
|
||||
* @returns Group members.
|
||||
*/
|
||||
async getGroupMembers(groupId: string): Promise<string[]> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
|
||||
const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock)
|
||||
|
||||
if (!groupCreatedEvent) {
|
||||
throw new Error(`Group '${groupId}' not found`)
|
||||
}
|
||||
|
||||
const zeroValue = groupCreatedEvent.zeroValue.toString()
|
||||
const memberRemovedEvents = await getEvents(
|
||||
this._contract,
|
||||
"MemberRemoved",
|
||||
[groupId],
|
||||
this._options.startBlock
|
||||
)
|
||||
const memberUpdatedEvents = await getEvents(
|
||||
this._contract,
|
||||
"MemberUpdated",
|
||||
[groupId],
|
||||
this._options.startBlock
|
||||
)
|
||||
const groupUpdates = new Map<string, [number, string]>()
|
||||
|
||||
for (const { blockNumber, index, newIdentityCommitment } of memberUpdatedEvents) {
|
||||
groupUpdates.set(index.toString(), [blockNumber, newIdentityCommitment.toString()])
|
||||
}
|
||||
|
||||
for (const { blockNumber, index } of memberRemovedEvents) {
|
||||
const groupUpdate = groupUpdates.get(index.toString())
|
||||
|
||||
if (!groupUpdate || (groupUpdate && groupUpdate[0] < blockNumber)) {
|
||||
groupUpdates.set(index.toString(), [blockNumber, zeroValue])
|
||||
}
|
||||
}
|
||||
|
||||
const memberAddedEvents = await getEvents(this._contract, "MemberAdded", [groupId], this._options.startBlock)
|
||||
const members: string[] = []
|
||||
|
||||
for (const { index, identityCommitment } of memberAddedEvents) {
|
||||
const groupUpdate = groupUpdates.get(index.toString())
|
||||
const member = groupUpdate ? groupUpdate[1].toString() : identityCommitment.toString()
|
||||
|
||||
members.push(member)
|
||||
}
|
||||
|
||||
return members
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of group verified proofs.
|
||||
* @param groupId Group id.
|
||||
* @returns Group verified proofs.
|
||||
*/
|
||||
async getGroupVerifiedProofs(groupId: string): Promise<any> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
|
||||
const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock)
|
||||
|
||||
if (!groupCreatedEvent) {
|
||||
throw new Error(`Group '${groupId}' not found`)
|
||||
}
|
||||
|
||||
const proofVerifiedEvents = await getEvents(
|
||||
this._contract,
|
||||
"ProofVerified",
|
||||
[groupId],
|
||||
this._options.startBlock
|
||||
)
|
||||
|
||||
return proofVerifiedEvents.map((event) => ({
|
||||
signal: event.signal.toString(),
|
||||
merkleTreeRoot: event.merkleTreeRoot.toString(),
|
||||
externalNullifier: event.externalNullifier.toString(),
|
||||
nullifierHash: event.externalNullifier.toString()
|
||||
}))
|
||||
}
|
||||
}
|
||||
22
packages/data/src/getEvents.ts
Normal file
22
packages/data/src/getEvents.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/* istanbul ignore file */
|
||||
import { Contract } from "@ethersproject/contracts"
|
||||
|
||||
/**
|
||||
* Returns the list of events of a contract with possible filters.
|
||||
* @param contract Contract instance.
|
||||
* @param eventName Name of the event.
|
||||
* @param filterArgs Filter arguments.
|
||||
* @param startBlock Block from which to start fetching.
|
||||
* @returns List of contract events.
|
||||
*/
|
||||
export default async function getEvents(
|
||||
contract: Contract,
|
||||
eventName: string,
|
||||
filterArgs: any[] = [],
|
||||
startBlock: number = 0
|
||||
): Promise<any[]> {
|
||||
const filter = contract.filters[eventName](...filterArgs)
|
||||
const events = await contract.queryFilter(filter, startBlock)
|
||||
|
||||
return events.map(({ args, blockNumber }) => ({ ...args, blockNumber }))
|
||||
}
|
||||
@@ -8,9 +8,11 @@ import { Network } from "./types"
|
||||
export default function getURL(network: Network): string {
|
||||
switch (network) {
|
||||
case "goerli":
|
||||
return `https://api.thegraph.com/subgraphs/name/semaphore-protocol/goerli-5259d3`
|
||||
case "mumbai":
|
||||
case "optimism-goerli":
|
||||
case "arbitrum-goerli":
|
||||
case "arbitrum":
|
||||
return `https://api.thegraph.com/subgraphs/name/semaphore-protocol/arbitrum-86337c`
|
||||
return `https://api.studio.thegraph.com/query/14377/semaphore-${network}/v3.2.0`
|
||||
default:
|
||||
throw new TypeError(`Network '${network}' is not supported`)
|
||||
}
|
||||
5
packages/data/src/index.ts
Normal file
5
packages/data/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import SemaphoreSubgraph from "./subgraph"
|
||||
import SemaphoreEthers from "./ethers"
|
||||
|
||||
export { SemaphoreSubgraph, SemaphoreEthers }
|
||||
export * from "./types"
|
||||
@@ -8,7 +8,13 @@ import axios, { AxiosRequestConfig, AxiosResponse } from "axios"
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
export default async function request(url: string, config?: AxiosRequestConfig): Promise<any> {
|
||||
const { data }: AxiosResponse<any> = await axios(url, config)
|
||||
const { data }: AxiosResponse<any> = await axios(url, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...config?.headers
|
||||
},
|
||||
...config
|
||||
})
|
||||
|
||||
return data?.data
|
||||
}
|
||||
575
packages/data/src/semaphoreABI.json
Normal file
575
packages/data/src/semaphoreABI.json
Normal file
@@ -0,0 +1,575 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "contract ISemaphoreVerifier",
|
||||
"name": "_verifier",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__CallerIsNotTheGroupAdmin",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__GroupAlreadyExists",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__GroupDoesNotExist",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__MerkleTreeDepthIsNotSupported",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__MerkleTreeRootIsExpired",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__MerkleTreeRootIsNotPartOfTheGroup",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__YouAreUsingTheSameNillifierTwice",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "oldAdmin",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newAdmin",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "GroupAdminUpdated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeDepth",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "zeroValue",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "GroupCreated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "oldMerkleTreeDuration",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "newMerkleTreeDuration",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "GroupMerkleTreeDurationUpdated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "MemberAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "MemberRemoved",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "newIdentityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "MemberUpdated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "externalNullifier",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "nullifierHash",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "signal",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ProofVerified",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "addMember",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "identityCommitments",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"name": "addMembers",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeDepth",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "admin",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeDuration",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "createGroup",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeDepth",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "admin",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "createGroup",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getMerkleTreeDepth",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getMerkleTreeRoot",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getNumberOfMerkleTreeLeaves",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "groups",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "admin",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeDuration",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "proofSiblings",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8[]",
|
||||
"name": "proofPathIndices",
|
||||
"type": "uint8[]"
|
||||
}
|
||||
],
|
||||
"name": "removeMember",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newAdmin",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "updateGroupAdmin",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "newMerkleTreeDuration",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "updateGroupMerkleTreeDuration",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "newIdentityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "proofSiblings",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8[]",
|
||||
"name": "proofPathIndices",
|
||||
"type": "uint8[]"
|
||||
}
|
||||
],
|
||||
"name": "updateMember",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "verifier",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract ISemaphoreVerifier",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "signal",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "nullifierHash",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "externalNullifier",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[8]",
|
||||
"name": "proof",
|
||||
"type": "uint256[8]"
|
||||
}
|
||||
],
|
||||
"name": "verifyProof",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
@@ -1,5 +1,5 @@
|
||||
import request from "./request"
|
||||
import Subgraph from "./subgraph"
|
||||
import SemaphoreSubgraph from "./subgraph"
|
||||
|
||||
jest.mock("./request", () => ({
|
||||
__esModule: true,
|
||||
@@ -8,28 +8,58 @@ jest.mock("./request", () => ({
|
||||
|
||||
const requestMocked = request as jest.MockedFunction<typeof request>
|
||||
|
||||
describe("Subgraph", () => {
|
||||
let subgraph: Subgraph
|
||||
describe("SemaphoreSubgraph", () => {
|
||||
let semaphore: SemaphoreSubgraph
|
||||
|
||||
describe("# Subgraph", () => {
|
||||
it("Should instantiate a subgraph object", () => {
|
||||
subgraph = new Subgraph("goerli")
|
||||
const subgraph1 = new Subgraph()
|
||||
describe("# SemaphoreSubgraph", () => {
|
||||
it("Should instantiate a SemaphoreSubgraph object", () => {
|
||||
semaphore = new SemaphoreSubgraph()
|
||||
const semaphore1 = new SemaphoreSubgraph("arbitrum")
|
||||
|
||||
expect(subgraph.url).toContain("goerli")
|
||||
expect(subgraph1.url).toContain("arbitrum")
|
||||
expect(semaphore.url).toContain("goerli")
|
||||
expect(semaphore1.url).toContain("arbitrum")
|
||||
})
|
||||
|
||||
it("Should instantiate a SemaphoreSubgraph object using URL", () => {
|
||||
const url = "https://api.studio.thegraph.com/query/14377/semaphore-arbitrum/v3.2.0"
|
||||
const semaphore1 = new SemaphoreSubgraph(url)
|
||||
|
||||
expect(semaphore1.url).toBe(url)
|
||||
})
|
||||
|
||||
it("Should throw an error if there is a wrong network", () => {
|
||||
const fun = () => new Subgraph("wrong" as any)
|
||||
const fun = () => new SemaphoreSubgraph("wrong" as any)
|
||||
|
||||
expect(fun).toThrow("Network 'wrong' is not supported")
|
||||
})
|
||||
|
||||
it("Should throw an error if the network parameter type is wrong", () => {
|
||||
const fun = () => new Subgraph(33 as any)
|
||||
it("Should throw an error if the networkOrSubgraphURL parameter type is wrong", () => {
|
||||
const fun = () => new SemaphoreSubgraph(33 as any)
|
||||
|
||||
expect(fun).toThrow("Parameter 'network' is not a string")
|
||||
expect(fun).toThrow("Parameter 'networkOrSubgraphURL' is not a string")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroupIds", () => {
|
||||
it("Should return all the existing group ids", async () => {
|
||||
requestMocked.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
groups: [
|
||||
{
|
||||
id: "1"
|
||||
},
|
||||
{
|
||||
id: "2"
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await semaphore.getGroupIds()
|
||||
|
||||
expect(expectedValue).toBeDefined()
|
||||
expect(Array.isArray(expectedValue)).toBeTruthy()
|
||||
expect(expectedValue).toContainEqual("1")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -52,7 +82,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await subgraph.getGroups()
|
||||
const expectedValue = await semaphore.getGroups()
|
||||
|
||||
expect(expectedValue).toBeDefined()
|
||||
expect(Array.isArray(expectedValue)).toBeTruthy()
|
||||
@@ -69,7 +99,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
|
||||
it("Should throw an error if the options parameter type is wrong", async () => {
|
||||
const fun = () => subgraph.getGroups(1 as any)
|
||||
const fun = () => semaphore.getGroups(1 as any)
|
||||
|
||||
await expect(fun).rejects.toThrow("Parameter 'options' is not an object")
|
||||
})
|
||||
@@ -116,7 +146,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await subgraph.getGroups({
|
||||
const expectedValue = await semaphore.getGroups({
|
||||
members: true,
|
||||
verifiedProofs: true
|
||||
})
|
||||
@@ -151,6 +181,44 @@ describe("Subgraph", () => {
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it("Should return groups by applying filters", async () => {
|
||||
requestMocked.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
groups: [
|
||||
{
|
||||
id: "1",
|
||||
merkleTree: {
|
||||
depth: 20,
|
||||
zeroValue: 0,
|
||||
numberOfLeaves: 2,
|
||||
root: "2"
|
||||
},
|
||||
admin: "0x7bcd6f009471e9974a77086a69289d16eadba286"
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await semaphore.getGroups({
|
||||
filters: {
|
||||
admin: "0x7bcd6f009471e9974a77086a69289d16eadba286"
|
||||
}
|
||||
})
|
||||
|
||||
expect(expectedValue).toBeDefined()
|
||||
expect(Array.isArray(expectedValue)).toBeTruthy()
|
||||
expect(expectedValue).toContainEqual({
|
||||
id: "1",
|
||||
merkleTree: {
|
||||
depth: 20,
|
||||
zeroValue: 0,
|
||||
numberOfLeaves: 2,
|
||||
root: "2"
|
||||
},
|
||||
admin: "0x7bcd6f009471e9974a77086a69289d16eadba286"
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroup", () => {
|
||||
@@ -172,7 +240,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await subgraph.getGroup("1")
|
||||
const expectedValue = await semaphore.getGroup("1")
|
||||
|
||||
expect(expectedValue).toBeDefined()
|
||||
expect(expectedValue).toEqual({
|
||||
@@ -188,7 +256,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
|
||||
it("Should throw an error if the options parameter type is wrong", async () => {
|
||||
const fun = () => subgraph.getGroup("1", 1 as any)
|
||||
const fun = () => semaphore.getGroup("1", 1 as any)
|
||||
|
||||
await expect(fun).rejects.toThrow("Parameter 'options' is not an object")
|
||||
})
|
||||
@@ -235,7 +303,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await subgraph.getGroup("1", {
|
||||
const expectedValue = await semaphore.getGroup("1", {
|
||||
members: true,
|
||||
verifiedProofs: true
|
||||
})
|
||||
@@ -2,19 +2,25 @@ import { AxiosRequestConfig } from "axios"
|
||||
import checkParameter from "./checkParameter"
|
||||
import getURL from "./getURL"
|
||||
import request from "./request"
|
||||
import { GroupOptions, Network } from "./types"
|
||||
import { GroupResponse, GroupOptions, Network } from "./types"
|
||||
import { jsDateToGraphqlDate } from "./utils"
|
||||
|
||||
export default class Subgraph {
|
||||
export default class SemaphoreSubgraph {
|
||||
private _url: string
|
||||
|
||||
/**
|
||||
* Initializes the subgraph object with one of the supported networks.
|
||||
* @param network Supported Semaphore network.
|
||||
* Initializes the subgraph object with one of the supported networks or a custom URL.
|
||||
* @param networkOrSubgraphURL Supported Semaphore network or custom Subgraph URL.
|
||||
*/
|
||||
constructor(network: Network = "arbitrum") {
|
||||
checkParameter(network, "network", "string")
|
||||
constructor(networkOrSubgraphURL: Network | string = "goerli") {
|
||||
checkParameter(networkOrSubgraphURL, "networkOrSubgraphURL", "string")
|
||||
|
||||
this._url = getURL(network)
|
||||
if (networkOrSubgraphURL.startsWith("http")) {
|
||||
this._url = networkOrSubgraphURL
|
||||
return
|
||||
}
|
||||
|
||||
this._url = getURL(networkOrSubgraphURL as Network)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,12 +31,33 @@ export default class Subgraph {
|
||||
return this._url
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of group ids.
|
||||
* @returns List of group ids.
|
||||
*/
|
||||
async getGroupIds(): Promise<string[]> {
|
||||
const config: AxiosRequestConfig = {
|
||||
method: "post",
|
||||
data: JSON.stringify({
|
||||
query: `{
|
||||
groups {
|
||||
id
|
||||
}
|
||||
}`
|
||||
})
|
||||
}
|
||||
|
||||
const { groups } = await request(this._url, config)
|
||||
|
||||
return groups.map((group: any) => group.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of groups.
|
||||
* @param options Options to select the group parameters.
|
||||
* @returns List of groups.
|
||||
*/
|
||||
async getGroups(options: GroupOptions = {}): Promise<any[]> {
|
||||
async getGroups(options: GroupOptions = {}): Promise<GroupResponse[]> {
|
||||
checkParameter(options, "options", "object")
|
||||
|
||||
const { members = false, verifiedProofs = false } = options
|
||||
@@ -38,11 +65,35 @@ export default class Subgraph {
|
||||
checkParameter(members, "members", "boolean")
|
||||
checkParameter(verifiedProofs, "verifiedProofs", "boolean")
|
||||
|
||||
let filtersQuery = ""
|
||||
|
||||
if (options.filters) {
|
||||
const { admin, timestamp, timestampGte, timestampLte } = options.filters
|
||||
const filterFragments = []
|
||||
|
||||
if (admin) {
|
||||
filterFragments.push(`admin: "${admin}"`)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (timestamp) {
|
||||
filterFragments.push(`timestamp: "${jsDateToGraphqlDate(timestamp)}"`)
|
||||
} else if (timestampGte) {
|
||||
filterFragments.push(`timestamp_gte: "${jsDateToGraphqlDate(timestampGte)}"`)
|
||||
} else if (timestampLte) {
|
||||
filterFragments.push(`timestamp_lte: "${jsDateToGraphqlDate(timestampLte)}"`)
|
||||
}
|
||||
|
||||
if (filterFragments.length > 0) {
|
||||
filtersQuery = `(where: {${filterFragments.join(", ")}})`
|
||||
}
|
||||
}
|
||||
|
||||
const config: AxiosRequestConfig = {
|
||||
method: "post",
|
||||
data: JSON.stringify({
|
||||
query: `{
|
||||
groups {
|
||||
groups ${filtersQuery} {
|
||||
id
|
||||
merkleTree {
|
||||
root
|
||||
@@ -91,7 +142,7 @@ export default class Subgraph {
|
||||
* @param options Options to select the group parameters.
|
||||
* @returns Specific group.
|
||||
*/
|
||||
async getGroup(groupId: string, options: GroupOptions = {}): Promise<any> {
|
||||
async getGroup(groupId: string, options: Omit<GroupOptions, "filters"> = {}): Promise<GroupResponse> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
checkParameter(options, "options", "object")
|
||||
|
||||
48
packages/data/src/types/index.ts
Normal file
48
packages/data/src/types/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export type Network =
|
||||
| "homestead"
|
||||
| "matic"
|
||||
| "goerli"
|
||||
| "arbitrum"
|
||||
| "maticmum"
|
||||
| "mumbai"
|
||||
| "arbitrum-goerli"
|
||||
| "optimism"
|
||||
| "optimism-goerli"
|
||||
| "sepolia"
|
||||
|
||||
export type GroupOptions = {
|
||||
members?: boolean
|
||||
verifiedProofs?: boolean
|
||||
filters?: {
|
||||
admin?: string
|
||||
timestamp?: Date
|
||||
timestampGte?: Date
|
||||
timestampLte?: Date
|
||||
}
|
||||
}
|
||||
|
||||
export type GroupResponse = {
|
||||
id: string
|
||||
merkleTree: {
|
||||
root: string
|
||||
depth: number
|
||||
zeroValue: string
|
||||
numberOfLeaves: number
|
||||
}
|
||||
admin?: string
|
||||
members?: string[]
|
||||
verifiedProofs?: {
|
||||
signal: string
|
||||
merkleTreeRoot: string
|
||||
externalNullifier: string
|
||||
nullifierHash: string
|
||||
timestamp?: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export type EthersOptions = {
|
||||
address?: string
|
||||
startBlock?: number
|
||||
provider?: "etherscan" | "infura" | "alchemy" | "cloudflare" | "pocket" | "ankr"
|
||||
apiKey?: string
|
||||
}
|
||||
11
packages/data/src/utils.test.ts
Normal file
11
packages/data/src/utils.test.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { jsDateToGraphqlDate } from "./utils"
|
||||
|
||||
describe("Utils", () => {
|
||||
describe("# jsDateToGraphqlDate", () => {
|
||||
it("Should convert a JS date to the GraphQL date", async () => {
|
||||
const date = jsDateToGraphqlDate(new Date("2020-01-01"))
|
||||
|
||||
expect(date).toBe(1577836800)
|
||||
})
|
||||
})
|
||||
})
|
||||
4
packages/data/src/utils.ts
Normal file
4
packages/data/src/utils.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function jsDateToGraphqlDate(date: Date): number {
|
||||
return Math.round(date.getTime() / 1000)
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
@@ -67,19 +67,19 @@ yarn add @semaphore-protocol/group
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
\# **new Group**(treeDepth = 20, zeroValue = BigInt(0)): _Group_
|
||||
\# **new Group**(groupId: _Member_, treeDepth = 20): _Group_
|
||||
|
||||
```typescript
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
|
||||
// Group with max 1048576 members (20^²).
|
||||
const group1 = new Group()
|
||||
const group1 = new Group(1)
|
||||
|
||||
// Group with max 65536 members (16^²).
|
||||
const group2 = new Group(16)
|
||||
const group2 = new Group(1, 16)
|
||||
|
||||
// Group with max 16777216 members (24^²).
|
||||
const group3 = new Group(24)
|
||||
const group3 = new Group(1, 24)
|
||||
```
|
||||
|
||||
\# **addMember**(identityCommitment: _Member_)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/group",
|
||||
"version": "2.6.1",
|
||||
"version": "3.3.0",
|
||||
"description": "A library to create and manage Semaphore groups.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
@@ -30,12 +30,17 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^24.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"poseidon-lite": "^0.1.0",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@zk-kit/incremental-merkle-tree": "1.0.0",
|
||||
"poseidon-lite": "^0.0.2"
|
||||
"@ethersproject/bignumber": "^5.7.0",
|
||||
"@ethersproject/bytes": "^5.7.0",
|
||||
"@ethersproject/keccak256": "^5.7.0",
|
||||
"@zk-kit/incremental-merkle-tree": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
import commonjs from "@rollup/plugin-commonjs"
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve"
|
||||
import * as fs from "fs"
|
||||
import cleanup from "rollup-plugin-cleanup"
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
|
||||
const banner = `/**
|
||||
@@ -24,6 +26,8 @@ export default {
|
||||
tsconfig: "./build.tsconfig.json",
|
||||
useTsconfigDeclarationDir: true
|
||||
}),
|
||||
commonjs(),
|
||||
nodeResolve(),
|
||||
cleanup({ comments: "jsdoc" })
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
import Group from "./group"
|
||||
import hash from "./hash"
|
||||
|
||||
describe("Group", () => {
|
||||
describe("# Group", () => {
|
||||
it("Should create a group", () => {
|
||||
const group = new Group()
|
||||
const group = new Group(1)
|
||||
|
||||
expect(group.root.toString()).toContain("150197")
|
||||
expect(group.id).toBe(1)
|
||||
expect(group.root.toString()).toContain("103543")
|
||||
expect(group.depth).toBe(20)
|
||||
expect(group.zeroValue).toBe(BigInt(0))
|
||||
expect(group.zeroValue).toBe(hash(1))
|
||||
expect(group.members).toHaveLength(0)
|
||||
})
|
||||
|
||||
it("Should not create a group with a wrong tree depth", () => {
|
||||
const fun = () => new Group(33)
|
||||
const fun = () => new Group(1, 33)
|
||||
|
||||
expect(fun).toThrow("The tree depth must be between 16 and 32")
|
||||
})
|
||||
|
||||
it("Should create a group with different parameters", () => {
|
||||
const group = new Group(32, BigInt(1))
|
||||
const group = new Group(1, 32)
|
||||
|
||||
expect(group.root.toString()).toContain("640470")
|
||||
expect(group.root.toString()).toContain("460373")
|
||||
expect(group.depth).toBe(32)
|
||||
expect(group.zeroValue).toBe(BigInt(1))
|
||||
expect(group.zeroValue).toBe(hash(1))
|
||||
expect(group.members).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# addMember", () => {
|
||||
it("Should add a member to a group", () => {
|
||||
const group = new Group()
|
||||
const group = new Group(1)
|
||||
|
||||
group.addMember(BigInt(3))
|
||||
|
||||
@@ -39,7 +41,7 @@ describe("Group", () => {
|
||||
|
||||
describe("# addMembers", () => {
|
||||
it("Should add many members to a group", () => {
|
||||
const group = new Group()
|
||||
const group = new Group(1)
|
||||
|
||||
group.addMembers([BigInt(1), BigInt(3)])
|
||||
|
||||
@@ -49,7 +51,7 @@ describe("Group", () => {
|
||||
|
||||
describe("# indexOf", () => {
|
||||
it("Should return the index of a member in a group", () => {
|
||||
const group = new Group()
|
||||
const group = new Group(1)
|
||||
group.addMembers([BigInt(1), BigInt(3)])
|
||||
|
||||
const index = group.indexOf(BigInt(3))
|
||||
@@ -60,7 +62,7 @@ describe("Group", () => {
|
||||
|
||||
describe("# updateMember", () => {
|
||||
it("Should update a member in a group", () => {
|
||||
const group = new Group()
|
||||
const group = new Group(1)
|
||||
group.addMembers([BigInt(1), BigInt(3)])
|
||||
|
||||
group.updateMember(0, BigInt(1))
|
||||
@@ -72,7 +74,7 @@ describe("Group", () => {
|
||||
|
||||
describe("# removeMember", () => {
|
||||
it("Should remove a member from a group", () => {
|
||||
const group = new Group()
|
||||
const group = new Group(1)
|
||||
group.addMembers([BigInt(1), BigInt(3)])
|
||||
|
||||
group.removeMember(0)
|
||||
@@ -84,7 +86,7 @@ describe("Group", () => {
|
||||
|
||||
describe("# generateMerkleProof", () => {
|
||||
it("Should generate a proof of membership", () => {
|
||||
const group = new Group()
|
||||
const group = new Group(1)
|
||||
group.addMembers([BigInt(1), BigInt(3)])
|
||||
|
||||
const proof = group.generateMerkleProof(0)
|
||||
|
||||
@@ -1,28 +1,40 @@
|
||||
import { IncrementalMerkleTree, MerkleProof } from "@zk-kit/incremental-merkle-tree"
|
||||
import poseidon from "poseidon-lite"
|
||||
import { Member } from "./types"
|
||||
import { poseidon2 } from "poseidon-lite/poseidon2"
|
||||
import hash from "./hash"
|
||||
import { BigNumberish } from "./types"
|
||||
|
||||
export default class Group {
|
||||
private _id: BigNumberish
|
||||
|
||||
merkleTree: IncrementalMerkleTree
|
||||
|
||||
/**
|
||||
* Initializes the group with the tree depth and the zero value.
|
||||
* Initializes the group with the group id and the tree depth.
|
||||
* @param id Group identifier.
|
||||
* @param treeDepth Tree depth.
|
||||
* @param zeroValue Zero values for zeroes.
|
||||
*/
|
||||
constructor(treeDepth = 20, zeroValue: Member = BigInt(0)) {
|
||||
constructor(id: BigNumberish, treeDepth = 20) {
|
||||
if (treeDepth < 16 || treeDepth > 32) {
|
||||
throw new Error("The tree depth must be between 16 and 32")
|
||||
}
|
||||
|
||||
this.merkleTree = new IncrementalMerkleTree(poseidon, treeDepth, zeroValue, 2)
|
||||
this._id = id
|
||||
this.merkleTree = new IncrementalMerkleTree(poseidon2, treeDepth, hash(id), 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the group.
|
||||
* @returns Group id.
|
||||
*/
|
||||
get id(): BigNumberish {
|
||||
return this._id
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root hash of the tree.
|
||||
* @returns Root hash.
|
||||
*/
|
||||
get root(): Member {
|
||||
get root(): BigNumberish {
|
||||
return this.merkleTree.root
|
||||
}
|
||||
|
||||
@@ -38,7 +50,7 @@ export default class Group {
|
||||
* Returns the zero value of the tree.
|
||||
* @returns Tree zero value.
|
||||
*/
|
||||
get zeroValue(): Member {
|
||||
get zeroValue(): BigNumberish {
|
||||
return this.merkleTree.zeroes[0]
|
||||
}
|
||||
|
||||
@@ -46,7 +58,7 @@ export default class Group {
|
||||
* Returns the members (i.e. identity commitments) of the group.
|
||||
* @returns List of members.
|
||||
*/
|
||||
get members(): Member[] {
|
||||
get members(): BigNumberish[] {
|
||||
return this.merkleTree.leaves
|
||||
}
|
||||
|
||||
@@ -55,7 +67,7 @@ export default class Group {
|
||||
* @param member Group member.
|
||||
* @returns Index of the member.
|
||||
*/
|
||||
indexOf(member: Member): number {
|
||||
indexOf(member: BigNumberish): number {
|
||||
return this.merkleTree.indexOf(member)
|
||||
}
|
||||
|
||||
@@ -63,7 +75,7 @@ export default class Group {
|
||||
* Adds a new member to the group.
|
||||
* @param member New member.
|
||||
*/
|
||||
addMember(member: Member) {
|
||||
addMember(member: BigNumberish) {
|
||||
this.merkleTree.insert(BigInt(member))
|
||||
}
|
||||
|
||||
@@ -71,7 +83,7 @@ export default class Group {
|
||||
* Adds new members to the group.
|
||||
* @param members New members.
|
||||
*/
|
||||
addMembers(members: Member[]) {
|
||||
addMembers(members: BigNumberish[]) {
|
||||
for (const member of members) {
|
||||
this.addMember(member)
|
||||
}
|
||||
@@ -82,7 +94,7 @@ export default class Group {
|
||||
* @param index Index of the member to be updated.
|
||||
* @param member New member value.
|
||||
*/
|
||||
updateMember(index: number, member: Member) {
|
||||
updateMember(index: number, member: BigNumberish) {
|
||||
this.merkleTree.update(index, member)
|
||||
}
|
||||
|
||||
|
||||
15
packages/group/src/hash.ts
Normal file
15
packages/group/src/hash.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { BigNumber } from "@ethersproject/bignumber"
|
||||
import { BytesLike, Hexable, zeroPad } from "@ethersproject/bytes"
|
||||
import { keccak256 } from "@ethersproject/keccak256"
|
||||
|
||||
/**
|
||||
* Creates a keccak256 hash of a message compatible with the SNARK scalar modulus.
|
||||
* @param message The message to be hashed.
|
||||
* @returns The message digest.
|
||||
*/
|
||||
export default function hash(message: BytesLike | Hexable | number | bigint): bigint {
|
||||
message = BigNumber.from(message).toTwos(256).toHexString()
|
||||
message = zeroPad(message, 32)
|
||||
|
||||
return BigInt(keccak256(message)) >> BigInt(8)
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
export type Member = string | bigint
|
||||
export type BigNumberish = string | number | bigint
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<h1 align="center">
|
||||
Semaphore Hardhat plugin
|
||||
</h1>
|
||||
<p align="center">A Semaphore Hardhat plugin to deploy verifiers and Semaphore contracts.</p>
|
||||
<p align="center">A Semaphore Hardhat plugin to deploy Semaphore contracts.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -40,14 +40,14 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| This Hardhat plugin provides two simple tasks that can be used to deploy verifiers and Semaphore contracts without any additional configuration. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| This Hardhat plugin provides two simple tasks that can be used to deploy Semaphore contracts without any additional configuration. |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
@@ -88,21 +88,18 @@ import { task, types } from "hardhat/config"
|
||||
task("deploy", "Deploy a Greeter contract")
|
||||
.addOptionalParam("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs }, { ethers, run }) => {
|
||||
const { address: verifierAddress } = await run("deploy:verifier", { logs, merkleTreeDepth: 20 })
|
||||
|
||||
const { address: semaphoreAddress } = await run("deploy:semaphore", {
|
||||
logs,
|
||||
verifiers: [
|
||||
{
|
||||
merkleTreeDepth: 20,
|
||||
contractAddress: verifierAddress
|
||||
}
|
||||
]
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs
|
||||
})
|
||||
|
||||
// Or:
|
||||
// const { semaphoreVerifier } = await run("deploy:semaphore-verifier", {
|
||||
// logs
|
||||
// })
|
||||
|
||||
const Greeter = await ethers.getContractFactory("Greeter")
|
||||
|
||||
const greeter = await Greeter.deploy(semaphoreAddress)
|
||||
const greeter = await Greeter.deploy(semaphore.address)
|
||||
|
||||
await greeter.deployed()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/hardhat",
|
||||
"version": "0.1.0",
|
||||
"version": "3.3.0",
|
||||
"description": "A Semaphore Hardhat plugin to deploy verifiers and Semaphore contract.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nomiclabs/hardhat-ethers": "^2.1.1",
|
||||
"@semaphore-protocol/contracts": "^2.5.0",
|
||||
"@semaphore-protocol/contracts": "3.3.0",
|
||||
"circomlibjs": "^0.0.8",
|
||||
"ethers": "^5.7.1",
|
||||
"hardhat-dependency-compiler": "^1.1.3"
|
||||
|
||||
@@ -4,27 +4,12 @@ import { HardhatConfig, HardhatUserConfig } from "hardhat/types"
|
||||
import "hardhat-dependency-compiler"
|
||||
import "@nomiclabs/hardhat-ethers"
|
||||
import "./tasks/deploy-semaphore"
|
||||
import "./tasks/deploy-verifier"
|
||||
import "./tasks/deploy-semaphore-verifier"
|
||||
|
||||
extendConfig((config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
|
||||
config.dependencyCompiler.paths = [
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier16.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier17.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier18.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier19.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier20.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier21.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier22.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier23.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier24.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier25.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier26.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier27.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier28.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier29.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier30.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier31.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier32.sol",
|
||||
"@semaphore-protocol/contracts/base/Pairing.sol",
|
||||
"@semaphore-protocol/contracts/base/SemaphoreVerifier.sol",
|
||||
"@semaphore-protocol/contracts/Semaphore.sol"
|
||||
]
|
||||
|
||||
|
||||
38
packages/hardhat/src/tasks/deploy-semaphore-verifier.ts
Normal file
38
packages/hardhat/src/tasks/deploy-semaphore-verifier.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:semaphore-verifier", "Deploy a SemaphoreVerifier contract")
|
||||
.addOptionalParam<boolean>("pairing", "Pairing library address", undefined, types.string)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs, pairing: pairingAddress }, { ethers }): Promise<any> => {
|
||||
if (!pairingAddress) {
|
||||
const PairingFactory = await ethers.getContractFactory("Pairing")
|
||||
const pairing = await PairingFactory.deploy()
|
||||
|
||||
await pairing.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Pairing library has been deployed to: ${pairing.address}`)
|
||||
}
|
||||
|
||||
pairingAddress = pairing.address
|
||||
}
|
||||
|
||||
const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", {
|
||||
libraries: {
|
||||
Pairing: pairingAddress
|
||||
}
|
||||
})
|
||||
|
||||
const semaphoreVerifier = await SemaphoreVerifierFactory.deploy()
|
||||
|
||||
await semaphoreVerifier.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`)
|
||||
}
|
||||
|
||||
return {
|
||||
semaphoreVerifier,
|
||||
pairingAddress
|
||||
}
|
||||
})
|
||||
@@ -1,51 +1,114 @@
|
||||
import { poseidon_gencontract as poseidonContract } from "circomlibjs"
|
||||
import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:semaphore", "Deploy a Semaphore contract")
|
||||
.addParam("verifiers", "Tree depths and verifier addresses", [], types.json)
|
||||
.addOptionalParam<boolean>("pairing", "Pairing library address", undefined, types.string)
|
||||
.addOptionalParam<boolean>("semaphoreVerifier", "SemaphoreVerifier contract address", undefined, types.string)
|
||||
.addOptionalParam<boolean>("poseidon", "Poseidon library address", undefined, types.string)
|
||||
.addOptionalParam<boolean>(
|
||||
"incrementalBinaryTree",
|
||||
"IncrementalBinaryTree library address",
|
||||
undefined,
|
||||
types.string
|
||||
)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs, verifiers }, { ethers }): Promise<Contract> => {
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
.setAction(
|
||||
async (
|
||||
{
|
||||
logs,
|
||||
pairing: pairingAddress,
|
||||
semaphoreVerifier: semaphoreVerifierAddress,
|
||||
poseidon: poseidonAddress,
|
||||
incrementalBinaryTree: incrementalBinaryTreeAddress
|
||||
},
|
||||
{ ethers }
|
||||
): Promise<any> => {
|
||||
if (!semaphoreVerifierAddress) {
|
||||
if (!pairingAddress) {
|
||||
const PairingFactory = await ethers.getContractFactory("Pairing")
|
||||
const pairing = await PairingFactory.deploy()
|
||||
|
||||
const [signer] = await ethers.getSigners()
|
||||
await pairing.deployed()
|
||||
|
||||
const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer)
|
||||
const poseidonLib = await PoseidonLibFactory.deploy()
|
||||
if (logs) {
|
||||
console.info(`Pairing library has been deployed to: ${pairing.address}`)
|
||||
}
|
||||
|
||||
await poseidonLib.deployed()
|
||||
pairingAddress = pairing.address
|
||||
}
|
||||
|
||||
if (logs) {
|
||||
console.info(`Poseidon library has been deployed to: ${poseidonLib.address}`)
|
||||
}
|
||||
const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", {
|
||||
libraries: {
|
||||
Pairing: pairingAddress
|
||||
}
|
||||
})
|
||||
|
||||
const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
|
||||
libraries: {
|
||||
PoseidonT3: poseidonLib.address
|
||||
const semaphoreVerifier = await SemaphoreVerifierFactory.deploy()
|
||||
|
||||
await semaphoreVerifier.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`)
|
||||
}
|
||||
|
||||
semaphoreVerifierAddress = semaphoreVerifier.address
|
||||
}
|
||||
})
|
||||
const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy()
|
||||
|
||||
await incrementalBinaryTreeLib.deployed()
|
||||
if (!incrementalBinaryTreeAddress) {
|
||||
if (!poseidonAddress) {
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
|
||||
if (logs) {
|
||||
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
|
||||
}
|
||||
const [signer] = await ethers.getSigners()
|
||||
|
||||
const SemaphoreContractFactory = await ethers.getContractFactory("Semaphore", {
|
||||
libraries: {
|
||||
IncrementalBinaryTree: incrementalBinaryTreeLib.address
|
||||
const PoseidonFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer)
|
||||
const poseidon = await PoseidonFactory.deploy()
|
||||
|
||||
await poseidon.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Poseidon library has been deployed to: ${poseidon.address}`)
|
||||
}
|
||||
|
||||
poseidonAddress = poseidon.address
|
||||
}
|
||||
|
||||
const IncrementalBinaryTreeFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
|
||||
libraries: {
|
||||
PoseidonT3: poseidonAddress
|
||||
}
|
||||
})
|
||||
const incrementalBinaryTree = await IncrementalBinaryTreeFactory.deploy()
|
||||
|
||||
await incrementalBinaryTree.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTree.address}`)
|
||||
}
|
||||
|
||||
incrementalBinaryTreeAddress = incrementalBinaryTree.address
|
||||
}
|
||||
})
|
||||
|
||||
const semaphoreContract = await SemaphoreContractFactory.deploy(verifiers)
|
||||
const SemaphoreFactory = await ethers.getContractFactory("Semaphore", {
|
||||
libraries: {
|
||||
IncrementalBinaryTree: incrementalBinaryTreeAddress
|
||||
}
|
||||
})
|
||||
|
||||
await semaphoreContract.deployed()
|
||||
const semaphore = await SemaphoreFactory.deploy(semaphoreVerifierAddress)
|
||||
|
||||
if (logs) {
|
||||
console.info(`Semaphore contract has been deployed to: ${semaphoreContract.address}`)
|
||||
await semaphore.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Semaphore contract has been deployed to: ${semaphore.address}`)
|
||||
}
|
||||
|
||||
return {
|
||||
semaphore,
|
||||
pairingAddress,
|
||||
semaphoreVerifierAddress,
|
||||
poseidonAddress,
|
||||
incrementalBinaryTreeAddress
|
||||
}
|
||||
}
|
||||
|
||||
return semaphoreContract
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:verifier", "Deploy a Verifier contract")
|
||||
.addParam<number>("merkleTreeDepth", "Merkle tree depth", undefined, types.int)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ merkleTreeDepth, logs }, { ethers }): Promise<Contract> => {
|
||||
const VerifierContractFactory = await ethers.getContractFactory(`Verifier${merkleTreeDepth}`)
|
||||
|
||||
const verifierContract = await VerifierContractFactory.deploy()
|
||||
|
||||
await verifierContract.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Verifier${merkleTreeDepth} contract has been deployed to: ${verifierContract.address}`)
|
||||
}
|
||||
|
||||
return verifierContract
|
||||
})
|
||||
21
packages/heyauthn/LICENSE
Normal file
21
packages/heyauthn/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Vivek Bhupatiraju
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
118
packages/heyauthn/README.md
Normal file
118
packages/heyauthn/README.md
Normal file
@@ -0,0 +1,118 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
HeyAuthn
|
||||
</h1>
|
||||
<p align="center">A library to allow developers to create and manage Semaphore identities using WebAuthn.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/semaphore-protocol">
|
||||
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@semaphore-protocol/heyauthn">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/heyauthn?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/heyauthn.svg?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://eslint.org/">
|
||||
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
|
||||
</a>
|
||||
<a href="https://prettier.io/">
|
||||
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h4>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
|
||||
👥 Contributing
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
|
||||
🤝 Code of conduct
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| This library allows developers to create and manage Semaphore identities using [WebAuthn](https://webauthn.io/) as a cross-device biometric authentication in a way that is more convenient, smoother and secure than localStorage, Chrome extensions, or password manager based solutions. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@semaphore-protocol/heyauthn` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @semaphore-protocol/heyauthn
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @semaphore-protocol/heyauthn
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
```typescript
|
||||
import { HeyAuthn } from "@semaphore-protocol/heyauthn"
|
||||
|
||||
// STEP 1: Configure WebAuthn options.
|
||||
|
||||
const options = {
|
||||
rpName: "my-app",
|
||||
rpID: window.location.hostname,
|
||||
userID: "my-id",
|
||||
userName: "my-name"
|
||||
}
|
||||
|
||||
// STEP 2: Register a new WebAuthn credential and get its Semaphore identity.
|
||||
|
||||
const { identity } = await HeyAuthn.fromRegister(options)
|
||||
|
||||
// Now you could also save the identity commitment in your DB (pseudocode).
|
||||
fetch("/api/register" /* Replace this with your endpoint */, {
|
||||
identity.commmitment
|
||||
// ...
|
||||
})
|
||||
|
||||
// STEP 3: Authenticate existing WebAuthn credential and signal.
|
||||
|
||||
const { identity } = await HeyAuthn.fromRegister(options)
|
||||
|
||||
// Get existing group and signal anonymously (pseudocode).
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { utils } from "ethers"
|
||||
|
||||
const group = new Group("42")
|
||||
|
||||
group.addMembers(memberList)
|
||||
|
||||
const signal = utils.formatBytes32String("Hey anon!")
|
||||
|
||||
generateProof(identity, group, group.id, "42", {
|
||||
zkeyFilePath: "./semaphore.zkey",
|
||||
wasmFilePath: "./semaphore.wasm"
|
||||
})
|
||||
```
|
||||
|
||||
## Authors
|
||||
|
||||
- @vb7401
|
||||
- @rrrliu
|
||||
- @emmaguo13
|
||||
- @sehyunc
|
||||
8
packages/heyauthn/build.tsconfig.json
Normal file
8
packages/heyauthn/build.tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declarationDir": "dist/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
42
packages/heyauthn/package.json
Normal file
42
packages/heyauthn/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/heyauthn",
|
||||
"version": "3.3.0",
|
||||
"description": "A library to allow developers to create and manage Semaphore identities using WebAuthn",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
"exports": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.node.js"
|
||||
},
|
||||
"types": "dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist/",
|
||||
"src/",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"repository": "https://github.com/semaphore-protocol/semaphore",
|
||||
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/heyauthn",
|
||||
"bugs": {
|
||||
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
|
||||
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
|
||||
"prepublishOnly": "yarn build",
|
||||
"docs": "typedoc src/index.ts --out ../../docs/heyauthn"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/identity": "3.3.0",
|
||||
"@simplewebauthn/browser": "7.2.0",
|
||||
"@simplewebauthn/server": "7.2.0"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user