mirror of
https://github.com/semaphore-protocol/semaphore.git
synced 2026-01-11 07:38:14 -05:00
Compare commits
457 Commits
audit_v1
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3188d3dbff | ||
|
|
04f57db7f0 | ||
|
|
066f38c471 | ||
|
|
37e8784471 | ||
|
|
889cf1890a | ||
|
|
1a4a7f36e0 | ||
|
|
3d6c24a51b | ||
|
|
781922436e | ||
|
|
277a790e36 | ||
|
|
d880925604 | ||
|
|
5466178f40 | ||
|
|
6cc4dc07bb | ||
|
|
98a35c0a37 | ||
|
|
bfeb24791d | ||
|
|
37e2614ac7 | ||
|
|
84bb9c89a4 | ||
|
|
a4aaf7f7ec | ||
|
|
b92a6e1c7a | ||
|
|
3afae28e06 | ||
|
|
63cddf3da2 | ||
|
|
499ec1cbeb | ||
|
|
fb1ffee89d | ||
|
|
f7bc7900e0 | ||
|
|
bc14210bc7 | ||
|
|
aabad94a81 | ||
|
|
eeac211c01 | ||
|
|
6e0236e9bc | ||
|
|
a9f8379545 | ||
|
|
ac3e7b42a3 | ||
|
|
2b414f8c24 | ||
|
|
1fcff83c1a | ||
|
|
eb2d6ee62b | ||
|
|
db624c24e0 | ||
|
|
95e5ff669b | ||
|
|
6a5e9e32d3 | ||
|
|
08d7621fc2 | ||
|
|
ca3a4c1971 | ||
|
|
dc0ceee50d | ||
|
|
6fadd3e745 | ||
|
|
e86228102e | ||
|
|
91f56941b4 | ||
|
|
22aadf2745 | ||
|
|
8458a623e0 | ||
|
|
e6a3699a56 | ||
|
|
ed74eff52a | ||
|
|
a7e5b28b00 | ||
|
|
08576e1717 | ||
|
|
80376ab81e | ||
|
|
32efdd952b | ||
|
|
f6b79ba5d2 | ||
|
|
1a995d7087 | ||
|
|
20fc3c58d7 | ||
|
|
7329ca6a48 | ||
|
|
0c9c0c9791 | ||
|
|
39a7e32143 | ||
|
|
87c27b9d03 | ||
|
|
4d07a1ede5 | ||
|
|
55fbd0f2ed | ||
|
|
6dca198995 | ||
|
|
5f80aab430 | ||
|
|
c30fbb8d1c | ||
|
|
be1014452e | ||
|
|
0036da93b1 | ||
|
|
c984adef0e | ||
|
|
b2d8667963 | ||
|
|
466be38e42 | ||
|
|
adcb8e085d | ||
|
|
e9a3770a39 | ||
|
|
d9a1387f2a | ||
|
|
a6710ad435 | ||
|
|
3d3a63a10d | ||
|
|
0daf5b7dae | ||
|
|
ef4b3dd4b2 | ||
|
|
0f2a13463a | ||
|
|
0a305c019d | ||
|
|
de8c7f20ca | ||
|
|
23d7fdff3a | ||
|
|
21b9965f57 | ||
|
|
023c8bae64 | ||
|
|
0d771eb9fa | ||
|
|
62f775737d | ||
|
|
12aacf24b3 | ||
|
|
b450dcec79 | ||
|
|
e909e1db99 | ||
|
|
92c9c9bcc8 | ||
|
|
5588256072 | ||
|
|
8cf04ddb98 | ||
|
|
ecca5a4ee9 | ||
|
|
0a5ebe60df | ||
|
|
12982f338a | ||
|
|
47c7923723 | ||
|
|
a35a505b92 | ||
|
|
9487de51b4 | ||
|
|
65fbd732f2 | ||
|
|
e6fe600ed0 | ||
|
|
28694d3413 | ||
|
|
d5d065dfb0 | ||
|
|
cb96c7c237 | ||
|
|
cca4cf4922 | ||
|
|
a97a150e98 | ||
|
|
5167710bb2 | ||
|
|
af878df5ab | ||
|
|
e4e895a881 | ||
|
|
95ab343fcc | ||
|
|
4f36e0587c | ||
|
|
f50a0765fd | ||
|
|
7739050346 | ||
|
|
07f2e14fcb | ||
|
|
f7d20a4760 | ||
|
|
65e6d7b16c | ||
|
|
dfac0716a5 | ||
|
|
43850ecd3c | ||
|
|
125947d179 | ||
|
|
4643775745 | ||
|
|
dd6b7765cf | ||
|
|
b7597485ec | ||
|
|
47b06b057c | ||
|
|
c06a836687 | ||
|
|
7b3acfd68f | ||
|
|
972cbb632b | ||
|
|
b37b0e2d7a | ||
|
|
48928db10f | ||
|
|
5fdf6c6547 | ||
|
|
8be15f2926 | ||
|
|
baf7b80f64 | ||
|
|
b501fd6f01 | ||
|
|
78cbd16cb6 | ||
|
|
94331b07b0 | ||
|
|
f9ff3b1ac2 | ||
|
|
f59588ac2f | ||
|
|
b285a19186 | ||
|
|
5e58a2dc4a | ||
|
|
b7b6e452d8 | ||
|
|
78dcfe1816 | ||
|
|
f67cafeb02 | ||
|
|
9ca5d8b8cb | ||
|
|
b80fe7d1d9 | ||
|
|
bf2f8024e3 | ||
|
|
ba6acccffb | ||
|
|
e829885311 | ||
|
|
84e92ed975 | ||
|
|
05adc1e2fe | ||
|
|
971b354e1c | ||
|
|
fa1e86c3b0 | ||
|
|
621f56a0c6 | ||
|
|
5470804e1c | ||
|
|
17e683debe | ||
|
|
29090843aa | ||
|
|
d7c677bb05 | ||
|
|
16ebaa61e5 | ||
|
|
52581a6d56 | ||
|
|
43803ec21d | ||
|
|
79313cbf6c | ||
|
|
36c3252b0c | ||
|
|
168215d23e | ||
|
|
75bef955ab | ||
|
|
68a65f9645 | ||
|
|
13b724a34f | ||
|
|
073ec22b34 | ||
|
|
5d6f7b0629 | ||
|
|
75fc12f867 | ||
|
|
e21ccedc15 | ||
|
|
68187531a5 | ||
|
|
3dba37fd06 | ||
|
|
17234ddfd2 | ||
|
|
536cf452bb | ||
|
|
efe56e80ac | ||
|
|
f09e930857 | ||
|
|
2e5650a5b8 | ||
|
|
7c3763c012 | ||
|
|
e67cf5d75b | ||
|
|
5a99b66f18 | ||
|
|
df9286a455 | ||
|
|
f4dd5ba694 | ||
|
|
8c49c4ed14 | ||
|
|
1f805543e8 | ||
|
|
a674301faf | ||
|
|
12e92840a4 | ||
|
|
ac7f03a695 | ||
|
|
20036c3739 | ||
|
|
8826ddb47b | ||
|
|
c074930aad | ||
|
|
f2a1d50603 | ||
|
|
3dc63c83bb | ||
|
|
0f62fe1356 | ||
|
|
10562ca54f | ||
|
|
f237a44980 | ||
|
|
ec99ab0759 | ||
|
|
7c48005647 | ||
|
|
3b61ceebde | ||
|
|
0d43e0b4c8 | ||
|
|
6cf990bc22 | ||
|
|
9875778ad9 | ||
|
|
6ef014fa7c | ||
|
|
06fbe09823 | ||
|
|
80d3ffb622 | ||
|
|
52dbe8b14e | ||
|
|
c4207c73b0 | ||
|
|
45a74798c7 | ||
|
|
5e7c3a5ae9 | ||
|
|
4975db4f58 | ||
|
|
aa96f0bbc1 | ||
|
|
6e5c6fc84c | ||
|
|
8c1119dba1 | ||
|
|
68f4bead0e | ||
|
|
bd78a71387 | ||
|
|
e8f34ea272 | ||
|
|
2a4e92449b | ||
|
|
57b61614c4 | ||
|
|
45f48799f0 | ||
|
|
42c934ec1b | ||
|
|
35822a480e | ||
|
|
56edbb6cd7 | ||
|
|
d0962fda19 | ||
|
|
d17c4a6e0c | ||
|
|
4080f4af20 | ||
|
|
9e74e1a278 | ||
|
|
a8e0c1474c | ||
|
|
d1fcf8bfe2 | ||
|
|
5a895f42f8 | ||
|
|
8a44757417 | ||
|
|
ad1d77b200 | ||
|
|
c31b9490ed | ||
|
|
4aeeb3e1cc | ||
|
|
23dac4c6ed | ||
|
|
4c4bfb568a | ||
|
|
8595e3cd0f | ||
|
|
01a5b156bb | ||
|
|
dd19a5e213 | ||
|
|
9e2b54ef16 | ||
|
|
51fe424473 | ||
|
|
5dbfaf3518 | ||
|
|
4e18efb75c | ||
|
|
55a89753bb | ||
|
|
8105d9761b | ||
|
|
55cef34142 | ||
|
|
bf42264033 | ||
|
|
0035a62d7a | ||
|
|
d34bded3cc | ||
|
|
e156cf77c2 | ||
|
|
4d662a4a0d | ||
|
|
67e907da2c | ||
|
|
7771f15e5a | ||
|
|
aff0c87366 | ||
|
|
77bc53d9a7 | ||
|
|
2965d8decf | ||
|
|
528ad6ef4e | ||
|
|
bd2145dc09 | ||
|
|
67dd25e604 | ||
|
|
25379748e6 | ||
|
|
9e6c0df6a2 | ||
|
|
07cbe28701 | ||
|
|
d212947d07 | ||
|
|
bdffdfe200 | ||
|
|
a8922efae1 | ||
|
|
4ee9913035 | ||
|
|
04df22ed56 | ||
|
|
abc8ef3cf6 | ||
|
|
c2a8aee15e | ||
|
|
292ce60b81 | ||
|
|
3a64ab44dd | ||
|
|
d597b461a7 | ||
|
|
a7a42f8d2c | ||
|
|
9f7aa7955f | ||
|
|
2ffaf28a12 | ||
|
|
bfaeb19d14 | ||
|
|
aed9fa6785 | ||
|
|
ac1ad33bfd | ||
|
|
e0e366c52a | ||
|
|
e6f7df17a9 | ||
|
|
c610ddb45a | ||
|
|
8c7d09a5c1 | ||
|
|
8d5968a3c0 | ||
|
|
936e0d76a6 | ||
|
|
b5389fed3d | ||
|
|
5f1ed4e08e | ||
|
|
37bb2976b8 | ||
|
|
17e6330798 | ||
|
|
a4ecc5a125 | ||
|
|
9bfe26d81d | ||
|
|
9756917fc7 | ||
|
|
83e29909c3 | ||
|
|
ae22bd7be9 | ||
|
|
3295483a24 | ||
|
|
b21a17ea70 | ||
|
|
d1419045e3 | ||
|
|
c136ba809f | ||
|
|
bf87f4a3af | ||
|
|
7ddd0e0920 | ||
|
|
498d14cde1 | ||
|
|
91867d53aa | ||
|
|
dbc9ae303d | ||
|
|
0c9d717fa8 | ||
|
|
a6256d2cdb | ||
|
|
9944eb3746 | ||
|
|
d5fd1f99a3 | ||
|
|
71dc72daa8 | ||
|
|
7dd398ebd9 | ||
|
|
28990a27f3 | ||
|
|
7993440e53 | ||
|
|
05c26c108c | ||
|
|
940e6fcb75 | ||
|
|
31e885a075 | ||
|
|
d37deffb73 | ||
|
|
9285fe54e6 | ||
|
|
7095e03162 | ||
|
|
ce09c9e4dd | ||
|
|
17d616e0ba | ||
|
|
af6f040429 | ||
|
|
eeb65e4639 | ||
|
|
aa544bf2c9 | ||
|
|
967e93789e | ||
|
|
ec5c6a1c23 | ||
|
|
79a6bfcbf7 | ||
|
|
a08c5460c3 | ||
|
|
1259d8e53e | ||
|
|
bea3aca4a9 | ||
|
|
04e89d83e0 | ||
|
|
83798c959e | ||
|
|
23a9fe78bf | ||
|
|
108160740d | ||
|
|
fdc66d3769 | ||
|
|
bad1176c60 | ||
|
|
6d8511c35c | ||
|
|
ec48e81fe0 | ||
|
|
bf7dda0666 | ||
|
|
ccb1d762c4 | ||
|
|
6ecf6c20af | ||
|
|
e575c6025b | ||
|
|
1f4120f30d | ||
|
|
ea6ff9d6a9 | ||
|
|
ec7387f1c5 | ||
|
|
04e716a917 | ||
|
|
3efea8c2bf | ||
|
|
a38c5b4360 | ||
|
|
fd7342f5b3 | ||
|
|
7ca5de85d6 | ||
|
|
6d313e194e | ||
|
|
e9ed84c868 | ||
|
|
010726618e | ||
|
|
df0c87f5e7 | ||
|
|
55079c25a9 | ||
|
|
1242e73d34 | ||
|
|
1be081c10a | ||
|
|
4a4ef3d3bf | ||
|
|
ba02e7719f | ||
|
|
406424c0a3 | ||
|
|
633a1a7a19 | ||
|
|
3680cc294d | ||
|
|
6d51e94a88 | ||
|
|
9bd085195f | ||
|
|
0826bbcfa3 | ||
|
|
4c643f012b | ||
|
|
1a3d35662c | ||
|
|
064aa99b3d | ||
|
|
eec2ff5c1f | ||
|
|
7527f159ad | ||
|
|
f06a12535f | ||
|
|
7ad7acfb54 | ||
|
|
76092003f7 | ||
|
|
817783e797 | ||
|
|
14a7493cc8 | ||
|
|
f15ba8fcda | ||
|
|
4e04ba2413 | ||
|
|
5b9a4aaceb | ||
|
|
af9b07aa20 | ||
|
|
1ca9e568c4 | ||
|
|
c21d0b6e6b | ||
|
|
56716cb258 | ||
|
|
04b585021f | ||
|
|
2a44bd6c75 | ||
|
|
7d54825ba1 | ||
|
|
0900615ae4 | ||
|
|
6837dd3cbd | ||
|
|
7888aebcd7 | ||
|
|
a6cf53520b | ||
|
|
0b66144c76 | ||
|
|
9f931d1f19 | ||
|
|
db8da587ab | ||
|
|
96b5e10d75 | ||
|
|
e15fd3174e | ||
|
|
b0bbb981a0 | ||
|
|
d7dc8bdd60 | ||
|
|
21b7ccabd3 | ||
|
|
778d5a1574 | ||
|
|
e7c515aabc | ||
|
|
a77fcf848f | ||
|
|
1c91e59f71 | ||
|
|
19e54ac348 | ||
|
|
b7b8190edc | ||
|
|
ead549e9f1 | ||
|
|
55938b0eaa | ||
|
|
e614cc68ac | ||
|
|
3a94fb72f9 | ||
|
|
1772e776a7 | ||
|
|
e6a5d1f4b6 | ||
|
|
5abfed2b77 | ||
|
|
82c4dea962 | ||
|
|
544046a392 | ||
|
|
541cd4298b | ||
|
|
67c6af125e | ||
|
|
a11449493b | ||
|
|
42031746fa | ||
|
|
ca2d5a193c | ||
|
|
3602219bb6 | ||
|
|
710e546eee | ||
|
|
882191996c | ||
|
|
a9630e436b | ||
|
|
b524655db1 | ||
|
|
7dead05108 | ||
|
|
ed355829cd | ||
|
|
c37496cf00 | ||
|
|
1be8ac5df5 | ||
|
|
bc8362f4f0 | ||
|
|
e84264b24d | ||
|
|
970b19257f | ||
|
|
421b7da0ba | ||
|
|
665ab42870 | ||
|
|
69c25d4b05 | ||
|
|
37337ccff6 | ||
|
|
583cb2fb2a | ||
|
|
d5a3781d01 | ||
|
|
4d6045a01c | ||
|
|
acf5d7dc98 | ||
|
|
358dd69753 | ||
|
|
57be569adb | ||
|
|
948685edf2 | ||
|
|
548d7bbe31 | ||
|
|
ceb20422b7 | ||
|
|
8e41b6cffd | ||
|
|
a4378925ac | ||
|
|
8f0c937023 | ||
|
|
ad6873bfb3 | ||
|
|
2a5ebeb6fb | ||
|
|
bc4b0acefd | ||
|
|
3824857b04 | ||
|
|
d945172ce9 | ||
|
|
8f7214a921 | ||
|
|
4510809a1b | ||
|
|
c7ca8dc02d | ||
|
|
589ade76d5 | ||
|
|
36d634c47e | ||
|
|
dabd686b1f | ||
|
|
5f8ad2b71b | ||
|
|
f4734cf9ce | ||
|
|
12d54003c5 | ||
|
|
cfa98a9833 | ||
|
|
01921268fb | ||
|
|
93448311f5 | ||
|
|
6ed742771a | ||
|
|
86e22c8cb9 | ||
|
|
dd435e0bf9 | ||
|
|
6d26f0d39e | ||
|
|
c01132b535 | ||
|
|
f94e88c1af | ||
|
|
0af2ccc8c9 | ||
|
|
99922c3a76 |
@@ -1,66 +0,0 @@
|
||||
# Javascript Node CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
|
||||
#
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: circleci/node:11.14.0
|
||||
|
||||
working_directory: ~/semaphore/semaphorejs
|
||||
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/semaphore
|
||||
|
||||
# restore or install npm packages
|
||||
- restore_cache:
|
||||
name: restore-npm-cache
|
||||
keys:
|
||||
- v1.10-dependencies-{{ checksum "package-lock.json" }}
|
||||
|
||||
- run: npm install
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
key: v1.10-dependencies-{{ checksum "package-lock.json" }}
|
||||
|
||||
# checksum the snarks definitions
|
||||
- run:
|
||||
name: checksum-snarks
|
||||
command: cd scripts && ./checksum_snarks.sh
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
name: restore-snark-cache
|
||||
keys:
|
||||
- v1.10-dependencies-{{ checksum "build/.snark_checksum" }}
|
||||
|
||||
# build snarks
|
||||
- run:
|
||||
name: end-to-end
|
||||
command: cd scripts && ./build_snarks.sh
|
||||
no_output_timeout: 600m
|
||||
|
||||
# cache generated snark circuit and keys
|
||||
- save_cache:
|
||||
key: v1.10-dependencies-{{ checksum "build/.snark_checksum" }}
|
||||
paths:
|
||||
- build/circuit.json
|
||||
- build/proving_key.bin
|
||||
- build/proving_key.json
|
||||
- build/verification_key.json
|
||||
- build/verifier.sol
|
||||
|
||||
# build snarks
|
||||
- run:
|
||||
name: integration_tests
|
||||
command: cd scripts && ./integration_tests.sh
|
||||
no_output_timeout: 600m
|
||||
|
||||
- store_artifacts:
|
||||
path: ~/semaphore/semaphorejs/build
|
||||
|
||||
3
.commitlintrc.json
Normal file
3
.commitlintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
}
|
||||
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
#root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
8
.env.example
Normal file
8
.env.example
Normal file
@@ -0,0 +1,8 @@
|
||||
DEFAULT_NETWORK=hardhat
|
||||
TREE_DEPTH=20
|
||||
ALL_SNARK_ARTIFACTS=true
|
||||
REPORT_GAS=false
|
||||
BACKEND_PRIVATE_KEY=
|
||||
INFURA_API_KEY=
|
||||
COINMARKETCAP_API_KEY=
|
||||
ETHERSCAN_API_KEY=
|
||||
32
.eslintignore
Normal file
32
.eslintignore
Normal file
@@ -0,0 +1,32 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.yarn
|
||||
|
||||
# testing
|
||||
coverage
|
||||
coverage.json
|
||||
|
||||
# hardhat
|
||||
cache
|
||||
|
||||
# types
|
||||
types
|
||||
|
||||
# circuits
|
||||
circuits
|
||||
|
||||
# production
|
||||
dist
|
||||
build
|
||||
docs
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
24
.eslintrc.json
Normal file
24
.eslintrc.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"extends": ["airbnb-base", "airbnb-typescript/base", "plugin:jest/recommended", "plugin:jest/style", "prettier"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"project": ["./tsconfig.json", "./packages/**/tsconfig.json"]
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "jest"],
|
||||
"rules": {
|
||||
"no-underscore-dangle": "off",
|
||||
"import/no-extraneous-dependencies": "off",
|
||||
"no-bitwise": "off",
|
||||
"no-await-in-loop": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"no-console": ["warn", { "allow": ["info", "warn", "error"] }],
|
||||
"@typescript-eslint/lines-between-class-members": "off",
|
||||
"no-param-reassign": "off"
|
||||
}
|
||||
}
|
||||
18
.github/ISSUE_TEMPLATE/----network.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/----network.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: "\U0001F578️ Network"
|
||||
about: Propose a new network in which to deploy Semaphore contracts
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Provide data related to the network**
|
||||
|
||||
* Name
|
||||
* Chain id
|
||||
* URL
|
||||
|
||||
**Why are you using this network?**
|
||||
|
||||
Describe the reasons why you think it is important for Semaphore to be deployed on this network.
|
||||
22
.github/ISSUE_TEMPLATE/----project.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/----project.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: " \U0001F4A0 Project"
|
||||
about: If you are using Semaphore we can help you share your project
|
||||
title: ''
|
||||
labels: "documentation \U0001F4D6"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe your project**
|
||||
A brief description of your project. In what way have you used Semaphore?
|
||||
|
||||
**Other info**
|
||||
|
||||
- Name
|
||||
- Icon
|
||||
|
||||
**Links**
|
||||
|
||||
- Website
|
||||
- Github
|
||||
- Socials
|
||||
34
.github/ISSUE_TEMPLATE/---bug.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/---bug.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: "\U0001F41E Bug"
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: "bug \U0001F41B"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Technologies (please complete the following information):**
|
||||
|
||||
- Node.js version
|
||||
- NPM version
|
||||
- Solidity version
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
20
.github/ISSUE_TEMPLATE/---feature.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/---feature.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: "\U0001F680 Feature"
|
||||
about: Suggest an idea for Semaphore
|
||||
title: ''
|
||||
labels: 'feature :rocket:'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
14
.github/ISSUE_TEMPLATE/---package.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/---package.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: "\U0001F4E6 Package"
|
||||
about: Propose a new Semaphore package
|
||||
title: ''
|
||||
labels: 'feature :rocket:'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the package you'd like**
|
||||
A clear and concise description of the type of package you have in mind.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the package here.
|
||||
24
.github/pull_request_template.md
vendored
Normal file
24
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<!-- Please refer to our contributing documentation for any questions on submitting a pull request -->
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Related Issue
|
||||
|
||||
<!--- This project accepts pull requests related to open issues -->
|
||||
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
|
||||
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
|
||||
<!--- Please link to the issue here: -->
|
||||
|
||||
## Does this introduce a breaking change?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
<!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||
|
||||
## Other information
|
||||
|
||||
<!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
|
||||
45
.github/workflows/docs.yml
vendored
Normal file
45
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
gh-pages:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
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
|
||||
|
||||
- name: Generate doc website
|
||||
run: yarn docs
|
||||
|
||||
- name: Publish on Github Pages
|
||||
uses: crazy-max/ghaction-github-pages@v2.5.0
|
||||
with:
|
||||
build_dir: docs
|
||||
jekyll: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
107
.github/workflows/production.yml
vendored
Normal file
107
.github/workflows/production.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
TREE_DEPTH: 20
|
||||
ALL_SNARK_ARTIFACTS: false
|
||||
|
||||
jobs:
|
||||
style:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
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
|
||||
|
||||
- name: Compile contracts
|
||||
run: yarn compile:contracts
|
||||
|
||||
- name: Build libraries
|
||||
run: yarn build:libraries
|
||||
|
||||
- name: Run Prettier
|
||||
run: yarn prettier
|
||||
|
||||
- name: Run Eslint
|
||||
run: yarn lint
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
type:
|
||||
- libraries
|
||||
- contracts
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
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
|
||||
|
||||
- name: Build libraries
|
||||
run: yarn build:libraries
|
||||
|
||||
- name: Test contracts and libraries
|
||||
run: yarn test:${{ matrix.type }}
|
||||
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
flag-name: run-${{ matrix.type }}
|
||||
path-to-lcov: ./coverage/${{ matrix.type }}/lcov.info
|
||||
parallel: true
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- name: Coveralls Finished
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
parallel-finished: true
|
||||
81
.github/workflows/pull-requests.yml
vendored
Normal file
81
.github/workflows/pull-requests.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: pull-requests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TREE_DEPTH: 20
|
||||
ALL_SNARK_ARTIFACTS: false
|
||||
|
||||
jobs:
|
||||
style:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
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
|
||||
|
||||
- name: Compile contracts
|
||||
run: yarn compile:contracts
|
||||
|
||||
- name: Build libraries
|
||||
run: yarn build:libraries
|
||||
|
||||
- name: Run Prettier
|
||||
run: yarn prettier
|
||||
|
||||
- name: Run Eslint
|
||||
run: yarn lint
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
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
|
||||
|
||||
- name: Build libraries
|
||||
run: yarn build:libraries
|
||||
|
||||
- name: Test contracts and libraries
|
||||
run: yarn test
|
||||
90
.gitignore
vendored
Normal file
90
.gitignore
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
coverage.json
|
||||
*.lcov
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
.DS_Store
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# Production
|
||||
build
|
||||
dist
|
||||
docs/*
|
||||
!docs/CNAME
|
||||
!docs/index.html
|
||||
|
||||
# Hardhat
|
||||
artifacts
|
||||
cache
|
||||
packages/contracts/deployed-contracts/undefined.json
|
||||
packages/contracts/deployed-contracts/hardhat.json
|
||||
packages/contracts/deployed-contracts/localhost.json
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v3
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# Other
|
||||
snark-artifacts
|
||||
3
.lintstagedrc.json
Normal file
3
.lintstagedrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"**/*.{js,ts}": ["prettier --write", "eslint --fix"]
|
||||
}
|
||||
44
.prettierignore
Normal file
44
.prettierignore
Normal file
@@ -0,0 +1,44 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.yarn
|
||||
|
||||
# testing
|
||||
coverage
|
||||
coverage.json
|
||||
|
||||
# hardhat
|
||||
cache
|
||||
packages/contracts/deployed-contracts/undefined.json
|
||||
packages/contracts/deployed-contracts/hardhat.json
|
||||
packages/contracts/deployed-contracts/localhost.json
|
||||
|
||||
# types
|
||||
types
|
||||
|
||||
# circuits
|
||||
circuits
|
||||
|
||||
# contracts
|
||||
Verifier*.sol
|
||||
|
||||
# production
|
||||
dist
|
||||
build
|
||||
docs
|
||||
|
||||
# github
|
||||
.github/ISSUE_TEMPLATE
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# other
|
||||
snark-artifacts
|
||||
5
.prettierrc.json
Normal file
5
.prettierrc.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": false,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none"
|
||||
}
|
||||
28
.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
vendored
Normal file
28
.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id
vendored
Normal file
1
.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id
vendored
Normal file
@@ -0,0 +1 @@
|
||||
b3cadff6efb37a12712d12c2553ec703dbcaa4dd
|
||||
8
.yarnrc.yml
Normal file
8
.yarnrc.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
nodeLinker: node-modules
|
||||
checksumBehavior: update
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: "@yarnpkg/plugin-workspace-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.1.cjs
|
||||
127
CODE_OF_CONDUCT.md
Normal file
127
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
111
CONTRIBUTING.md
Normal file
111
CONTRIBUTING.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Contributing
|
||||
|
||||
:tada: Thank you for being interested in contributing to the Semaphore project! :tada:
|
||||
|
||||
Feel welcome and read the following sections in order to know how to ask questions and how to work on something.
|
||||
|
||||
All members of our community are expected to follow our [Code of Conduct](/CODE_OF_CONDUCT.md). Please make sure you are welcoming and friendly in all of our spaces.
|
||||
|
||||
We're really glad you're reading this, because we need volunteer developers to help this project come to fruition. 👏
|
||||
|
||||
## Issues
|
||||
|
||||
The best way to contribute to our projects is by opening a [new issue](https://github.com/semaphore-protocol/semaphore/issues/new/choose) or tackling one of the issues listed [here](https://github.com/semaphore-protocol/semaphore/contribute).
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Pull requests are great if you want to add a feature or fix a bug. Here's a quick guide:
|
||||
|
||||
1. Fork the repo.
|
||||
|
||||
2. Run the tests. We only take pull requests with passing tests.
|
||||
|
||||
3. Add a test for your change. Only refactoring and documentation changes require no new tests.
|
||||
|
||||
4. Make sure to check out the [Style Guide](/CONTRIBUTING#style-guide) and ensure that your code complies with the rules.
|
||||
|
||||
5. Make the test pass.
|
||||
|
||||
6. Commit your changes.
|
||||
|
||||
7. Push to your fork and submit a pull request on our `dev` branch. Please provide us with some explanation of why you made the changes you made. For new features make sure to explain a standard use case to us.
|
||||
|
||||
## CI (Github Actions) Tests
|
||||
|
||||
We use GitHub Actions to test each PR before it is merged.
|
||||
|
||||
When you submit your PR (or later change that code), a CI build will automatically be kicked off. A note will be added to the PR, and will indicate the current status of the build.
|
||||
|
||||
## Style Guide
|
||||
|
||||
### Code rules
|
||||
|
||||
We always use ESLint and Prettier. To check that your code follows the rules, simply run the npm script `yarn lint`.
|
||||
|
||||
### Commits rules
|
||||
|
||||
For commits it is recommended to use [Conventional Commits](https://www.conventionalcommits.org).
|
||||
|
||||
Don't worry if it looks complicated, in our repositories, after `git add`, you can usually run the npm script `yarn commit` to make many of these steps interactive.
|
||||
|
||||
Each commit message consists of a **header**, a **body** and a **footer**. The **header** has a special format that includes a **type**, a **scope** and a **subject**:
|
||||
|
||||
<type>(<scope>): <subject>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
|
||||
The **header** is mandatory and the **scope** of the header must contain the name of the package you are working on.
|
||||
|
||||
#### Type
|
||||
|
||||
The type must be one of the following:
|
||||
|
||||
- feat: A new feature
|
||||
- fix: A bug fix
|
||||
- docs: Documentation only changes
|
||||
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
- refactor: A code change that neither fixes a bug nor adds a feature (improvements of the code structure)
|
||||
- perf: A code change that improves the performance
|
||||
- test: Adding missing or correcting existing tests
|
||||
- build: Changes that affect the build system or external dependencies (example scopes: gulp, npm)
|
||||
- ci: Changes to CI configuration files and scripts (example scopes: travis, circle)
|
||||
- chore: Other changes that don't modify src or test files
|
||||
- revert: Reverts a previous commit
|
||||
|
||||
#### Scope
|
||||
|
||||
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages).
|
||||
|
||||
#### Subject
|
||||
|
||||
The subject contains a succinct description of the change:
|
||||
|
||||
- Use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
- Don't capitalize the first letter
|
||||
- No dot (.) at the end
|
||||
|
||||
#### Body
|
||||
|
||||
Just as in the subject, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior.
|
||||
|
||||
### Branch rules
|
||||
|
||||
- There must be a `main` branch, used only for the releases.
|
||||
- There must be a `dev` branch, used to merge all the branches under it.
|
||||
- Avoid long descriptive names for long-lived branches.
|
||||
- Use kebab-case (no CamelCase).
|
||||
- Use grouping tokens (words) at the beginning of your branch names (in a similar way to the `type` of commit).
|
||||
- Define and use short lead tokens to differentiate branches in a way that is meaningful to your workflow.
|
||||
- Use slashes to separate parts of your branch names.
|
||||
- Remove branch after merge if it is not important.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
git branch -b docs/readme
|
||||
git branch -b test/a-feature
|
||||
git branch -b feat/sidebar
|
||||
git branch -b fix/b-feature
|
||||
```
|
||||
21
LICENSE
Normal file
21
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.
|
||||
380
README.md
380
README.md
@@ -1,169 +1,307 @@
|
||||
# Semaphore
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/semaphore-protocol/website/blob/main/static/img/semaphore-icon-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/semaphore-protocol/website/blob/main/static/img/semaphore-icon.svg">
|
||||
<img width="40" alt="Semaphore icon." src="https://github.com/semaphore-protocol/website/blob/main/static/img/semaphore-icon.svg">
|
||||
</picture>
|
||||
Semaphore
|
||||
</h1>
|
||||
</p>
|
||||
|
||||
[](https://circleci.com/gh/kobigurk/semaphore)
|
||||
<p align="center">
|
||||
<a href="https://github.com/semaphore-protocol" target="_blank">
|
||||
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<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">
|
||||
</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">
|
||||
</a>
|
||||
<a href="https://deepscan.io/dashboard#view=project&tid=16502&pid=22324&bid=657461">
|
||||
<img src="https://deepscan.io/api/teams/16502/projects/22324/branches/657461/badge/grade.svg" alt="DeepScan grade">
|
||||
</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>
|
||||
<img alt="Repository top language" src="https://img.shields.io/github/languages/top/semaphore-protocol/semaphore?style=flat-square">
|
||||
</p>
|
||||
|
||||
Join the [Telegram group](https://t.me/joinchat/B-PQx1U3GtAh--Z4Fwo56A) to discuss.
|
||||
<div align="center">
|
||||
<h4>
|
||||
<a href="/CONTRIBUTING.md">
|
||||
👥 Contributing
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="/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://discord.gg/6mSdGHnstH">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
## Introduction
|
||||
| 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. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
Semaphore has been introduced by [barryWhiteHat](https://github.com/barryWhiteHat) as a method of zero-knowledge signaling - a method for an approved user to broadcast an arbitrary string without exposing their identity. This repository is an implementation of an upgraded version of the concept, including the zero-knowledge circuits and the tools necessary to use it, both server-side and client-side.
|
||||
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).
|
||||
|
||||
The project is implemented in plain Node.JS and uses [circom](https://github.com/iden3/circom) for the zero-knowledge proofs.
|
||||
You can find Semaphore V1 on [`version/1.0.0`](https://github.com/semaphore-protocol/semaphore/tree/version/1.0.0).
|
||||
|
||||
## Design
|
||||
Semaphore is comprised of a zkSNARK statement, a few smart contracts, a server application and a client application.
|
||||
---
|
||||
|
||||
### Smart contracts
|
||||
Implemented in [**semaphorejs/contracts**](semaphorejs/contracts).
|
||||
## 📦 Packages
|
||||
|
||||
#### Semaphore
|
||||
The Semaphore contract is the base layer of Semaphore. Other contracts can build upon this to create applications that rely on anonymous signaling. Semaphore has a tree of allowed identities, a tree of signals, a set of previously broadcast nullifiers hashes, an external nullifier and a gas price refund price:
|
||||
<table>
|
||||
<th>Package</th>
|
||||
<th>Version</th>
|
||||
<th>Downloads</th>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/contracts">
|
||||
@semaphore-protocol/contracts
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/contracts">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/contracts.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/contracts">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/contracts.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/identity">
|
||||
@semaphore-protocol/identity
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/identity">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/identity">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/identity.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/identity">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/identity.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/group">
|
||||
@semaphore-protocol/group
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/group">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/group">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/group.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/group">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/group.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/proof">
|
||||
@semaphore-protocol/proof
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/proof">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/proof">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/proof.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/proof">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/proof.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/subgraph">
|
||||
@semaphore-protocol/subgraph
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/subgraph">
|
||||
(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>
|
||||
</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>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/hardhat">
|
||||
@semaphore-protocol/hardhat
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/hardhat">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/hardhat.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/hardhat">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/hardhat.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
|
||||
* The tree of allowed identities allows a prover to show that they are an identity which is approved to signal.
|
||||
* The tree of signals allows a user to verify the integrity of a list of signals.
|
||||
* The nullifiers hashes set and external nullifier allows the contract to prevent double signals by the same user, without exposing the specific user.
|
||||
* The gas price refund price is a mechanism that supports transaction abstraction - a server can broadcast on behalf of a user to provide further anonymity, and in return they receive a refund and a small reward, with a maximum gas price for their transaction.
|
||||
</table>
|
||||
|
||||
The contract allows administrative operations that only the owner is allowed to perform:
|
||||
## 🛠 Install
|
||||
|
||||
* Managing identities using the **insertIdentity** and **updateIdentity** methods.
|
||||
* Setting the **external_nullifier**.
|
||||
* Setting the broadcast permissioning - whether only the owner can broadcast.
|
||||
Clone this repository:
|
||||
|
||||
The contract allows anyone to read the current state:
|
||||
```bash
|
||||
git clone https://github.com/semaphore-protocol/semaphore.git
|
||||
```
|
||||
|
||||
* Reading the roots of the two trees.
|
||||
* Reading the current parameters of **external_nullifier**.
|
||||
And install the dependencies:
|
||||
|
||||
The contract allows anyone to attempt broadcasting a signal, given a signal, a proof and the relevant public inputs.
|
||||
The contract allows anyone to fund the contract for gas refund and rewards.
|
||||
```bash
|
||||
cd semaphore && yarn
|
||||
```
|
||||
|
||||
Lastly, the contract has a few events to allow a server to build a local state to serve users wishing to generate proofs:
|
||||
## 📜 Usage
|
||||
|
||||
* **Funded** - when the contract has received some funding for refunds and rewards.
|
||||
* **SignalBroadcast** - when a signal has been broadcast successfully, after verification of the proof, the public inputs and double-signaling checks.
|
||||
* **LeafAdded**, **LeafUpdated** (from MerkleTreeLib) - when the trees have been updated.
|
||||
Copy the `.env.example` file as `.env`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
#### MerkleTreeLib
|
||||
And add your environment variables.
|
||||
|
||||
Manages a number of append-only Merkle trees with efficient inserts and updates.
|
||||
### Code quality and formatting
|
||||
|
||||
### zkSNARK statement
|
||||
Implemented in [**semaphorejs/snark**](semaphorejs/snark).
|
||||
Run [ESLint](https://eslint.org/) to analyze the code and catch bugs:
|
||||
|
||||
The statement assures that given public inputs:
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
* **signal_hash**
|
||||
* **external_nullifier**
|
||||
* **broadcaster_address**
|
||||
* **root**
|
||||
* **nullifiers_hash**
|
||||
Run [Prettier](https://prettier.io/) to check formatting rules:
|
||||
|
||||
and private inputs:
|
||||
* **identity_pk**
|
||||
* **identity_nullifier**
|
||||
* **identity_trapdoor**
|
||||
* **identity_path_elements**
|
||||
* **identity_path_index**
|
||||
* **auth_sig_r**
|
||||
* **auth_sig_s**
|
||||
```bash
|
||||
yarn prettier
|
||||
```
|
||||
|
||||
the following conditions hold:
|
||||
Or to automatically format the code:
|
||||
|
||||
* The commitment of the identity structure (**identity_pk**, **identity_nullifier**, **identity_trapdoor**) exists in the identity tree with the root **root**, using the path (**identity_path_elements**, **identity_path_index**). This ensures that the user was added to the system at some point in the past.
|
||||
* **nullifiers_hash** is uniquely derived from **external_nullifier**, **identity_nullifier** and **identity_path_index**. This ensures a user cannot broadcast a signal with the same **external_nullifier** more than once.
|
||||
* The message (**external_nullifier**, **signal_hash**, **broadcaster_address**) is signed by the secret key corresponding to **identity_pk**, having the signature (**auth_sig_r**, **auth_sig_s**). This ensures that the user approves the signal broadcast by a specific **broadcaster_address**, preventing front-running attacks, and a specific state of the contract having a specific **external_nullifier**, ensuring no double-signaling.
|
||||
```bash
|
||||
yarn prettier:write
|
||||
```
|
||||
|
||||
#### Cryptographic primitives
|
||||
### Conventional commits
|
||||
|
||||
Semaphore uses a few cryptographic primitives provided by circomlib:
|
||||
Semaphore uses [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). A [command line utility](https://github.com/commitizen/cz-cli) to commit using the correct syntax can be used by running:
|
||||
|
||||
* MiMCHash for the Merkle tree, the identity commitments and the message hash in the signature.
|
||||
* EdDSA for the signature.
|
||||
```bash
|
||||
yarn commit
|
||||
```
|
||||
|
||||
Note: MiMCHash, and especially the specific paramteres used in the circuit, have not been heavily audited yet by the cryptography community. Additionally, the circuit and code should also receive further review before relying on it for production applications.
|
||||
It will also automatically check that the modified files comply with ESLint and Prettier rules.
|
||||
|
||||
### Server
|
||||
### Snark artifacts
|
||||
|
||||
Implemented in [**semaphorejs/src/server/server.js**](semaphorejs/src/server/server.js). Acts as a manager of the identities merkle tree and as an identity onboarder. The REST API allows:
|
||||
Download the Semaphore snark artifacts needed to generate and verify proofs:
|
||||
|
||||
* An owner to submit a transaction that adds an identity to the merkle tree, provided proper authentication.
|
||||
* A client to ask for a path from an identity commitment to the current root of the tree, relieving the client from the need to manage this tree by themselves.
|
||||
* A client to ask a list of signals, together with their paths to the signals tree root.
|
||||
* An owner to set the external nullifier.
|
||||
```bash
|
||||
yarn download:snark-artifacts
|
||||
```
|
||||
|
||||
The server relies on an Ethereum node and the events in the smart contract to synchronize to the current state and handle rollbacks if they occur.
|
||||
### Testing
|
||||
|
||||
It uses [**semaphore-merkle-tree**](https://github.com/weijiekoh/semaphore-merkle-tree) - Semaphore requires managing a growing merkle tree containing the identities allowed to signal and the signals broadcast by users. semaphore-merkle-tree manages the trees either in-memory, for browser usage or a database, making the tree scale by the disk size.
|
||||
Run [Jest](https://jestjs.io/) to test the JS libraries:
|
||||
|
||||
### Client
|
||||
```bash
|
||||
yarn test:libraries
|
||||
```
|
||||
|
||||
Implemented in [**src/client/client.js**](semaphorejs/src/client/client.js). Enables signaling a user's support of an arbitrary statemnt, given identity secrets of an identity existing in the tree. The client has 2 CLI functions:
|
||||
Run [Mocha](https://mochajs.org/) to test the contracts:
|
||||
|
||||
* **generate_identity** - generate random identity secrets and randomness, save them to disk and print the identity commitment. The client can then send the commitment to the onboarder (using another channel), requesting they add them to the tree.
|
||||
* **signal STRING** - given an arbitrary string, generates a zero-knowledge proof of the client's authorization to signal. The signalling requests the path of the identity commitment from the server, and broadcasts the transaction directly to the contract.
|
||||
```bash
|
||||
yarn test:contracts
|
||||
```
|
||||
|
||||
### Web
|
||||
Or test everything with:
|
||||
|
||||
A web interface to run the server and client APIs, generate identities and proofs directly in the browser and broadcast signals.
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
## Running modes
|
||||
### Build libraries & compile contracts
|
||||
|
||||
Schematically, Semaphore has the following actors:
|
||||
Run [Rollup](https://www.rollupjs.org) to build all the packages:
|
||||
|
||||

|
||||
```bash
|
||||
yarn build:libraries
|
||||
```
|
||||
|
||||
There are 3 main running modes:
|
||||
Compile the smart contracts with [Hardhat](https://hardhat.org/):
|
||||
|
||||
* One server is the owner of the Semaphore contract, other servers act as intermediate miners and clients uses them to broadcast their signals. This the lightest running mode for clients, as they rely on servers to provide them with tree paths, a list of signals and broadcast. This is the default mode that is exposed in the CLI and web clients.
|
||||
* One server is the owner of the Semaphore contract and clients broadcast directly. This is still a light running mode for clients, and is less prone to server censorship, at the cost of less anonymity - as the user's Ethereum address is exposed.
|
||||
* One server is the owner of the Semaphore contract and clients run a server locally. This is a heavier running mode, allowing the clients to run autonomously, reconstructing the signals and identities states locally.
|
||||
```bash
|
||||
yarn compile:contracts
|
||||
```
|
||||
|
||||
## Configuration
|
||||
### Documentation (JS libraries)
|
||||
|
||||
The server and the client look for **server-config.json** and **client-config.json**, respectively. They can also accept their configuration as environment variables:
|
||||
* **server**:
|
||||
* CONFIG_ENV - load configuration from environment variables rather than a file.
|
||||
* CONFIG_PATH - location of the configuration file.
|
||||
* LOG_LEVEL - error, info, debug or verbose.
|
||||
* DB_PATH - location of the RocksDB database.
|
||||
* CHAIN_ID - chain ID of the Ethereum network.
|
||||
* CONTRACT_ADDRESS - the deployed Semaphore contract address.
|
||||
* CREATION_HASH - the transaction hash in which the contract was created, to allow for faster initial sync.
|
||||
* NODE_URL - the RPC URL of the Ehtereum node.
|
||||
* SEMAPHORE_PORT - the port on which to serve the Semaphore server REST API.
|
||||
* SEMAPHORE_LOGIN - the password with which clients communicating with the Semaphore server REST API must authenticate.
|
||||
* FROM_ADDRESS - the address to send transactions from.
|
||||
* FROM_PRIVATE_KEY - the private key of FROM_ADDRESS.
|
||||
* TRANSACTION_CONFIRMATION_BLOCKS - the amount of blocks to wait until a transaction is considered confirmed. The default is 24.
|
||||
* **client:**
|
||||
* CONFIG_ENV - load configuration from environment variables rather than a file.
|
||||
* LOG_LEVEL - error, info, debug or verbose.
|
||||
* IDENTITY_PATH - location of the identity secrets file.
|
||||
* CHAIN_ID - chain ID of the Ethereum network.
|
||||
* CONTRACT_ADDRESS - the deployed Semaphore contract address.
|
||||
* NODE_URL - the RPC URL of the Ehtereum node.
|
||||
* FROM_ADDRESS - the address to send transactions from.
|
||||
* FROM_PRIVATE_KEY - the private key of FROM_ADDRESS.
|
||||
* TRANSACTION_CONFIRMATION_BLOCKS - the amount of blocks to wait until a transaction is considered confirmed. The default is 24.
|
||||
* EXTERNAL_NULLIFIER - the external nullifier to be used with the signal. Must match the one in the contract.
|
||||
* SEMAPHORE_SERVER_URL - the URL of the Semaphore REST server.
|
||||
* BROADCASTER_ADDRESS - the address of the Semaphore server that will be allowed to broadcast the client's signals.
|
||||
Run [TypeDoc](https://typedoc.org/) to generate a documentation website for each package:
|
||||
|
||||
## Running
|
||||
```bash
|
||||
yarn docs
|
||||
```
|
||||
|
||||
The easiest way to try Semaphore out is to use [https://semaphore.kobi.one](https://semaphore.kobi.one) - a web interface to broadcast to a remote server and generate proofs locally. First, load the Rinkeby config using the button at the top. Then, you can generate an identity and send the commitment to @kobigurk on Telegram or open an issue in the repository. Then, you can broadcast signals, including the proof generation, directly in the browser. Lastly, you can see the signals that have been broadcast to date in the table.
|
||||
|
||||
* To try out Semaphore locally you can clone the repository and run the following in `semaphore/semaphorejs/`:
|
||||
* **npm install && npm link**
|
||||
* **cd scripts && ./compile.sh && ./do_setup.sh && ./build_verifier.sh** - compile, do a setup and build the verifier of the Semaphore circuit.
|
||||
|
||||
* **scripts/run_ganache.sh** - runs ganache with appropriate parameters for Semaphore testing.
|
||||
* **scripts/run_all_test.sh** - runs a server and a client, generates a new random identity and broadcasts a signal.
|
||||
|
||||
It assumes bash, node and truffle are globally available.
|
||||
|
||||
Examples of run commands (roughly matching the test contract deployed on Rinkeby):
|
||||
* `LOG_LEVEL=debug CHAIN_ID=4 CONTRACT_ADDRESS=0x3dE2c3f8853594440c3363f8D491449Defa0bE1F NODE_URL=https://rinkeby.infura.io/v3/f4a3ad81db3f4750bd201955c8d20066 SEMAPHORE_PORT=3000 FROM_ADDRESS=0x1929c15f4e818abf2549510622a50c440c474223 FROM_PRIVATE_KEY=0x6738837df169e8d6ffc6e33a2947e58096d644fa4aa6d74358c8d9d57c12cd21 TRANSACTION_CONFIRMATION_BLOCKS=1 CREATION_HASH=0x4d6998f49f3ebb6e2bd3567c5adbf3f5ab711fbb24e618b4b53498d521f9c758 SEMAPHORE_LOGIN=test123 CONFIG_ENV=true npx semaphorejs-server`
|
||||
* `LOG_LEVEL=debug TRANSACTION_CONFIRMATION_BLOCKS=1 CHAIN_ID=4 CONTRACT_ADDRESS=0x3dE2c3f8853594440c3363f8D491449Defa0bE1F NODE_URL=https://rinkeby.infura.io/v3/f4a3ad81db3f4750bd201955c8d20066 EXTERNAL_NULLIFIER=12312 SEMAPHORE_SERVER_URL=https://semaphore-server.kobi.one BROADCASTER_ADDRESS=0x1929c15f4e818abf2549510622a50c440c474223 CONFIG_ENV=true npx semaphorejs-client signal "I vote for fork A"`
|
||||
The output will be placed on the `docs` folder.
|
||||
|
||||
3
babel.config.json
Normal file
3
babel.config.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }], "@babel/preset-typescript"]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 25 KiB |
123
docs/index.html
Normal file
123
docs/index.html
Normal file
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html style="height: 100%">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="IE=edge" />
|
||||
<title>Semaphore packages</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="A monorepo of Semaphore packages."
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
style="
|
||||
margin: 0;
|
||||
background-color: #EAF0F4;
|
||||
color: #000;
|
||||
height: 100%;
|
||||
font-family: 'Courier New', monospace;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
padding: 0 20px;
|
||||
text-align: center;
|
||||
"
|
||||
>
|
||||
<div style="display: flex">
|
||||
<span style="margin-right: 5px">
|
||||
<img width="40" src="https://raw.githubusercontent.com/semaphore-protocol/website/main/static/img/semaphore-icon.svg">
|
||||
</span>
|
||||
<h1 style="margin: 0; font-size: 40px">Semaphore packages</h1>
|
||||
</div>
|
||||
<p style="max-width: 500px">
|
||||
A monorepo of Semaphore packages.
|
||||
</p>
|
||||
<ul style="list-style-type: none; padding: 0; margin: 0; margin-top: 10px"></ul>
|
||||
</div>
|
||||
<footer
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 15px 20px;
|
||||
background-color: #EAF0F4;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 900px;
|
||||
"
|
||||
>
|
||||
<p style="margin: 0; font-size: 16px">
|
||||
Copyright © 2022 Ethereum Foundation
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
style="margin-right: 15px; text-decoration: none"
|
||||
target="_blank"
|
||||
href="https://github.com/semaphore-protocol/semaphore"
|
||||
>
|
||||
<i
|
||||
class="fa fa-github"
|
||||
style="font-size: 24px; color: #000"
|
||||
></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
<script>
|
||||
const url =
|
||||
"https://api.github.com/repos/semaphore-protocol/semaphore/contents?ref=gh-pages"
|
||||
|
||||
function insertLinks(packages) {
|
||||
const [element] = window.document.getElementsByTagName("ul")
|
||||
let html = ""
|
||||
|
||||
for (const package of packages) {
|
||||
html += `<li style="display: flex; align-items: center; margin-bottom: 8px">
|
||||
<a style="margin-right: 15px" target="_blank" href="https://github.com/semaphore-protocol/semaphore/tree/main/packages/${package}">
|
||||
<i class="fa fa-github" style="font-size: 24px; color: #000"></i>
|
||||
</a>
|
||||
<a style="color: #000; text-decoration: none; font-size: 16px"
|
||||
onmouseover="this.style.color='#404A4E';"
|
||||
onmouseout="this.style.color='#000';"
|
||||
target="_blank" href="https://semaphore-protocol.github.io/semaphore/${package}">
|
||||
@semaphore-protocol/${package} >
|
||||
</a></li>`
|
||||
}
|
||||
|
||||
element.innerHTML = html
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
const ignore = [".nojekyll", "index.html", "CNAME"]
|
||||
const packages = data
|
||||
.map((c) => c.name)
|
||||
.filter((name) => !ignore.includes(name))
|
||||
|
||||
localStorage.setItem("packages", JSON.stringify(packages))
|
||||
|
||||
insertLinks(packages)
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
29
jest.config.ts
Normal file
29
jest.config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import fs from "fs"
|
||||
import type { Config } from "@jest/types"
|
||||
|
||||
const projects: any = fs
|
||||
.readdirSync("./packages", { withFileTypes: true })
|
||||
.filter((directory) => directory.isDirectory())
|
||||
.map(({ name }) => ({
|
||||
rootDir: `packages/${name}`,
|
||||
displayName: name,
|
||||
setupFiles: ["dotenv/config"],
|
||||
moduleNameMapper: {
|
||||
"@semaphore-protocol/(.*)": "<rootDir>/../$1/src/index.ts" // Interdependency packages.
|
||||
}
|
||||
}))
|
||||
|
||||
export default async (): Promise<Config.InitialOptions> => ({
|
||||
projects,
|
||||
verbose: true,
|
||||
coverageDirectory: "./coverage/libraries",
|
||||
collectCoverageFrom: ["<rootDir>/src/**/*.ts", "!<rootDir>/src/**/index.ts", "!<rootDir>/src/**/*.d.ts"],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 90,
|
||||
functions: 95,
|
||||
lines: 95,
|
||||
statements: 95
|
||||
}
|
||||
}
|
||||
})
|
||||
80
package.json
Normal file
80
package.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "semaphore-protocol",
|
||||
"description": "A zero-knowledge protocol for anonymous signalling on Ethereum.",
|
||||
"license": "MIT",
|
||||
"repository": "git@github.com:semaphore-protocol/semaphore.git",
|
||||
"homepage": "https://github.com/semaphore-protocol/semaphore",
|
||||
"bugs": "https://github.com/semaphore-protocol/semaphore/issues",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:libraries": "yarn workspaces foreach 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",
|
||||
"test:libraries": "jest --coverage",
|
||||
"test:contracts": "yarn workspace contracts test:coverage",
|
||||
"lint": "eslint . --ext .js,.ts && yarn workspace contracts lint",
|
||||
"prettier": "prettier -c .",
|
||||
"prettier:write": "prettier -w .",
|
||||
"docs": "yarn workspaces foreach run docs",
|
||||
"commit": "cz",
|
||||
"precommit": "lint-staged",
|
||||
"postinstall": "yarn download:snark-artifacts"
|
||||
},
|
||||
"keywords": [
|
||||
"ethereum",
|
||||
"semaphore",
|
||||
"solidity",
|
||||
"circom",
|
||||
"javascript",
|
||||
"typescript",
|
||||
"zero-knowledge",
|
||||
"zk-snarks",
|
||||
"zero-knowledge-proofs",
|
||||
"proof-of-membership",
|
||||
"monorepo"
|
||||
],
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"packageManager": "yarn@3.2.1",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.7",
|
||||
"@babel/preset-env": "^7.16.8",
|
||||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@commitlint/cli": "^16.0.2",
|
||||
"@commitlint/config-conventional": "^16.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@types/download": "^8.0.1",
|
||||
"@types/glob": "^7.2.0",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@types/node": "^17.0.9",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||
"@typescript-eslint/parser": "^5.9.1",
|
||||
"babel-jest": "^27.4.6",
|
||||
"commitizen": "^4.2.4",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"dotenv": "^16.0.2",
|
||||
"eslint": "^8.2.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-jest": "^25.7.0",
|
||||
"jest": "^27.4.1",
|
||||
"jest-config": "^27.4.7",
|
||||
"lint-staged": "^12.1.7",
|
||||
"prettier": "^2.5.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.64.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "./node_modules/cz-conventional-changelog"
|
||||
}
|
||||
}
|
||||
}
|
||||
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://discord.gg/6mSdGHnstH">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
To learn more about circuits visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org/docs/technical-reference/circuits).
|
||||
7
packages/circuits/package.json
Normal file
7
packages/circuits/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "circuits",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"circomlib": "^2.0.2"
|
||||
}
|
||||
}
|
||||
BIN
packages/circuits/scheme.png
Normal file
BIN
packages/circuits/scheme.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
90
packages/circuits/semaphore.circom
Normal file
90
packages/circuits/semaphore.circom
Normal file
@@ -0,0 +1,90 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "../node_modules/circomlib/circuits/poseidon.circom";
|
||||
include "./tree.circom";
|
||||
|
||||
template CalculateSecret() {
|
||||
signal input identityNullifier;
|
||||
signal input identityTrapdoor;
|
||||
|
||||
signal output out;
|
||||
|
||||
component poseidon = Poseidon(2);
|
||||
|
||||
poseidon.inputs[0] <== identityNullifier;
|
||||
poseidon.inputs[1] <== identityTrapdoor;
|
||||
|
||||
out <== poseidon.out;
|
||||
}
|
||||
|
||||
template CalculateIdentityCommitment() {
|
||||
signal input secret;
|
||||
|
||||
signal output out;
|
||||
|
||||
component poseidon = Poseidon(1);
|
||||
|
||||
poseidon.inputs[0] <== secret;
|
||||
|
||||
out <== poseidon.out;
|
||||
}
|
||||
|
||||
template CalculateNullifierHash() {
|
||||
signal input externalNullifier;
|
||||
signal input identityNullifier;
|
||||
|
||||
signal output out;
|
||||
|
||||
component poseidon = Poseidon(2);
|
||||
|
||||
poseidon.inputs[0] <== externalNullifier;
|
||||
poseidon.inputs[1] <== identityNullifier;
|
||||
|
||||
out <== poseidon.out;
|
||||
}
|
||||
|
||||
// nLevels must be < 32.
|
||||
template Semaphore(nLevels) {
|
||||
signal input identityNullifier;
|
||||
signal input identityTrapdoor;
|
||||
signal input treePathIndices[nLevels];
|
||||
signal input treeSiblings[nLevels];
|
||||
|
||||
signal input signalHash;
|
||||
signal input externalNullifier;
|
||||
|
||||
signal output root;
|
||||
signal output nullifierHash;
|
||||
|
||||
component calculateSecret = CalculateSecret();
|
||||
calculateSecret.identityNullifier <== identityNullifier;
|
||||
calculateSecret.identityTrapdoor <== identityTrapdoor;
|
||||
|
||||
signal secret;
|
||||
secret <== calculateSecret.out;
|
||||
|
||||
component calculateIdentityCommitment = CalculateIdentityCommitment();
|
||||
calculateIdentityCommitment.secret <== secret;
|
||||
|
||||
component calculateNullifierHash = CalculateNullifierHash();
|
||||
calculateNullifierHash.externalNullifier <== externalNullifier;
|
||||
calculateNullifierHash.identityNullifier <== identityNullifier;
|
||||
|
||||
component inclusionProof = MerkleTreeInclusionProof(nLevels);
|
||||
inclusionProof.leaf <== calculateIdentityCommitment.out;
|
||||
|
||||
for (var i = 0; i < nLevels; i++) {
|
||||
inclusionProof.siblings[i] <== treeSiblings[i];
|
||||
inclusionProof.pathIndices[i] <== treePathIndices[i];
|
||||
}
|
||||
|
||||
root <== inclusionProof.root;
|
||||
|
||||
// Dummy square to prevent tampering signalHash.
|
||||
signal signalHashSquared;
|
||||
signalHashSquared <== signalHash * signalHash;
|
||||
|
||||
nullifierHash <== calculateNullifierHash.out;
|
||||
}
|
||||
|
||||
component main {public [signalHash, externalNullifier]} = Semaphore(20);
|
||||
40
packages/circuits/tree.circom
Normal file
40
packages/circuits/tree.circom
Normal file
@@ -0,0 +1,40 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "../node_modules/circomlib/circuits/poseidon.circom";
|
||||
include "../node_modules/circomlib/circuits/mux1.circom";
|
||||
|
||||
template MerkleTreeInclusionProof(nLevels) {
|
||||
signal input leaf;
|
||||
signal input pathIndices[nLevels];
|
||||
signal input siblings[nLevels];
|
||||
|
||||
signal output root;
|
||||
|
||||
component poseidons[nLevels];
|
||||
component mux[nLevels];
|
||||
|
||||
signal hashes[nLevels + 1];
|
||||
hashes[0] <== leaf;
|
||||
|
||||
for (var i = 0; i < nLevels; i++) {
|
||||
pathIndices[i] * (1 - pathIndices[i]) === 0;
|
||||
|
||||
poseidons[i] = Poseidon(2);
|
||||
mux[i] = MultiMux1(2);
|
||||
|
||||
mux[i].c[0][0] <== hashes[i];
|
||||
mux[i].c[0][1] <== siblings[i];
|
||||
|
||||
mux[i].c[1][0] <== siblings[i];
|
||||
mux[i].c[1][1] <== hashes[i];
|
||||
|
||||
mux[i].s <== pathIndices[i];
|
||||
|
||||
poseidons[i].inputs[0] <== mux[i].out[0];
|
||||
poseidons[i].inputs[1] <== mux[i].out[1];
|
||||
|
||||
hashes[i + 1] <== poseidons[i].out;
|
||||
}
|
||||
|
||||
root <== hashes[nLevels];
|
||||
}
|
||||
4
packages/contracts/.solcover.js
Normal file
4
packages/contracts/.solcover.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
istanbulFolder: "../../coverage/contracts",
|
||||
skipFiles: ["base/Pairing.sol"]
|
||||
}
|
||||
22
packages/contracts/.solhint.json
Normal file
22
packages/contracts/.solhint.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "solhint:recommended",
|
||||
"plugins": ["prettier"],
|
||||
"rules": {
|
||||
"code-complexity": ["error", 7],
|
||||
"compiler-version": ["error", ">=0.8.0"],
|
||||
"var-name-mixedcase": "off",
|
||||
"const-name-snakecase": "off",
|
||||
"no-empty-blocks": "off",
|
||||
"constructor-syntax": "error",
|
||||
"func-visibility": ["error", { "ignoreConstructors": true }],
|
||||
"max-line-length": ["error", 120],
|
||||
"not-rely-on-time": "off",
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
],
|
||||
"reason-string": ["warn", { "maxLength": 80 }]
|
||||
}
|
||||
}
|
||||
1
packages/contracts/.solhintignore
Normal file
1
packages/contracts/.solhintignore
Normal file
@@ -0,0 +1 @@
|
||||
contracts/base/Pairing.sol
|
||||
1
packages/contracts/README.md
Symbolic link
1
packages/contracts/README.md
Symbolic link
@@ -0,0 +1 @@
|
||||
contracts/README.md
|
||||
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.
|
||||
120
packages/contracts/contracts/README.md
Normal file
120
packages/contracts/contracts/README.md
Normal file
@@ -0,0 +1,120 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Semaphore contracts
|
||||
</h1>
|
||||
<p align="center">Semaphore contracts to manage groups and broadcast anonymous signals.</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/contracts">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/contracts?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/contracts">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/contracts.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://discord.gg/6mSdGHnstH">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
To learn more about contracts visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org/docs/technical-reference/contracts).
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@semaphore-protocol/contracts` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @semaphore-protocol/contracts
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @semaphore-protocol/contracts
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
### Compile contracts
|
||||
|
||||
Compile the smart contracts with [Hardhat](https://hardhat.org/):
|
||||
|
||||
```bash
|
||||
yarn compile
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Run [Mocha](https://mochajs.org/) to test the contracts:
|
||||
|
||||
```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 contracts
|
||||
|
||||
Deploy a verifier contract with depth = 20:
|
||||
|
||||
```bash
|
||||
yarn deploy:verifier --depth 20
|
||||
```
|
||||
|
||||
Deploy the `Semaphore.sol` contract with one verifier:
|
||||
|
||||
```bash
|
||||
yarn deploy:semaphore --verifiers '[{"merkleTreeDepth": 20, "contractAddress": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1"}]'
|
||||
```
|
||||
|
||||
Deploy all verifiers and Semaphore contract:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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.
|
||||
174
packages/contracts/contracts/Semaphore.sol
Normal file
174
packages/contracts/contracts/Semaphore.sol
Normal file
@@ -0,0 +1,174 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import "./interfaces/ISemaphore.sol";
|
||||
import "./interfaces/ISemaphoreVerifier.sol";
|
||||
import "./base/SemaphoreGroups.sol";
|
||||
|
||||
/// @title Semaphore
|
||||
contract Semaphore is ISemaphore, SemaphoreGroups {
|
||||
ISemaphoreVerifier public verifier;
|
||||
|
||||
/// @dev Gets a group id and returns the group parameters.
|
||||
mapping(uint256 => Group) public groups;
|
||||
|
||||
/// @dev Checks if the group admin is the transaction sender.
|
||||
/// @param groupId: Id of the group.
|
||||
modifier onlyGroupAdmin(uint256 groupId) {
|
||||
if (groups[groupId].admin != _msgSender()) {
|
||||
revert Semaphore__CallerIsNotTheGroupAdmin();
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev Checks if there is a verifier for the given tree depth.
|
||||
/// @param merkleTreeDepth: Depth of the tree.
|
||||
modifier onlySupportedMerkleTreeDepth(uint256 merkleTreeDepth) {
|
||||
if (merkleTreeDepth < 16 || merkleTreeDepth > 32) {
|
||||
revert Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev Initializes the Semaphore verifier used to verify the user's ZK proofs.
|
||||
/// @param _verifier: Semaphore verifier address.
|
||||
constructor(ISemaphoreVerifier _verifier) {
|
||||
verifier = _verifier;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-createGroup}.
|
||||
function createGroup(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeDepth,
|
||||
uint256 zeroValue,
|
||||
address admin
|
||||
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
|
||||
_createGroup(groupId, merkleTreeDepth, zeroValue);
|
||||
|
||||
groups[groupId].admin = admin;
|
||||
groups[groupId].merkleRootDuration = 1 hours;
|
||||
|
||||
emit GroupAdminUpdated(groupId, address(0), admin);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-createGroup}.
|
||||
function createGroup(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeDepth,
|
||||
uint256 zeroValue,
|
||||
address admin,
|
||||
uint256 merkleTreeRootDuration
|
||||
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
|
||||
_createGroup(groupId, merkleTreeDepth, zeroValue);
|
||||
|
||||
groups[groupId].admin = admin;
|
||||
groups[groupId].merkleRootDuration = merkleTreeRootDuration;
|
||||
|
||||
emit GroupAdminUpdated(groupId, address(0), admin);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-updateGroupAdmin}.
|
||||
function updateGroupAdmin(uint256 groupId, address newAdmin) external override onlyGroupAdmin(groupId) {
|
||||
groups[groupId].admin = newAdmin;
|
||||
|
||||
emit GroupAdminUpdated(groupId, _msgSender(), newAdmin);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-addMember}.
|
||||
function addMember(uint256 groupId, uint256 identityCommitment) external override onlyGroupAdmin(groupId) {
|
||||
_addMember(groupId, identityCommitment);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
|
||||
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-addMembers}.
|
||||
function addMembers(uint256 groupId, uint256[] calldata identityCommitments)
|
||||
external
|
||||
override
|
||||
onlyGroupAdmin(groupId)
|
||||
{
|
||||
for (uint8 i = 0; i < identityCommitments.length; ) {
|
||||
_addMember(groupId, identityCommitments[i]);
|
||||
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
|
||||
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-updateMember}.
|
||||
function updateMember(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256 newIdentityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) external override onlyGroupAdmin(groupId) {
|
||||
_updateMember(groupId, identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
|
||||
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-removeMember}.
|
||||
function removeMember(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) external override onlyGroupAdmin(groupId) {
|
||||
_removeMember(groupId, identityCommitment, proofSiblings, proofPathIndices);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
|
||||
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-verifyProof}.
|
||||
function verifyProof(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 signal,
|
||||
uint256 nullifierHash,
|
||||
uint256 externalNullifier,
|
||||
uint256[8] calldata proof
|
||||
) external override {
|
||||
uint256 currentMerkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
|
||||
if (currentMerkleTreeRoot == 0) {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
if (merkleTreeRoot != currentMerkleTreeRoot) {
|
||||
uint256 merkleRootCreationDate = groups[groupId].merkleRootCreationDates[merkleTreeRoot];
|
||||
uint256 merkleRootDuration = groups[groupId].merkleRootDuration;
|
||||
|
||||
if (merkleRootCreationDate == 0) {
|
||||
revert Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
|
||||
}
|
||||
|
||||
if (block.timestamp > merkleRootCreationDate + merkleRootDuration) {
|
||||
revert Semaphore__MerkleTreeRootIsExpired();
|
||||
}
|
||||
}
|
||||
|
||||
if (groups[groupId].nullifierHashes[nullifierHash]) {
|
||||
revert Semaphore__YouAreUsingTheSameNillifierTwice();
|
||||
}
|
||||
|
||||
uint256 merkleTreeDepth = getMerkleTreeDepth(groupId);
|
||||
|
||||
verifier.verifyProof(merkleTreeRoot, nullifierHash, signal, externalNullifier, proof, merkleTreeDepth);
|
||||
|
||||
groups[groupId].nullifierHashes[nullifierHash] = true;
|
||||
|
||||
emit ProofVerified(groupId, merkleTreeRoot, nullifierHash, externalNullifier, signal);
|
||||
}
|
||||
}
|
||||
151
packages/contracts/contracts/base/Pairing.sol
Normal file
151
packages/contracts/contracts/base/Pairing.sol
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2017 Christian Reitwiessner
|
||||
// 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.
|
||||
//
|
||||
// The following Pairing library is a modified version adapted to Semaphore.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
library Pairing {
|
||||
error Semaphore__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.
|
||||
uint256 constant SCALAR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
|
||||
struct G1Point {
|
||||
uint256 X;
|
||||
uint256 Y;
|
||||
}
|
||||
|
||||
// Encoding of field elements is: X[0] * z + X[1]
|
||||
struct G2Point {
|
||||
uint256[2] X;
|
||||
uint256[2] Y;
|
||||
}
|
||||
|
||||
/// @return the generator of G1
|
||||
function P1() public pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() public pure returns (G2Point memory) {
|
||||
return
|
||||
G2Point(
|
||||
[
|
||||
11559732032986387107991004021392285783925812861821192530917403151452391805634,
|
||||
10857046999023057135944570762232829481370756359578518086990519993285655852781
|
||||
],
|
||||
[
|
||||
4082367875863433681332203403145435568316851327593401208105741076214120093531,
|
||||
8495653923123431417604973247489272438418190587263600148770280649306958101930
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// @return r the negation of p, i.e. p.addition(p.negate()) should be zero.
|
||||
function negate(G1Point memory p) public pure returns (G1Point memory r) {
|
||||
if (p.X == 0 && p.Y == 0) {
|
||||
return G1Point(0, 0);
|
||||
}
|
||||
|
||||
// Validate input or revert
|
||||
if (p.X >= BASE_MODULUS || p.Y >= BASE_MODULUS) {
|
||||
revert Semaphore__InvalidProof();
|
||||
}
|
||||
|
||||
// We know p.Y > 0 and p.Y < BASE_MODULUS.
|
||||
return G1Point(p.X, BASE_MODULUS - p.Y);
|
||||
}
|
||||
|
||||
/// @return r the sum of two points of G1
|
||||
function addition(G1Point memory p1, G1Point memory p2) public view returns (G1Point memory r) {
|
||||
// By EIP-196 all input is validated to be less than the BASE_MODULUS and form points
|
||||
// on the curve.
|
||||
uint256[4] memory input;
|
||||
|
||||
input[0] = p1.X;
|
||||
input[1] = p1.Y;
|
||||
input[2] = p2.X;
|
||||
input[3] = p2.Y;
|
||||
|
||||
bool success;
|
||||
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
revert Semaphore__InvalidProof();
|
||||
}
|
||||
}
|
||||
|
||||
/// @return r the product of a point on G1 and a scalar, i.e.
|
||||
/// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p.
|
||||
function scalar_mul(G1Point memory p, uint256 s) public view returns (G1Point memory r) {
|
||||
// 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();
|
||||
}
|
||||
|
||||
uint256[3] memory input;
|
||||
|
||||
input[0] = p.X;
|
||||
input[1] = p.Y;
|
||||
input[2] = s;
|
||||
|
||||
bool success;
|
||||
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
revert Semaphore__InvalidProof();
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts the pairing check
|
||||
/// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
|
||||
/// For example pairing([P1(), P1().negate()], [P2(), P2()]) should succeed
|
||||
function pairingCheck(G1Point[] memory p1, G2Point[] memory p2) public view {
|
||||
// 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();
|
||||
}
|
||||
|
||||
uint256 elements = p1.length;
|
||||
uint256 inputSize = elements * 6;
|
||||
uint256[] memory input = new uint256[](inputSize);
|
||||
|
||||
for (uint256 i = 0; i < elements; i++) {
|
||||
input[i * 6 + 0] = p1[i].X;
|
||||
input[i * 6 + 1] = p1[i].Y;
|
||||
input[i * 6 + 2] = p2[i].X[0];
|
||||
input[i * 6 + 3] = p2[i].X[1];
|
||||
input[i * 6 + 4] = p2[i].Y[0];
|
||||
input[i * 6 + 5] = p2[i].Y[1];
|
||||
}
|
||||
|
||||
uint256[1] memory out;
|
||||
bool success;
|
||||
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
|
||||
}
|
||||
|
||||
if (!success || out[0] != 1) {
|
||||
revert Semaphore__InvalidProof();
|
||||
}
|
||||
}
|
||||
}
|
||||
138
packages/contracts/contracts/base/SemaphoreGroups.sol
Normal file
138
packages/contracts/contracts/base/SemaphoreGroups.sol
Normal file
@@ -0,0 +1,138 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
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.
|
||||
/// 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;
|
||||
|
||||
/// @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 (getMerkleTreeDepth(groupId) != 0) {
|
||||
revert Semaphore__GroupAlreadyExists();
|
||||
}
|
||||
|
||||
merkleTree[groupId].init(merkleTreeDepth, zeroValue);
|
||||
|
||||
emit GroupCreated(groupId, merkleTreeDepth, zeroValue);
|
||||
}
|
||||
|
||||
/// @dev Adds an identity commitment to an existing group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param identityCommitment: New identity commitment.
|
||||
function _addMember(uint256 groupId, uint256 identityCommitment) internal virtual {
|
||||
if (getMerkleTreeDepth(groupId) == 0) {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
merkleTree[groupId].insert(identityCommitment);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
uint256 index = getNumberOfMerkleTreeLeaves(groupId) - 1;
|
||||
|
||||
emit MemberAdded(groupId, index, identityCommitment, merkleTreeRoot);
|
||||
}
|
||||
|
||||
/// @dev Updates an identity commitment of an existing group. A proof of membership is
|
||||
/// needed to check if the node to be updated is part of the tree.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param identityCommitment: Existing identity commitment to be updated.
|
||||
/// @param newIdentityCommitment: New identity commitment.
|
||||
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
|
||||
/// @param proofPathIndices: Path of the proof of membership.
|
||||
function _updateMember(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256 newIdentityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) internal virtual {
|
||||
if (getMerkleTreeRoot(groupId) == 0) {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
merkleTree[groupId].update(identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
|
||||
|
||||
emit MemberUpdated(groupId, index, identityCommitment, newIdentityCommitment, merkleTreeRoot);
|
||||
}
|
||||
|
||||
/// @dev Removes an identity commitment from an existing group. A proof of membership is
|
||||
/// needed to check if the node to be deleted is part of the tree.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param identityCommitment: Existing identity commitment to be removed.
|
||||
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
|
||||
/// @param proofPathIndices: Path of the proof of membership.
|
||||
function _removeMember(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) internal virtual {
|
||||
if (getMerkleTreeRoot(groupId) == 0) {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
merkleTree[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
|
||||
|
||||
emit MemberRemoved(groupId, index, identityCommitment, merkleTreeRoot);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreGroups-getMerkleTreeRoot}.
|
||||
function getMerkleTreeRoot(uint256 groupId) public view virtual override returns (uint256) {
|
||||
return merkleTree[groupId].root;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreGroups-getMerkleTreeDepth}.
|
||||
function getMerkleTreeDepth(uint256 groupId) public view virtual override returns (uint256) {
|
||||
return merkleTree[groupId].depth;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreGroups-getNumberOfMerkleTreeLeaves}.
|
||||
function getNumberOfMerkleTreeLeaves(uint256 groupId) public view virtual override returns (uint256) {
|
||||
return merkleTree[groupId].numberOfLeaves;
|
||||
}
|
||||
|
||||
/// @dev Converts the path indices of a Merkle proof to the identity commitment index in the tree.
|
||||
/// @param proofPathIndices: Path of the proof of membership.
|
||||
/// @return Index of a group member.
|
||||
function proofPathIndicesToMemberIndex(uint8[] calldata proofPathIndices) private pure returns (uint256) {
|
||||
uint256 memberIndex = 0;
|
||||
|
||||
for (uint8 i = uint8(proofPathIndices.length); i > 0; ) {
|
||||
if (memberIndex > 0 || proofPathIndices[i - 1] != 0) {
|
||||
memberIndex *= 2;
|
||||
|
||||
if (proofPathIndices[i - 1] == 1) {
|
||||
memberIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
unchecked {
|
||||
--i;
|
||||
}
|
||||
}
|
||||
|
||||
return memberIndex;
|
||||
}
|
||||
}
|
||||
113
packages/contracts/contracts/base/SemaphoreVerifier.sol
Normal file
113
packages/contracts/contracts/base/SemaphoreVerifier.sol
Normal file
File diff suppressed because one or more lines are too long
114
packages/contracts/contracts/extensions/SemaphoreVoting.sol
Normal file
114
packages/contracts/contracts/extensions/SemaphoreVoting.sol
Normal file
@@ -0,0 +1,114 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import "../interfaces/ISemaphoreVoting.sol";
|
||||
import "../interfaces/ISemaphoreVerifier.sol";
|
||||
import "../base/SemaphoreGroups.sol";
|
||||
|
||||
/// @title Semaphore voting contract.
|
||||
/// @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;
|
||||
|
||||
/// @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) {
|
||||
if (polls[pollId].coordinator != _msgSender()) {
|
||||
revert Semaphore__CallerIsNotThePollCoordinator();
|
||||
}
|
||||
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev Initializes the Semaphore verifier used to verify the user's ZK proofs.
|
||||
/// @param _verifier: Semaphore verifier address.
|
||||
constructor(ISemaphoreVerifier _verifier) {
|
||||
verifier = _verifier;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreVoting-createPoll}.
|
||||
function createPoll(
|
||||
uint256 pollId,
|
||||
address coordinator,
|
||||
uint256 merkleTreeDepth
|
||||
) public override {
|
||||
if (merkleTreeDepth < 16 || merkleTreeDepth > 32) {
|
||||
revert Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
}
|
||||
|
||||
_createGroup(pollId, merkleTreeDepth, 0);
|
||||
|
||||
Poll memory poll;
|
||||
|
||||
poll.coordinator = coordinator;
|
||||
|
||||
polls[pollId] = poll;
|
||||
|
||||
emit PollCreated(pollId, coordinator);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreVoting-addVoter}.
|
||||
function addVoter(uint256 pollId, uint256 identityCommitment) public override onlyCoordinator(pollId) {
|
||||
if (polls[pollId].state != PollState.Created) {
|
||||
revert Semaphore__PollHasAlreadyBeenStarted();
|
||||
}
|
||||
|
||||
_addMember(pollId, identityCommitment);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreVoting-addVoter}.
|
||||
function startPoll(uint256 pollId, uint256 encryptionKey) public override onlyCoordinator(pollId) {
|
||||
if (polls[pollId].state != PollState.Created) {
|
||||
revert Semaphore__PollHasAlreadyBeenStarted();
|
||||
}
|
||||
|
||||
polls[pollId].state = PollState.Ongoing;
|
||||
|
||||
emit PollStarted(pollId, _msgSender(), encryptionKey);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreVoting-castVote}.
|
||||
function castVote(
|
||||
uint256 vote,
|
||||
uint256 nullifierHash,
|
||||
uint256 pollId,
|
||||
uint256[8] calldata proof
|
||||
) public override {
|
||||
Poll memory poll = polls[pollId];
|
||||
|
||||
if (poll.state != PollState.Ongoing) {
|
||||
revert Semaphore__PollIsNotOngoing();
|
||||
}
|
||||
|
||||
if (nullifierHashes[nullifierHash]) {
|
||||
revert Semaphore__YouAreUsingTheSameNillifierTwice();
|
||||
}
|
||||
|
||||
uint256 merkleTreeDepth = getMerkleTreeDepth(pollId);
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(pollId);
|
||||
|
||||
verifier.verifyProof(merkleTreeRoot, nullifierHash, vote, pollId, proof, merkleTreeDepth);
|
||||
|
||||
nullifierHashes[nullifierHash] = true;
|
||||
|
||||
emit VoteAdded(pollId, vote);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreVoting-publishDecryptionKey}.
|
||||
function endPoll(uint256 pollId, uint256 decryptionKey) public override onlyCoordinator(pollId) {
|
||||
if (polls[pollId].state != PollState.Ongoing) {
|
||||
revert Semaphore__PollIsNotOngoing();
|
||||
}
|
||||
|
||||
polls[pollId].state = PollState.Ended;
|
||||
|
||||
emit PollEnded(pollId, _msgSender(), decryptionKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import "../interfaces/ISemaphoreWhistleblowing.sol";
|
||||
import "../interfaces/ISemaphoreVerifier.sol";
|
||||
import "../base/SemaphoreGroups.sol";
|
||||
|
||||
/// @title Semaphore whistleblowing contract.
|
||||
/// @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.
|
||||
contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreGroups {
|
||||
ISemaphoreVerifier public verifier;
|
||||
|
||||
/// @dev Gets an editor address and return their entity.
|
||||
mapping(address => uint256) 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()]) {
|
||||
revert Semaphore__CallerIsNotTheEditor();
|
||||
}
|
||||
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev Initializes the Semaphore verifier used to verify the user's ZK proofs.
|
||||
/// @param _verifier: Semaphore verifier address.
|
||||
constructor(ISemaphoreVerifier _verifier) {
|
||||
verifier = _verifier;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreWhistleblowing-createEntity}.
|
||||
function createEntity(
|
||||
uint256 entityId,
|
||||
address editor,
|
||||
uint256 merkleTreeDepth
|
||||
) public override {
|
||||
if (merkleTreeDepth < 16 || merkleTreeDepth > 32) {
|
||||
revert Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
}
|
||||
|
||||
_createGroup(entityId, merkleTreeDepth, 0);
|
||||
|
||||
entities[editor] = entityId;
|
||||
|
||||
emit EntityCreated(entityId, editor);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreWhistleblowing-addWhistleblower}.
|
||||
function addWhistleblower(uint256 entityId, uint256 identityCommitment) public override onlyEditor(entityId) {
|
||||
_addMember(entityId, identityCommitment);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreWhistleblowing-removeWhistleblower}.
|
||||
function removeWhistleblower(
|
||||
uint256 entityId,
|
||||
uint256 identityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) public override onlyEditor(entityId) {
|
||||
_removeMember(entityId, identityCommitment, proofSiblings, proofPathIndices);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreWhistleblowing-publishLeak}.
|
||||
function publishLeak(
|
||||
uint256 leak,
|
||||
uint256 nullifierHash,
|
||||
uint256 entityId,
|
||||
uint256[8] calldata proof
|
||||
) public override {
|
||||
uint256 merkleTreeDepth = getMerkleTreeDepth(entityId);
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(entityId);
|
||||
|
||||
verifier.verifyProof(merkleTreeRoot, nullifierHash, leak, entityId, proof, merkleTreeDepth);
|
||||
|
||||
emit LeakPublished(entityId, leak);
|
||||
}
|
||||
}
|
||||
131
packages/contracts/contracts/interfaces/ISemaphore.sol
Normal file
131
packages/contracts/contracts/interfaces/ISemaphore.sol
Normal file
@@ -0,0 +1,131 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title Semaphore interface.
|
||||
/// @dev Interface of a Semaphore contract.
|
||||
interface ISemaphore {
|
||||
error Semaphore__CallerIsNotTheGroupAdmin();
|
||||
error Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
error Semaphore__MerkleTreeRootIsExpired();
|
||||
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;
|
||||
mapping(uint256 => uint256) merkleRootCreationDates;
|
||||
mapping(uint256 => bool) nullifierHashes;
|
||||
}
|
||||
|
||||
/// @dev Emitted when an admin is assigned to a group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param oldAdmin: Old admin of the group.
|
||||
/// @param newAdmin: New admin of the group.
|
||||
event GroupAdminUpdated(uint256 indexed groupId, address indexed oldAdmin, address indexed newAdmin);
|
||||
|
||||
/// @dev Emitted when a Semaphore proof is verified.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param merkleTreeRoot: Root of the Merkle tree.
|
||||
/// @param externalNullifier: External nullifier.
|
||||
/// @param nullifierHash: Nullifier hash.
|
||||
/// @param signal: Semaphore signal.
|
||||
event ProofVerified(
|
||||
uint256 indexed groupId,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 externalNullifier,
|
||||
uint256 nullifierHash,
|
||||
uint256 signal
|
||||
);
|
||||
|
||||
/// @dev Saves the nullifier hash to avoid double signaling and emits an event
|
||||
/// if the zero-knowledge proof is valid.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param merkleTreeRoot: Root of the Merkle tree.
|
||||
/// @param signal: Semaphore signal.
|
||||
/// @param nullifierHash: Nullifier hash.
|
||||
/// @param externalNullifier: External nullifier.
|
||||
/// @param proof: Zero-knowledge proof.
|
||||
function verifyProof(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 signal,
|
||||
uint256 nullifierHash,
|
||||
uint256 externalNullifier,
|
||||
uint256[8] calldata proof
|
||||
) 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.
|
||||
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;
|
||||
|
||||
/// @dev Updates the group admin.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param newAdmin: New admin of the group.
|
||||
function updateGroupAdmin(uint256 groupId, address newAdmin) external;
|
||||
|
||||
/// @dev Adds a new member to an existing group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param identityCommitment: New identity commitment.
|
||||
function addMember(uint256 groupId, uint256 identityCommitment) external;
|
||||
|
||||
/// @dev Adds new members to an existing group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param identityCommitments: New identity commitments.
|
||||
function addMembers(uint256 groupId, uint256[] calldata identityCommitments) external;
|
||||
|
||||
/// @dev Updates an identity commitment of an existing group. A proof of membership is
|
||||
/// needed to check if the node to be updated is part of the tree.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param identityCommitment: Existing identity commitment to be updated.
|
||||
/// @param newIdentityCommitment: New identity commitment.
|
||||
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
|
||||
/// @param proofPathIndices: Path of the proof of membership.
|
||||
function updateMember(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256 newIdentityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) external;
|
||||
|
||||
/// @dev Removes a member from an existing group. A proof of membership is
|
||||
/// needed to check if the node to be removed is part of the tree.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param identityCommitment: Identity commitment to be removed.
|
||||
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
|
||||
/// @param proofPathIndices: Path of the proof of membership.
|
||||
function removeMember(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) external;
|
||||
}
|
||||
59
packages/contracts/contracts/interfaces/ISemaphoreGroups.sol
Normal file
59
packages/contracts/contracts/interfaces/ISemaphoreGroups.sol
Normal file
@@ -0,0 +1,59 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreGroups interface.
|
||||
/// @dev Interface of a SemaphoreGroups contract.
|
||||
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.
|
||||
/// @param merkleTreeDepth: Depth of the tree.
|
||||
/// @param zeroValue: Zero value of the tree.
|
||||
event GroupCreated(uint256 indexed groupId, uint256 merkleTreeDepth, uint256 zeroValue);
|
||||
|
||||
/// @dev Emitted when a new identity commitment is added.
|
||||
/// @param groupId: Group id of the group.
|
||||
/// @param index: Identity commitment index.
|
||||
/// @param identityCommitment: New identity commitment.
|
||||
/// @param merkleTreeRoot: New root hash of the tree.
|
||||
event MemberAdded(uint256 indexed groupId, uint256 index, uint256 identityCommitment, uint256 merkleTreeRoot);
|
||||
|
||||
/// @dev Emitted when an identity commitment is updated.
|
||||
/// @param groupId: Group id of the group.
|
||||
/// @param index: Identity commitment index.
|
||||
/// @param identityCommitment: Existing identity commitment to be updated.
|
||||
/// @param newIdentityCommitment: New identity commitment.
|
||||
/// @param merkleTreeRoot: New root hash of the tree.
|
||||
event MemberUpdated(
|
||||
uint256 indexed groupId,
|
||||
uint256 index,
|
||||
uint256 identityCommitment,
|
||||
uint256 newIdentityCommitment,
|
||||
uint256 merkleTreeRoot
|
||||
);
|
||||
|
||||
/// @dev Emitted when a new identity commitment is removed.
|
||||
/// @param groupId: Group id of the group.
|
||||
/// @param index: Identity commitment index.
|
||||
/// @param identityCommitment: Existing identity commitment to be removed.
|
||||
/// @param merkleTreeRoot: New root hash of the tree.
|
||||
event MemberRemoved(uint256 indexed groupId, uint256 index, uint256 identityCommitment, uint256 merkleTreeRoot);
|
||||
|
||||
/// @dev Returns the last root hash of a group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @return Root hash of the group.
|
||||
function getMerkleTreeRoot(uint256 groupId) external view returns (uint256);
|
||||
|
||||
/// @dev Returns the depth of the tree of a group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @return Depth of the group tree.
|
||||
function getMerkleTreeDepth(uint256 groupId) external view returns (uint256);
|
||||
|
||||
/// @dev Returns the number of tree leaves of a group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @return Number of tree leaves.
|
||||
function getNumberOfMerkleTreeLeaves(uint256 groupId) external view returns (uint256);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import "../base/Pairing.sol";
|
||||
|
||||
/// @title SemaphoreVerifier interface.
|
||||
/// @dev Interface of SemaphoreVerifier contract.
|
||||
interface ISemaphoreVerifier {
|
||||
struct VerificationKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
/// @dev Verifies that the zero-knowledge proof is valid.
|
||||
/// @param merkleTreeRoot: Root of the Merkle tree.
|
||||
/// @param nullifierHash: Nullifier hash.
|
||||
/// @param signal: Semaphore signal.
|
||||
/// @param externalNullifier: External nullifier.
|
||||
/// @param proof: Zero-knowledge proof.
|
||||
/// @param merkleTreeDepth: Depth of the tree.
|
||||
function verifyProof(
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 nullifierHash,
|
||||
uint256 signal,
|
||||
uint256 externalNullifier,
|
||||
uint256[8] calldata proof,
|
||||
uint256 merkleTreeDepth
|
||||
) external view;
|
||||
}
|
||||
87
packages/contracts/contracts/interfaces/ISemaphoreVoting.sol
Normal file
87
packages/contracts/contracts/interfaces/ISemaphoreVoting.sol
Normal file
@@ -0,0 +1,87 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreVoting interface.
|
||||
/// @dev Interface of SemaphoreVoting contract.
|
||||
interface ISemaphoreVoting {
|
||||
error Semaphore__CallerIsNotThePollCoordinator();
|
||||
error Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
error Semaphore__PollHasAlreadyBeenStarted();
|
||||
error Semaphore__PollIsNotOngoing();
|
||||
error Semaphore__YouAreUsingTheSameNillifierTwice();
|
||||
|
||||
enum PollState {
|
||||
Created,
|
||||
Ongoing,
|
||||
Ended
|
||||
}
|
||||
|
||||
struct Verifier {
|
||||
address contractAddress;
|
||||
uint256 merkleTreeDepth;
|
||||
}
|
||||
|
||||
struct Poll {
|
||||
address coordinator;
|
||||
PollState state;
|
||||
}
|
||||
|
||||
/// @dev Emitted when a new poll is created.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param coordinator: Coordinator of the poll.
|
||||
event PollCreated(uint256 pollId, address indexed coordinator);
|
||||
|
||||
/// @dev Emitted when a poll is started.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param coordinator: Coordinator of the poll.
|
||||
/// @param encryptionKey: Key to encrypt the poll votes.
|
||||
event PollStarted(uint256 pollId, address indexed coordinator, uint256 encryptionKey);
|
||||
|
||||
/// @dev Emitted when a user votes on a poll.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param vote: User encrypted vote.
|
||||
event VoteAdded(uint256 indexed pollId, uint256 vote);
|
||||
|
||||
/// @dev Emitted when a poll is ended.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param coordinator: Coordinator of the poll.
|
||||
/// @param decryptionKey: Key to decrypt the poll votes.
|
||||
event PollEnded(uint256 pollId, address indexed coordinator, uint256 decryptionKey);
|
||||
|
||||
/// @dev Creates a poll and the associated Merkle tree/group.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param coordinator: Coordinator of the poll.
|
||||
/// @param merkleTreeDepth: Depth of the tree.
|
||||
function createPoll(
|
||||
uint256 pollId,
|
||||
address coordinator,
|
||||
uint256 merkleTreeDepth
|
||||
) external;
|
||||
|
||||
/// @dev Adds a voter to a poll.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param identityCommitment: Identity commitment of the group member.
|
||||
function addVoter(uint256 pollId, uint256 identityCommitment) external;
|
||||
|
||||
/// @dev Starts a pull and publishes the key to encrypt the votes.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param encryptionKey: Key to encrypt poll votes.
|
||||
function startPoll(uint256 pollId, uint256 encryptionKey) external;
|
||||
|
||||
/// @dev Casts an anonymous vote in a poll.
|
||||
/// @param vote: Encrypted vote.
|
||||
/// @param nullifierHash: Nullifier hash.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param proof: Private zk-proof parameters.
|
||||
function castVote(
|
||||
uint256 vote,
|
||||
uint256 nullifierHash,
|
||||
uint256 pollId,
|
||||
uint256[8] calldata proof
|
||||
) external;
|
||||
|
||||
/// @dev Ends a pull and publishes the key to decrypt the votes.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param decryptionKey: Key to decrypt poll votes.
|
||||
function endPoll(uint256 pollId, uint256 decryptionKey) external;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreWhistleblowing interface.
|
||||
/// @dev Interface of SemaphoreWhistleblowing contract.
|
||||
interface ISemaphoreWhistleblowing {
|
||||
error Semaphore__CallerIsNotTheEditor();
|
||||
error Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
|
||||
struct Verifier {
|
||||
address contractAddress;
|
||||
uint256 merkleTreeDepth;
|
||||
}
|
||||
|
||||
/// @dev Emitted when a new entity is created.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param editor: Editor of the entity.
|
||||
event EntityCreated(uint256 entityId, address indexed editor);
|
||||
|
||||
/// @dev Emitted when a whistleblower publish a new leak.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param leak: News leak.
|
||||
event LeakPublished(uint256 indexed entityId, uint256 leak);
|
||||
|
||||
/// @dev Creates an entity and the associated Merkle tree/group.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param editor: Editor of the entity.
|
||||
/// @param merkleTreeDepth: Depth of the tree.
|
||||
function createEntity(
|
||||
uint256 entityId,
|
||||
address editor,
|
||||
uint256 merkleTreeDepth
|
||||
) external;
|
||||
|
||||
/// @dev Adds a whistleblower to an entity.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param identityCommitment: Identity commitment of the group member.
|
||||
function addWhistleblower(uint256 entityId, uint256 identityCommitment) external;
|
||||
|
||||
/// @dev Removes a whistleblower from an entity.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param identityCommitment: Identity commitment of the group member.
|
||||
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
|
||||
/// @param proofPathIndices: Path of the proof of membership.
|
||||
function removeWhistleblower(
|
||||
uint256 entityId,
|
||||
uint256 identityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) external;
|
||||
|
||||
/// @dev Allows whistleblowers to publish leaks anonymously.
|
||||
/// @param leak: News leak.
|
||||
/// @param nullifierHash: Nullifier hash.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param proof: Private zk-proof parameters.
|
||||
function publishLeak(
|
||||
uint256 leak,
|
||||
uint256 nullifierHash,
|
||||
uint256 entityId,
|
||||
uint256[8] calldata proof
|
||||
) external;
|
||||
}
|
||||
35
packages/contracts/contracts/package.json
Normal file
35
packages/contracts/contracts/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/contracts",
|
||||
"version": "2.6.1",
|
||||
"description": "Semaphore contracts to manage groups and broadcast anonymous signals.",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"**/*.sol"
|
||||
],
|
||||
"keywords": [
|
||||
"blockchain",
|
||||
"ethereum",
|
||||
"hardhat",
|
||||
"smart-contracts",
|
||||
"semaphore",
|
||||
"identity",
|
||||
"solidity",
|
||||
"zero-knowledge",
|
||||
"zk-snarks",
|
||||
"zero-knowledge-proofs",
|
||||
"circom",
|
||||
"proof-of-membership"
|
||||
],
|
||||
"repository": "https://github.com/semaphore-protocol/semaphore",
|
||||
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts",
|
||||
"bugs": {
|
||||
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "4.7.3",
|
||||
"@zk-kit/incremental-merkle-tree.sol": "1.3.1"
|
||||
}
|
||||
}
|
||||
24
packages/contracts/deployed-contracts/arbitrum.json
Normal file
24
packages/contracts/deployed-contracts/arbitrum.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
24
packages/contracts/deployed-contracts/goerli.json
Normal file
24
packages/contracts/deployed-contracts/goerli.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"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",
|
||||
"PoseidonT3": "0xe0A452533853310C371b50Bd91BB9DCC8961350F",
|
||||
"IncrementalBinaryTree": "0x61AE89E372492e53D941DECaaC9821649fa9B236"
|
||||
}
|
||||
70
packages/contracts/hardhat.config.ts
Normal file
70
packages/contracts/hardhat.config.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
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"
|
||||
import { HardhatUserConfig } from "hardhat/config"
|
||||
import { NetworksUserConfig } from "hardhat/types"
|
||||
import { resolve } from "path"
|
||||
import "solidity-coverage"
|
||||
import { config } from "./package.json"
|
||||
import "./tasks/accounts"
|
||||
import "./tasks/deploy-semaphore"
|
||||
import "./tasks/deploy-semaphore-voting"
|
||||
import "./tasks/deploy-semaphore-whistleblowing"
|
||||
|
||||
dotenvConfig({ path: resolve(__dirname, "../../.env") })
|
||||
|
||||
function getNetworks(): NetworksUserConfig {
|
||||
if (!process.env.INFURA_API_KEY || !process.env.BACKEND_PRIVATE_KEY) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const infuraApiKey = process.env.INFURA_API_KEY
|
||||
const accounts = [`0x${process.env.BACKEND_PRIVATE_KEY}`]
|
||||
|
||||
return {
|
||||
goerli: {
|
||||
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: "https://arb1.arbitrum.io/rpc",
|
||||
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,
|
||||
allowUnlimitedContractSize: true
|
||||
},
|
||||
...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"
|
||||
},
|
||||
etherscan: {
|
||||
apiKey: process.env.ETHERSCAN_API_KEY
|
||||
}
|
||||
}
|
||||
|
||||
export default hardhatConfig
|
||||
69
packages/contracts/package.json
Normal file
69
packages/contracts/package.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "contracts",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "hardhat node",
|
||||
"compile": "hardhat compile",
|
||||
"deploy:semaphore": "hardhat deploy:semaphore",
|
||||
"deploy:semaphore-voting": "hardhat deploy:semaphore-voting",
|
||||
"deploy:semaphore-whistleblowing": "hardhat deploy:semaphore-whistleblowing",
|
||||
"verify:contracts": "hardhat run scripts/verify-contracts.ts",
|
||||
"test": "hardhat test",
|
||||
"test:report-gas": "REPORT_GAS=true hardhat test",
|
||||
"test:coverage": "hardhat coverage",
|
||||
"typechain": "hardhat typechain",
|
||||
"lint": "solhint 'contracts/**/*.sol'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.6",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.1.0",
|
||||
"@nomiclabs/hardhat-waffle": "^2.0.3",
|
||||
"@semaphore-protocol/group": "workspace:packages/group",
|
||||
"@semaphore-protocol/identity": "workspace:packages/identity",
|
||||
"@semaphore-protocol/proof": "workspace:packages/proof",
|
||||
"@typechain/ethers-v5": "^10.0.0",
|
||||
"@typechain/hardhat": "^6.0.0",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/download": "^8.0.1",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@types/node": "^17.0.12",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"chai": "^4.3.5",
|
||||
"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",
|
||||
"js-logger": "^1.6.1",
|
||||
"prettier-plugin-solidity": "^1.0.0-beta.19",
|
||||
"rimraf": "^3.0.2",
|
||||
"snarkjs": "^0.4.13",
|
||||
"solhint": "^3.3.6",
|
||||
"solhint-plugin-prettier": "^0.0.5",
|
||||
"solidity-coverage": "^0.7.21",
|
||||
"ts-node": "^10.4.0",
|
||||
"typechain": "^8.0.0"
|
||||
},
|
||||
"config": {
|
||||
"solidity": {
|
||||
"version": "0.8.4"
|
||||
},
|
||||
"paths": {
|
||||
"contracts": "./contracts",
|
||||
"circuit": "./circuit",
|
||||
"tests": "./test",
|
||||
"cache": "./cache",
|
||||
"snarkjs-templates": "./snarkjs-templates",
|
||||
"build": {
|
||||
"contracts": "./build/contracts",
|
||||
"typechain": "./build/typechain"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "4.7.3",
|
||||
"@zk-kit/incremental-merkle-tree.sol": "1.3.1"
|
||||
}
|
||||
}
|
||||
33
packages/contracts/scripts/utils.ts
Normal file
33
packages/contracts/scripts/utils.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { readFileSync, writeFileSync } from "fs"
|
||||
|
||||
type DeployedContracts = {
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
|
||||
export function saveDeployedContracts(network: string | undefined, newDeployedContracts: DeployedContracts) {
|
||||
const deployedContracts = getDeployedContracts(network)
|
||||
|
||||
writeFileSync(
|
||||
`./deployed-contracts/${network}.json`,
|
||||
JSON.stringify(
|
||||
{
|
||||
...deployedContracts,
|
||||
...newDeployedContracts
|
||||
},
|
||||
null,
|
||||
4
|
||||
)
|
||||
)
|
||||
}
|
||||
29
packages/contracts/scripts/verify-contracts.ts
Normal file
29
packages/contracts/scripts/verify-contracts.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { hardhatArguments, run } from "hardhat"
|
||||
import { getDeployedContracts } from "./utils"
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
217
packages/contracts/snarkjs-templates/verifier_groth16.sol.ejs
Normal file
217
packages/contracts/snarkjs-templates/verifier_groth16.sol.ejs
Normal file
@@ -0,0 +1,217 @@
|
||||
//
|
||||
// Copyright 2017 Christian Reitwiessner
|
||||
// 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.
|
||||
//
|
||||
// 2019 OKIMS
|
||||
// ported to solidity 0.6
|
||||
// fixed linter warnings
|
||||
// added requiere error messages
|
||||
//
|
||||
// 2021 Remco Bloemen
|
||||
// cleaned up code
|
||||
// added InvalidProve() error
|
||||
// always revert with InvalidProof() on invalid proof
|
||||
// make Pairing strict
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
library Pairing {
|
||||
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.
|
||||
uint256 constant SCALAR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
|
||||
struct G1Point {
|
||||
uint256 X;
|
||||
uint256 Y;
|
||||
}
|
||||
|
||||
// Encoding of field elements is: X[0] * z + X[1]
|
||||
struct G2Point {
|
||||
uint256[2] X;
|
||||
uint256[2] Y;
|
||||
}
|
||||
|
||||
/// @return the generator of G1
|
||||
function P1() internal pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() internal pure returns (G2Point memory) {
|
||||
return
|
||||
G2Point(
|
||||
[
|
||||
11559732032986387107991004021392285783925812861821192530917403151452391805634,
|
||||
10857046999023057135944570762232829481370756359578518086990519993285655852781
|
||||
],
|
||||
[
|
||||
4082367875863433681332203403145435568316851327593401208105741076214120093531,
|
||||
8495653923123431417604973247489272438418190587263600148770280649306958101930
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// @return r the negation of p, i.e. p.addition(p.negate()) should be zero.
|
||||
function negate(G1Point memory p) internal pure returns (G1Point memory r) {
|
||||
if (p.X == 0 && p.Y == 0) return G1Point(0, 0);
|
||||
// Validate input or revert
|
||||
if (p.X >= BASE_MODULUS || p.Y >= BASE_MODULUS) revert InvalidProof();
|
||||
// We know p.Y > 0 and p.Y < BASE_MODULUS.
|
||||
return G1Point(p.X, BASE_MODULUS - p.Y);
|
||||
}
|
||||
|
||||
/// @return r the sum of two points of G1
|
||||
function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
|
||||
// By EIP-196 all input is validated to be less than the BASE_MODULUS and form points
|
||||
// on the curve.
|
||||
uint256[4] memory input;
|
||||
input[0] = p1.X;
|
||||
input[1] = p1.Y;
|
||||
input[2] = p2.X;
|
||||
input[3] = p2.Y;
|
||||
bool success;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
|
||||
}
|
||||
if (!success) revert InvalidProof();
|
||||
}
|
||||
|
||||
/// @return r the product of a point on G1 and a scalar, i.e.
|
||||
/// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p.
|
||||
function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
|
||||
// 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 InvalidProof();
|
||||
uint256[3] memory input;
|
||||
input[0] = p.X;
|
||||
input[1] = p.Y;
|
||||
input[2] = s;
|
||||
bool success;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
|
||||
}
|
||||
if (!success) revert InvalidProof();
|
||||
}
|
||||
|
||||
/// Asserts the pairing check
|
||||
/// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
|
||||
/// For example pairing([P1(), P1().negate()], [P2(), P2()]) should succeed
|
||||
function pairingCheck(G1Point[] memory p1, G2Point[] memory p2) internal view {
|
||||
// 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 InvalidProof();
|
||||
uint256 elements = p1.length;
|
||||
uint256 inputSize = elements * 6;
|
||||
uint256[] memory input = new uint256[](inputSize);
|
||||
for (uint256 i = 0; i < elements; i++) {
|
||||
input[i * 6 + 0] = p1[i].X;
|
||||
input[i * 6 + 1] = p1[i].Y;
|
||||
input[i * 6 + 2] = p2[i].X[0];
|
||||
input[i * 6 + 3] = p2[i].X[1];
|
||||
input[i * 6 + 4] = p2[i].Y[0];
|
||||
input[i * 6 + 5] = p2[i].Y[1];
|
||||
}
|
||||
uint256[1] memory out;
|
||||
bool success;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
|
||||
}
|
||||
if (!success || out[0] != 1) revert InvalidProof();
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier {
|
||||
using Pairing for *;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(
|
||||
<%=vk_alpha_1[0]%>,
|
||||
<%=vk_alpha_1[1]%>
|
||||
);
|
||||
|
||||
vk.beta2 = Pairing.G2Point(
|
||||
[<%=vk_beta_2[0][1]%>, <%=vk_beta_2[0][0]%>],
|
||||
[<%=vk_beta_2[1][1]%>, <%=vk_beta_2[1][0]%>]
|
||||
);
|
||||
|
||||
vk.gamma2 = Pairing.G2Point(
|
||||
[<%=vk_gamma_2[0][1]%>, <%=vk_gamma_2[0][0]%>],
|
||||
[<%=vk_gamma_2[1][1]%>, <%=vk_gamma_2[1][0]%>]
|
||||
);
|
||||
|
||||
vk.delta2 = Pairing.G2Point(
|
||||
[<%=vk_delta_2[0][1]%>, <%=vk_delta_2[0][0]%>],
|
||||
[<%=vk_delta_2[1][1]%>, <%=vk_delta_2[1][0]%>]
|
||||
);
|
||||
|
||||
vk.IC = new Pairing.G1Point[](<%=IC.length%>);
|
||||
|
||||
<% for (let i=0; i<IC.length; i++) { %>
|
||||
vk.IC[<%=i%>] = Pairing.G1Point(
|
||||
<%=IC[i][0]%>,
|
||||
<%=IC[i][1]%>
|
||||
);
|
||||
<% } %>
|
||||
}
|
||||
|
||||
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
|
||||
function verifyProof(
|
||||
uint[2] memory a,
|
||||
uint[2][2] memory b,
|
||||
uint[2] memory c,
|
||||
uint[<%=IC.length-1%>] memory input
|
||||
) public view {
|
||||
// If the values are not in the correct range, the Pairing contract will revert.
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x of inputs times IC
|
||||
if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
|
||||
Pairing.G1Point memory vk_x = vk.IC[0];
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
|
||||
|
||||
// Check pairing
|
||||
Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
|
||||
Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
|
||||
p1[0] = Pairing.negate(proof.A);
|
||||
p2[0] = proof.B;
|
||||
p1[1] = vk.alfa1;
|
||||
p2[1] = vk.beta2;
|
||||
p1[2] = vk_x;
|
||||
p2[2] = vk.gamma2;
|
||||
p1[3] = proof.C;
|
||||
p2[3] = vk.delta2;
|
||||
Pairing.pairingCheck(p1, p2);
|
||||
}
|
||||
}
|
||||
16
packages/contracts/tasks/accounts.ts
Normal file
16
packages/contracts/tasks/accounts.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Signer } from "@ethersproject/abstract-signer"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("accounts", "Prints the list of accounts")
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs }, { ethers }) => {
|
||||
const accounts: Signer[] = await ethers.getSigners()
|
||||
|
||||
if (logs) {
|
||||
for (const account of accounts) {
|
||||
console.info(await account.getAddress())
|
||||
}
|
||||
}
|
||||
|
||||
return accounts
|
||||
})
|
||||
73
packages/contracts/tasks/deploy-semaphore-voting.ts
Normal file
73
packages/contracts/tasks/deploy-semaphore-voting.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
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> => {
|
||||
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}`)
|
||||
}
|
||||
|
||||
const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", {
|
||||
libraries: {
|
||||
Pairing: pairing.address
|
||||
}
|
||||
})
|
||||
|
||||
const semaphoreVerifier = await SemaphoreVerifierFactory.deploy()
|
||||
|
||||
await semaphoreVerifier.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`)
|
||||
}
|
||||
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
|
||||
const [signer] = await ethers.getSigners()
|
||||
|
||||
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}`)
|
||||
}
|
||||
|
||||
const IncrementalBinaryTreeFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
|
||||
libraries: {
|
||||
PoseidonT3: poseidon.address
|
||||
}
|
||||
})
|
||||
const incrementalBinaryTree = await IncrementalBinaryTreeFactory.deploy()
|
||||
|
||||
await incrementalBinaryTree.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTree.address}`)
|
||||
}
|
||||
|
||||
const SemaphoreVotingFactory = await ethers.getContractFactory("SemaphoreVoting", {
|
||||
libraries: {
|
||||
IncrementalBinaryTree: incrementalBinaryTree.address
|
||||
}
|
||||
})
|
||||
|
||||
const semaphoreVoting = await SemaphoreVotingFactory.deploy(semaphoreVerifier.address)
|
||||
|
||||
await semaphoreVoting.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`SemaphoreVoting contract has been deployed to: ${semaphoreVoting.address}`)
|
||||
}
|
||||
|
||||
return semaphoreVoting
|
||||
})
|
||||
73
packages/contracts/tasks/deploy-semaphore-whistleblowing.ts
Normal file
73
packages/contracts/tasks/deploy-semaphore-whistleblowing.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
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> => {
|
||||
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}`)
|
||||
}
|
||||
|
||||
const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", {
|
||||
libraries: {
|
||||
Pairing: pairing.address
|
||||
}
|
||||
})
|
||||
|
||||
const semaphoreVerifier = await SemaphoreVerifierFactory.deploy()
|
||||
|
||||
await semaphoreVerifier.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`)
|
||||
}
|
||||
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
|
||||
const [signer] = await ethers.getSigners()
|
||||
|
||||
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}`)
|
||||
}
|
||||
|
||||
const IncrementalBinaryTreeFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
|
||||
libraries: {
|
||||
PoseidonT3: poseidon.address
|
||||
}
|
||||
})
|
||||
const incrementalBinaryTree = await IncrementalBinaryTreeFactory.deploy()
|
||||
|
||||
await incrementalBinaryTree.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTree.address}`)
|
||||
}
|
||||
|
||||
const SemaphoreWhistleblowingFactory = await ethers.getContractFactory("SemaphoreWhistleblowing", {
|
||||
libraries: {
|
||||
IncrementalBinaryTree: incrementalBinaryTree.address
|
||||
}
|
||||
})
|
||||
|
||||
const semaphoreWhistleblowing = await SemaphoreWhistleblowingFactory.deploy(semaphoreVerifier.address)
|
||||
|
||||
await semaphoreWhistleblowing.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`SemaphoreWhistleblowing contract has been deployed to: ${semaphoreWhistleblowing.address}`)
|
||||
}
|
||||
|
||||
return semaphoreWhistleblowing
|
||||
})
|
||||
118
packages/contracts/tasks/deploy-semaphore.ts
Normal file
118
packages/contracts/tasks/deploy-semaphore.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { poseidon_gencontract as poseidonContract } from "circomlibjs"
|
||||
import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
import { saveDeployedContracts } from "../scripts/utils"
|
||||
|
||||
task("deploy:semaphore", "Deploy a Semaphore contract")
|
||||
.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,
|
||||
pairing: pairingAddress,
|
||||
semaphoreVerifier: semaphoreVerifierAddress,
|
||||
poseidon: poseidonAddress,
|
||||
incrementalBinaryTree: incrementalBinaryTreeAddress
|
||||
},
|
||||
{ ethers, hardhatArguments }
|
||||
): Promise<Contract> => {
|
||||
if (!semaphoreVerifierAddress) {
|
||||
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}`)
|
||||
}
|
||||
|
||||
semaphoreVerifierAddress = semaphoreVerifier.address
|
||||
}
|
||||
|
||||
if (!incrementalBinaryTreeAddress) {
|
||||
if (!poseidonAddress) {
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
|
||||
const [signer] = await ethers.getSigners()
|
||||
|
||||
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 SemaphoreFactory = await ethers.getContractFactory("Semaphore", {
|
||||
libraries: {
|
||||
IncrementalBinaryTree: incrementalBinaryTreeAddress
|
||||
}
|
||||
})
|
||||
|
||||
const semaphore = await SemaphoreFactory.deploy(semaphoreVerifierAddress)
|
||||
|
||||
await semaphore.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Semaphore contract has been deployed to: ${semaphore.address}`)
|
||||
}
|
||||
|
||||
saveDeployedContracts(hardhatArguments.network, {
|
||||
Pairing: pairingAddress,
|
||||
SemaphoreVerifier: semaphoreVerifierAddress,
|
||||
Poseidon: poseidonAddress,
|
||||
IncrementalBinaryTree: incrementalBinaryTreeAddress,
|
||||
Semaphore: semaphore.address
|
||||
})
|
||||
|
||||
return semaphore
|
||||
}
|
||||
)
|
||||
287
packages/contracts/test/Semaphore.ts
Normal file
287
packages/contracts/test/Semaphore.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* 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 { expect } from "chai"
|
||||
import { constants, Signer } from "ethers"
|
||||
import { run } from "hardhat"
|
||||
import { Semaphore } from "../build/typechain"
|
||||
import { createIdentityCommitments } from "./utils"
|
||||
|
||||
describe("Semaphore", () => {
|
||||
let contract: Semaphore
|
||||
let signers: Signer[]
|
||||
let accounts: string[]
|
||||
|
||||
const treeDepth = Number(process.env.TREE_DEPTH) || 20
|
||||
const groupId = 1
|
||||
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", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
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])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeDepthIsNotSupported()")
|
||||
})
|
||||
|
||||
it("Should create a group", async () => {
|
||||
const transaction = contract
|
||||
.connect(signers[1])
|
||||
["createGroup(uint256,uint256,uint256,address)"](groupId, treeDepth, 0, accounts[1])
|
||||
|
||||
await expect(transaction).to.emit(contract, "GroupCreated").withArgs(groupId, treeDepth, 0)
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "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
|
||||
.connect(signers[1])
|
||||
["createGroup(uint256,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 expect(transaction).to.emit(contract, "GroupCreated").withArgs(groupId, treeDepth, 0)
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "GroupAdminUpdated")
|
||||
.withArgs(groupId, constants.AddressZero, accounts[0])
|
||||
})
|
||||
})
|
||||
|
||||
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])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheGroupAdmin()")
|
||||
})
|
||||
|
||||
it("Should update the group admin", async () => {
|
||||
const transaction = contract.connect(signers[1]).updateGroupAdmin(groupId, accounts[0])
|
||||
|
||||
await expect(transaction).to.emit(contract, "GroupAdminUpdated").withArgs(groupId, accounts[1], accounts[0])
|
||||
})
|
||||
})
|
||||
|
||||
describe("# addMember", () => {
|
||||
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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheGroupAdmin()")
|
||||
})
|
||||
|
||||
it("Should add a new member in an existing group", async () => {
|
||||
const group = new Group(treeDepth)
|
||||
|
||||
group.addMember(members[0])
|
||||
|
||||
const transaction = contract.addMember(groupId, members[0])
|
||||
|
||||
await expect(transaction).to.emit(contract, "MemberAdded").withArgs(groupId, 0, members[0], group.root)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# addMembers", () => {
|
||||
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)
|
||||
|
||||
group.addMembers(members)
|
||||
|
||||
await contract["createGroup(uint256,uint256,uint256,address)"](groupId, treeDepth, 0, accounts[0])
|
||||
|
||||
const transaction = contract.addMembers(groupId, members)
|
||||
|
||||
await expect(transaction).to.emit(contract, "MemberAdded").withArgs(groupId, 2, BigInt(3), group.root)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# updateMember", () => {
|
||||
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])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("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)
|
||||
|
||||
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)
|
||||
|
||||
const { siblings, pathIndices, root } = group.generateMerkleProof(0)
|
||||
|
||||
const transaction = contract.updateMember(groupId, BigInt(1), BigInt(4), siblings, pathIndices)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "MemberUpdated")
|
||||
.withArgs(groupId, 0, BigInt(1), BigInt(4), root)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# removeMember", () => {
|
||||
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])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("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)
|
||||
|
||||
group.addMembers(members)
|
||||
|
||||
group.removeMember(2)
|
||||
|
||||
await contract["createGroup(uint256,uint256,uint256,address)"](groupId, treeDepth, 0, accounts[0])
|
||||
await contract.addMembers(groupId, members)
|
||||
|
||||
const { siblings, pathIndices, root } = group.generateMerkleProof(2)
|
||||
|
||||
const transaction = contract.removeMember(groupId, BigInt(3), siblings, pathIndices)
|
||||
|
||||
await expect(transaction).to.emit(contract, "MemberRemoved").withArgs(groupId, 2, BigInt(3), root)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# verifyProof", () => {
|
||||
const signal = 2
|
||||
const identity = new Identity("0")
|
||||
|
||||
const group = new Group(treeDepth)
|
||||
|
||||
group.addMembers(members)
|
||||
|
||||
let fullProof: FullProof
|
||||
let solidityProof: SolidityProof
|
||||
|
||||
before(async () => {
|
||||
await contract.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])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("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])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeRootIsNotPartOfTheGroup()")
|
||||
})
|
||||
|
||||
it("Should throw an exception if the proof is not valid", async () => {
|
||||
const transaction = contract.verifyProof(
|
||||
groupId,
|
||||
group.root,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
0,
|
||||
solidityProof
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__InvalidProof()")
|
||||
})
|
||||
|
||||
it("Should verify a proof for an onchain group correctly", async () => {
|
||||
const transaction = contract.verifyProof(
|
||||
groupId,
|
||||
group.root,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.merkleTreeRoot,
|
||||
solidityProof
|
||||
)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "ProofVerified")
|
||||
.withArgs(
|
||||
groupId,
|
||||
group.root,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.merkleTreeRoot,
|
||||
signal
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not verify the same proof for an onchain group twice", async () => {
|
||||
const transaction = contract.verifyProof(
|
||||
groupId,
|
||||
group.root,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.merkleTreeRoot,
|
||||
solidityProof
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__YouAreUsingTheSameNillifierTwice()")
|
||||
})
|
||||
|
||||
it("Should not verify a proof if the Merkle tree root is expired", async () => {
|
||||
const groupId = 2
|
||||
const group = new Group(treeDepth)
|
||||
|
||||
group.addMembers([members[0], members[1]])
|
||||
|
||||
const fullProof = await generateProof(identity, group, group.root, signal, {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
const solidityProof = packToSolidityProof(fullProof.proof)
|
||||
|
||||
const transaction = contract.verifyProof(
|
||||
groupId,
|
||||
group.root,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
0,
|
||||
solidityProof
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeRootIsExpired()")
|
||||
})
|
||||
})
|
||||
})
|
||||
186
packages/contracts/test/SemaphoreVoting.ts
Normal file
186
packages/contracts/test/SemaphoreVoting.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
/* 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 { expect } from "chai"
|
||||
import { Signer } from "ethers"
|
||||
import { ethers, run } from "hardhat"
|
||||
import { SemaphoreVoting } from "../build/typechain"
|
||||
|
||||
describe("SemaphoreVoting", () => {
|
||||
let contract: SemaphoreVoting
|
||||
let accounts: Signer[]
|
||||
let coordinator: string
|
||||
|
||||
const treeDepth = Number(process.env.TREE_DEPTH) || 20
|
||||
const pollIds = [1, 2, 3]
|
||||
const encryptionKey = BigInt(0)
|
||||
const decryptionKey = BigInt(0)
|
||||
|
||||
const wasmFilePath = `../../snark-artifacts/${treeDepth}/semaphore.wasm`
|
||||
const zkeyFilePath = `../../snark-artifacts/${treeDepth}/semaphore.zkey`
|
||||
|
||||
before(async () => {
|
||||
contract = await run("deploy:semaphore-voting", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeDepthIsNotSupported()")
|
||||
})
|
||||
|
||||
it("Should create a poll", async () => {
|
||||
const transaction = contract.createPoll(pollIds[0], coordinator, treeDepth)
|
||||
|
||||
await expect(transaction).to.emit(contract, "PollCreated").withArgs(pollIds[0], coordinator)
|
||||
})
|
||||
|
||||
it("Should not create a poll if it already exists", async () => {
|
||||
const transaction = contract.createPoll(pollIds[0], coordinator, treeDepth)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotThePollCoordinator()")
|
||||
})
|
||||
|
||||
it("Should start the poll", async () => {
|
||||
const transaction = contract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey)
|
||||
|
||||
await expect(transaction).to.emit(contract, "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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__PollHasAlreadyBeenStarted()")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# addVoter", () => {
|
||||
before(async () => {
|
||||
await contract.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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__PollHasAlreadyBeenStarted()")
|
||||
})
|
||||
|
||||
it("Should add a voter to an existing poll", async () => {
|
||||
const { commitment } = new Identity("test")
|
||||
const group = new Group(treeDepth)
|
||||
|
||||
group.addMember(commitment)
|
||||
|
||||
const transaction = contract.connect(accounts[1]).addVoter(pollIds[1], commitment)
|
||||
|
||||
await expect(transaction).to.emit(contract, "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])
|
||||
|
||||
expect(size).to.be.eq(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# castVote", () => {
|
||||
const identity = new Identity("test")
|
||||
const vote = 1
|
||||
|
||||
const group = new Group(treeDepth)
|
||||
|
||||
group.addMembers([identity.commitment, BigInt(1)])
|
||||
|
||||
let solidityProof: SolidityProof
|
||||
let publicSignals: PublicSignals
|
||||
|
||||
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)
|
||||
|
||||
const 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
|
||||
.connect(accounts[1])
|
||||
.castVote(vote, publicSignals.nullifierHash, pollIds[2], solidityProof)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__InvalidProof()")
|
||||
})
|
||||
|
||||
it("Should cast a vote", async () => {
|
||||
const transaction = contract
|
||||
.connect(accounts[1])
|
||||
.castVote(vote, publicSignals.nullifierHash, pollIds[1], solidityProof)
|
||||
|
||||
await expect(transaction).to.emit(contract, "VoteAdded").withArgs(pollIds[1], vote)
|
||||
})
|
||||
|
||||
it("Should not cast a vote twice", async () => {
|
||||
const transaction = contract
|
||||
.connect(accounts[1])
|
||||
.castVote(vote, publicSignals.nullifierHash, pollIds[1], solidityProof)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotThePollCoordinator()")
|
||||
})
|
||||
|
||||
it("Should end the poll", async () => {
|
||||
const transaction = contract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey)
|
||||
|
||||
await expect(transaction).to.emit(contract, "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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__PollIsNotOngoing()")
|
||||
})
|
||||
})
|
||||
})
|
||||
150
packages/contracts/test/SemaphoreWhistleblowing.ts
Normal file
150
packages/contracts/test/SemaphoreWhistleblowing.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
/* 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 { expect } from "chai"
|
||||
import { Signer, utils } from "ethers"
|
||||
import { ethers, run } from "hardhat"
|
||||
import { SemaphoreWhistleblowing } from "../build/typechain"
|
||||
|
||||
describe("SemaphoreWhistleblowing", () => {
|
||||
let contract: SemaphoreWhistleblowing
|
||||
let accounts: Signer[]
|
||||
let editor: string
|
||||
|
||||
const treeDepth = Number(process.env.TREE_DEPTH) || 20
|
||||
const entityIds = [1, 2]
|
||||
|
||||
const wasmFilePath = `../../snark-artifacts/${treeDepth}/semaphore.wasm`
|
||||
const zkeyFilePath = `../../snark-artifacts/${treeDepth}/semaphore.zkey`
|
||||
|
||||
before(async () => {
|
||||
contract = await run("deploy:semaphore-whistleblowing", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__MerkleTreeDepthIsNotSupported()")
|
||||
})
|
||||
|
||||
it("Should create an entity", async () => {
|
||||
const transaction = contract.createEntity(entityIds[0], editor, treeDepth)
|
||||
|
||||
await expect(transaction).to.emit(contract, "EntityCreated").withArgs(entityIds[0], editor)
|
||||
})
|
||||
|
||||
it("Should not create a entity if it already exists", async () => {
|
||||
const transaction = contract.createEntity(entityIds[0], editor, treeDepth)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__GroupAlreadyExists()")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# addWhistleblower", () => {
|
||||
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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheEditor()")
|
||||
})
|
||||
|
||||
it("Should add a whistleblower to an existing entity", async () => {
|
||||
const { commitment } = new Identity("test")
|
||||
const group = new Group(treeDepth)
|
||||
|
||||
group.addMember(commitment)
|
||||
|
||||
const transaction = contract.connect(accounts[1]).addWhistleblower(entityIds[0], commitment)
|
||||
|
||||
await expect(transaction).to.emit(contract, "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])
|
||||
|
||||
expect(size).to.be.eq(1)
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
group.addMember(commitment)
|
||||
|
||||
const { siblings, pathIndices } = group.generateMerkleProof(0)
|
||||
|
||||
const transaction = contract.removeWhistleblower(entityIds[0], commitment, siblings, pathIndices)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheEditor()")
|
||||
})
|
||||
|
||||
it("Should remove a whistleblower from an existing entity", async () => {
|
||||
const { commitment } = new Identity("test")
|
||||
const group = new Group(treeDepth)
|
||||
|
||||
group.addMember(commitment)
|
||||
|
||||
const { siblings, pathIndices } = group.generateMerkleProof(0)
|
||||
|
||||
group.removeMember(0)
|
||||
|
||||
const transaction = contract
|
||||
.connect(accounts[1])
|
||||
.removeWhistleblower(entityIds[0], commitment, siblings, pathIndices)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "MemberRemoved")
|
||||
.withArgs(entityIds[0], 0, commitment, group.root)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# publishLeak", () => {
|
||||
const identity = new Identity("test")
|
||||
const leak = utils.formatBytes32String("This is a leak")
|
||||
|
||||
const group = new Group(treeDepth)
|
||||
|
||||
group.addMembers([identity.commitment, BigInt(1)])
|
||||
|
||||
let solidityProof: SolidityProof
|
||||
let publicSignals: PublicSignals
|
||||
|
||||
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))
|
||||
|
||||
const 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)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Semaphore__InvalidProof()")
|
||||
})
|
||||
|
||||
it("Should publish a leak", async () => {
|
||||
const transaction = contract
|
||||
.connect(accounts[1])
|
||||
.publishLeak(leak, publicSignals.nullifierHash, entityIds[1], solidityProof)
|
||||
|
||||
await expect(transaction).to.emit(contract, "LeakPublished").withArgs(entityIds[1], leak)
|
||||
})
|
||||
})
|
||||
})
|
||||
14
packages/contracts/test/utils.ts
Normal file
14
packages/contracts/test/utils.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function createIdentityCommitments(n: number): bigint[] {
|
||||
const identityCommitments: bigint[] = []
|
||||
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
const { commitment } = new Identity(i.toString())
|
||||
|
||||
identityCommitments.push(commitment)
|
||||
}
|
||||
|
||||
return identityCommitments
|
||||
}
|
||||
10
packages/contracts/tsconfig.json
Normal file
10
packages/contracts/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2018",
|
||||
"module": "CommonJS",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"],
|
||||
"files": ["hardhat.config.ts"]
|
||||
}
|
||||
21
packages/group/LICENSE
Normal file
21
packages/group/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.
|
||||
127
packages/group/README.md
Normal file
127
packages/group/README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Semaphore group
|
||||
</h1>
|
||||
<p align="center">A library to create and manage Semaphore groups.</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/group">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/group?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/group">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/group.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://discord.gg/6mSdGHnstH">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| This library is an abstraction of [`@zk-kit/incremental-merkle-tree`](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/incremental-merkle-tree). The main goal is to make it easier to create offchain groups, which are also used to generate Semaphore proofs. Semaphore groups are actually incremental Merkle trees, and the group members are tree leaves. Since the Merkle tree implementation we are using is a binary tree, the maximum number of members of a group is equal to `2^treeDepth`. |
|
||||
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@semaphore-protocol/group` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @semaphore-protocol/group
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @semaphore-protocol/group
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
\# **new Group**(treeDepth = 20, zeroValue = BigInt(0)): _Group_
|
||||
|
||||
```typescript
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
|
||||
// Group with max 1048576 members (20^²).
|
||||
const group1 = new Group()
|
||||
|
||||
// Group with max 65536 members (16^²).
|
||||
const group2 = new Group(16)
|
||||
|
||||
// Group with max 16777216 members (24^²).
|
||||
const group3 = new Group(24)
|
||||
```
|
||||
|
||||
\# **addMember**(identityCommitment: _Member_)
|
||||
|
||||
```typescript
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
|
||||
const identity = new Identity()
|
||||
const commitment = identity.generateCommitment()
|
||||
|
||||
group.addMember(commitment)
|
||||
```
|
||||
|
||||
\# **addMembers**(identityCommitments: _Member\[]_)
|
||||
|
||||
```typescript
|
||||
let identityCommitments: bigint[]
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const identity = new Identity()
|
||||
const commitment = identity.generateCommitment()
|
||||
|
||||
identityCommitments.push(commitment)
|
||||
}
|
||||
|
||||
group.addMember(identityCommitments)
|
||||
```
|
||||
|
||||
\# **removeMember**(index: _number_)
|
||||
|
||||
```typescript
|
||||
group.removeMember(0)
|
||||
```
|
||||
|
||||
\# **indexOf**(member: _Member_): _number_
|
||||
|
||||
```typescript
|
||||
group.indexOf(commitment) // 0
|
||||
```
|
||||
|
||||
\# **generateMerkleProof**(index: _number_): _MerkleProof_
|
||||
|
||||
```typescript
|
||||
const proof = group.generateMerkleProof(0)
|
||||
```
|
||||
8
packages/group/build.tsconfig.json
Normal file
8
packages/group/build.tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declarationDir": "dist/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
41
packages/group/package.json
Normal file
41
packages/group/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/group",
|
||||
"version": "2.6.1",
|
||||
"description": "A library to create and manage Semaphore groups.",
|
||||
"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/group",
|
||||
"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/group"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
29
packages/group/rollup.config.ts
Normal file
29
packages/group/rollup.config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
import * as fs from "fs"
|
||||
import cleanup from "rollup-plugin-cleanup"
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
|
||||
const banner = `/**
|
||||
* @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.exports.require, format: "cjs", banner, exports: "auto" },
|
||||
{ file: pkg.exports.import, format: "es", banner }
|
||||
],
|
||||
external: Object.keys(pkg.dependencies),
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: "./build.tsconfig.json",
|
||||
useTsconfigDeclarationDir: true
|
||||
}),
|
||||
cleanup({ comments: "jsdoc" })
|
||||
]
|
||||
}
|
||||
95
packages/group/src/group.test.ts
Normal file
95
packages/group/src/group.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import Group from "./group"
|
||||
|
||||
describe("Group", () => {
|
||||
describe("# Group", () => {
|
||||
it("Should create a group", () => {
|
||||
const group = new Group()
|
||||
|
||||
expect(group.root.toString()).toContain("150197")
|
||||
expect(group.depth).toBe(20)
|
||||
expect(group.zeroValue).toBe(BigInt(0))
|
||||
expect(group.members).toHaveLength(0)
|
||||
})
|
||||
|
||||
it("Should not create a group with a wrong tree depth", () => {
|
||||
const fun = () => new Group(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))
|
||||
|
||||
expect(group.root.toString()).toContain("640470")
|
||||
expect(group.depth).toBe(32)
|
||||
expect(group.zeroValue).toBe(BigInt(1))
|
||||
expect(group.members).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# addMember", () => {
|
||||
it("Should add a member to a group", () => {
|
||||
const group = new Group()
|
||||
|
||||
group.addMember(BigInt(3))
|
||||
|
||||
expect(group.members).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# addMembers", () => {
|
||||
it("Should add many members to a group", () => {
|
||||
const group = new Group()
|
||||
|
||||
group.addMembers([BigInt(1), BigInt(3)])
|
||||
|
||||
expect(group.members).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# indexOf", () => {
|
||||
it("Should return the index of a member in a group", () => {
|
||||
const group = new Group()
|
||||
group.addMembers([BigInt(1), BigInt(3)])
|
||||
|
||||
const index = group.indexOf(BigInt(3))
|
||||
|
||||
expect(index).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# updateMember", () => {
|
||||
it("Should update a member in a group", () => {
|
||||
const group = new Group()
|
||||
group.addMembers([BigInt(1), BigInt(3)])
|
||||
|
||||
group.updateMember(0, BigInt(1))
|
||||
|
||||
expect(group.members).toHaveLength(2)
|
||||
expect(group.members[0]).toBe(BigInt(1))
|
||||
})
|
||||
})
|
||||
|
||||
describe("# removeMember", () => {
|
||||
it("Should remove a member from a group", () => {
|
||||
const group = new Group()
|
||||
group.addMembers([BigInt(1), BigInt(3)])
|
||||
|
||||
group.removeMember(0)
|
||||
|
||||
expect(group.members).toHaveLength(2)
|
||||
expect(group.members[0]).toBe(group.zeroValue)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# generateMerkleProof", () => {
|
||||
it("Should generate a proof of membership", () => {
|
||||
const group = new Group()
|
||||
group.addMembers([BigInt(1), BigInt(3)])
|
||||
|
||||
const proof = group.generateMerkleProof(0)
|
||||
|
||||
expect(proof.leaf).toBe(BigInt(1))
|
||||
})
|
||||
})
|
||||
})
|
||||
109
packages/group/src/group.ts
Normal file
109
packages/group/src/group.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { IncrementalMerkleTree, MerkleProof } from "@zk-kit/incremental-merkle-tree"
|
||||
import poseidon from "poseidon-lite"
|
||||
import { Member } from "./types"
|
||||
|
||||
export default class Group {
|
||||
merkleTree: IncrementalMerkleTree
|
||||
|
||||
/**
|
||||
* Initializes the group with the tree depth and the zero value.
|
||||
* @param treeDepth Tree depth.
|
||||
* @param zeroValue Zero values for zeroes.
|
||||
*/
|
||||
constructor(treeDepth = 20, zeroValue: Member = BigInt(0)) {
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root hash of the tree.
|
||||
* @returns Root hash.
|
||||
*/
|
||||
get root(): Member {
|
||||
return this.merkleTree.root
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the depth of the tree.
|
||||
* @returns Tree depth.
|
||||
*/
|
||||
get depth(): number {
|
||||
return this.merkleTree.depth
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the zero value of the tree.
|
||||
* @returns Tree zero value.
|
||||
*/
|
||||
get zeroValue(): Member {
|
||||
return this.merkleTree.zeroes[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the members (i.e. identity commitments) of the group.
|
||||
* @returns List of members.
|
||||
*/
|
||||
get members(): Member[] {
|
||||
return this.merkleTree.leaves
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of a member. If the member does not exist it returns -1.
|
||||
* @param member Group member.
|
||||
* @returns Index of the member.
|
||||
*/
|
||||
indexOf(member: Member): number {
|
||||
return this.merkleTree.indexOf(member)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new member to the group.
|
||||
* @param member New member.
|
||||
*/
|
||||
addMember(member: Member) {
|
||||
this.merkleTree.insert(BigInt(member))
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new members to the group.
|
||||
* @param members New members.
|
||||
*/
|
||||
addMembers(members: Member[]) {
|
||||
for (const member of members) {
|
||||
this.addMember(member)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a member in the group.
|
||||
* @param index Index of the member to be updated.
|
||||
* @param member New member value.
|
||||
*/
|
||||
updateMember(index: number, member: Member) {
|
||||
this.merkleTree.update(index, member)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a member from the group.
|
||||
* @param index Index of the member to be removed.
|
||||
*/
|
||||
removeMember(index: number) {
|
||||
this.merkleTree.delete(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a proof of membership.
|
||||
* @param index Index of the proof's member.
|
||||
* @returns Proof object.
|
||||
*/
|
||||
generateMerkleProof(index: number): MerkleProof {
|
||||
const merkleProof = this.merkleTree.createProof(index)
|
||||
|
||||
merkleProof.siblings = merkleProof.siblings.map((s) => s[0])
|
||||
|
||||
return merkleProof
|
||||
}
|
||||
}
|
||||
4
packages/group/src/index.ts
Normal file
4
packages/group/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import Group from "./group"
|
||||
|
||||
export { Group }
|
||||
export * from "./types"
|
||||
1
packages/group/src/types/index.ts
Normal file
1
packages/group/src/types/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type Member = string | bigint
|
||||
4
packages/group/tsconfig.json
Normal file
4
packages/group/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src", "rollup.config.ts"]
|
||||
}
|
||||
115
packages/hardhat/README.md
Normal file
115
packages/hardhat/README.md
Normal file
@@ -0,0 +1,115 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Semaphore Hardhat plugin
|
||||
</h1>
|
||||
<p align="center">A Semaphore Hardhat plugin to deploy verifiers and 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/hardhat">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/hardhat?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/hardhat">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/hardhat.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://discord.gg/6mSdGHnstH">
|
||||
🗣️ 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. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@semaphore-protocol/hardhat` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @semaphore-protocol/hardhat
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @semaphore-protocol/hardhat
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
Import the plugin in your `hardhat.config.ts` file:
|
||||
|
||||
```typescript
|
||||
import "@semaphore-protocol/hardhat"
|
||||
import "./tasks/deploy"
|
||||
|
||||
const hardhatConfig: HardhatUserConfig = {
|
||||
solidity: "0.8.4"
|
||||
}
|
||||
|
||||
export default hardhatConfig
|
||||
```
|
||||
|
||||
And use its tasks to create your own `deploy` task and deploy your contract with a Semaphore address.
|
||||
|
||||
```typescript
|
||||
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 Greeter = await ethers.getContractFactory("Greeter")
|
||||
|
||||
const greeter = await Greeter.deploy(semaphoreAddress)
|
||||
|
||||
await greeter.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.log(`Greeter contract has been deployed to: ${greeter.address}`)
|
||||
}
|
||||
|
||||
return greeter
|
||||
})
|
||||
```
|
||||
8
packages/hardhat/build.tsconfig.json
Normal file
8
packages/hardhat/build.tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declarationDir": "dist/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
46
packages/hardhat/package.json
Normal file
46
packages/hardhat/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/hardhat",
|
||||
"version": "0.1.0",
|
||||
"description": "A Semaphore Hardhat plugin to deploy verifiers and Semaphore contract.",
|
||||
"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/hardhat",
|
||||
"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"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"hardhat": "^2.0.0",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"hardhat": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nomiclabs/hardhat-ethers": "^2.1.1",
|
||||
"@semaphore-protocol/contracts": "^2.5.0",
|
||||
"circomlibjs": "^0.0.8",
|
||||
"ethers": "^5.7.1",
|
||||
"hardhat-dependency-compiler": "^1.1.3"
|
||||
}
|
||||
}
|
||||
29
packages/hardhat/rollup.config.ts
Normal file
29
packages/hardhat/rollup.config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
import * as fs from "fs"
|
||||
import cleanup from "rollup-plugin-cleanup"
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
|
||||
const banner = `/**
|
||||
* @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.exports.require, format: "cjs", banner, exports: "auto" },
|
||||
{ file: pkg.exports.import, format: "es", banner }
|
||||
],
|
||||
external: Object.keys(pkg.dependencies),
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: "./build.tsconfig.json",
|
||||
useTsconfigDeclarationDir: true
|
||||
}),
|
||||
cleanup({ comments: "jsdoc" })
|
||||
]
|
||||
}
|
||||
34
packages/hardhat/src/index.ts
Normal file
34
packages/hardhat/src/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { extendConfig } from "hardhat/config"
|
||||
import { HardhatConfig, HardhatUserConfig } from "hardhat/types"
|
||||
|
||||
import "hardhat-dependency-compiler"
|
||||
import "@nomiclabs/hardhat-ethers"
|
||||
import "./tasks/deploy-semaphore"
|
||||
import "./tasks/deploy-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/Semaphore.sol"
|
||||
]
|
||||
|
||||
if (userConfig.dependencyCompiler?.paths) {
|
||||
config.dependencyCompiler.paths = [...config.dependencyCompiler.paths, ...userConfig.dependencyCompiler.paths]
|
||||
}
|
||||
})
|
||||
51
packages/hardhat/src/tasks/deploy-semaphore.ts
Normal file
51
packages/hardhat/src/tasks/deploy-semaphore.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
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>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs, verifiers }, { ethers }): Promise<Contract> => {
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
|
||||
const [signer] = await ethers.getSigners()
|
||||
|
||||
const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer)
|
||||
const poseidonLib = await PoseidonLibFactory.deploy()
|
||||
|
||||
await poseidonLib.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Poseidon library has been deployed to: ${poseidonLib.address}`)
|
||||
}
|
||||
|
||||
const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
|
||||
libraries: {
|
||||
PoseidonT3: poseidonLib.address
|
||||
}
|
||||
})
|
||||
const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy()
|
||||
|
||||
await incrementalBinaryTreeLib.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
|
||||
}
|
||||
|
||||
const SemaphoreContractFactory = await ethers.getContractFactory("Semaphore", {
|
||||
libraries: {
|
||||
IncrementalBinaryTree: incrementalBinaryTreeLib.address
|
||||
}
|
||||
})
|
||||
|
||||
const semaphoreContract = await SemaphoreContractFactory.deploy(verifiers)
|
||||
|
||||
await semaphoreContract.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Semaphore contract has been deployed to: ${semaphoreContract.address}`)
|
||||
}
|
||||
|
||||
return semaphoreContract
|
||||
})
|
||||
19
packages/hardhat/src/tasks/deploy-verifier.ts
Normal file
19
packages/hardhat/src/tasks/deploy-verifier.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
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
|
||||
})
|
||||
4
packages/hardhat/tsconfig.json
Normal file
4
packages/hardhat/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src", "rollup.config.ts"]
|
||||
}
|
||||
21
packages/identity/LICENSE
Normal file
21
packages/identity/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.
|
||||
86
packages/identity/README.md
Normal file
86
packages/identity/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Semaphore identity
|
||||
</h1>
|
||||
<p align="center">A library to create Semaphore identities.</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/identity">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/identity?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/identity">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/identity.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://discord.gg/6mSdGHnstH">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| This library provides a class that can be used to create identities compatible with the Semaphore [circuits](https://github.com/semaphore-protocol/semaphore/tree/main/circuits). Each identity contains two secret values: _trapdoor_ and _nullifier_, and one public value: _commitment_. The Poseidon hash of the secret values is the identity secret, and its hash is the identity commitment. |
|
||||
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@semaphore-protocol/identity` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @semaphore-protocol/identity
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @semaphore-protocol/identity
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
\# **new Identity**(identityOrMessage?: _string_): _Identity_
|
||||
|
||||
```typescript
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
|
||||
// The identity can be generated randomly.
|
||||
const identity1 = new Identity()
|
||||
|
||||
// Deterministically from a secret message.
|
||||
const identity2 = new Identity("secret-message")
|
||||
|
||||
// Or it can be retrieved from an existing identity.
|
||||
const identity3 = new Identity(identity1.toString())
|
||||
|
||||
// Trapdoor, nullifier and commitment are the attributes (e.g. JS getters).
|
||||
const { trapdoor, nullifier, commitment } = identity1
|
||||
```
|
||||
8
packages/identity/build.tsconfig.json
Normal file
8
packages/identity/build.tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declarationDir": "dist/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
44
packages/identity/package.json
Normal file
44
packages/identity/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/identity",
|
||||
"version": "2.6.1",
|
||||
"description": "A library to create Semaphore identities.",
|
||||
"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/identity",
|
||||
"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/identity"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersproject/bignumber": "^5.5.0",
|
||||
"@ethersproject/random": "^5.5.1",
|
||||
"@ethersproject/sha2": "^5.6.1",
|
||||
"@ethersproject/strings": "^5.6.1",
|
||||
"poseidon-lite": "^0.0.2"
|
||||
}
|
||||
}
|
||||
29
packages/identity/rollup.config.ts
Normal file
29
packages/identity/rollup.config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
import * as fs from "fs"
|
||||
import cleanup from "rollup-plugin-cleanup"
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
|
||||
const banner = `/**
|
||||
* @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.exports.require, format: "cjs", banner, exports: "auto" },
|
||||
{ file: pkg.exports.import, format: "es", banner }
|
||||
],
|
||||
external: Object.keys(pkg.dependencies),
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: "./build.tsconfig.json",
|
||||
useTsconfigDeclarationDir: true
|
||||
}),
|
||||
cleanup({ comments: "jsdoc" })
|
||||
]
|
||||
}
|
||||
5
packages/identity/src/checkParameter.ts
Normal file
5
packages/identity/src/checkParameter.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function checkParameter(value: any, name: string, type: string) {
|
||||
if (typeof value !== type) {
|
||||
throw new TypeError(`Parameter '${name}' is not a ${type}`)
|
||||
}
|
||||
}
|
||||
115
packages/identity/src/identity.test.ts
Normal file
115
packages/identity/src/identity.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { BigNumber } from "@ethersproject/bignumber"
|
||||
import Identity from "./identity"
|
||||
|
||||
describe("Identity", () => {
|
||||
describe("# Identity", () => {
|
||||
it("Should not create a identity if the parameter is not valid", () => {
|
||||
const fun1 = () => new Identity(13 as any)
|
||||
const fun2 = () => new Identity(true as any)
|
||||
const fun3 = () => new Identity((() => true) as any)
|
||||
|
||||
expect(fun1).toThrow("Parameter 'identityOrMessage' is not a string")
|
||||
expect(fun2).toThrow("Parameter 'identityOrMessage' is not a string")
|
||||
expect(fun3).toThrow("Parameter 'identityOrMessage' is not a string")
|
||||
})
|
||||
|
||||
it("Should create random identities", () => {
|
||||
const identity1 = new Identity()
|
||||
const identity2 = new Identity()
|
||||
|
||||
expect(identity1.trapdoor).not.toBe(identity2.getTrapdoor())
|
||||
expect(identity1.nullifier).not.toBe(identity2.getNullifier())
|
||||
expect(identity1.commitment).not.toBe(identity2.getCommitment())
|
||||
})
|
||||
|
||||
it("Should create deterministic identities from a message", () => {
|
||||
const identity1 = new Identity("message")
|
||||
const identity2 = new Identity("message")
|
||||
|
||||
expect(identity1.trapdoor).toBe(identity2.getTrapdoor())
|
||||
expect(identity1.nullifier).toBe(identity2.getNullifier())
|
||||
})
|
||||
|
||||
it("Should create deterministic identities from number/boolean messages", () => {
|
||||
const identity1 = new Identity("true")
|
||||
const identity2 = new Identity("true")
|
||||
const identity3 = new Identity("7")
|
||||
const identity4 = new Identity("7")
|
||||
|
||||
expect(identity1.trapdoor).toBe(identity2.getTrapdoor())
|
||||
expect(identity1.nullifier).toBe(identity2.getNullifier())
|
||||
expect(identity3.trapdoor).toBe(identity4.getTrapdoor())
|
||||
expect(identity3.nullifier).toBe(identity4.getNullifier())
|
||||
})
|
||||
|
||||
it("Should not recreate an existing invalid identity", () => {
|
||||
const fun = () => new Identity('[true, "01323"]')
|
||||
|
||||
expect(fun).toThrow("invalid BigNumber string")
|
||||
})
|
||||
|
||||
it("Should recreate an existing identity", () => {
|
||||
const identity1 = new Identity("message")
|
||||
|
||||
const identity2 = new Identity(identity1.toString())
|
||||
|
||||
expect(identity1.trapdoor).toBe(identity2.getTrapdoor())
|
||||
expect(identity1.nullifier).toBe(identity2.getNullifier())
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getTrapdoor", () => {
|
||||
it("Should return the identity trapdoor", () => {
|
||||
const identity = new Identity("message")
|
||||
|
||||
const trapdoor = identity.getTrapdoor()
|
||||
|
||||
expect(trapdoor).toBe(
|
||||
BigInt("58952291509798197436757858062402199043831251943841934828591473955215726495831")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getNullifier", () => {
|
||||
it("Should return the identity nullifier", () => {
|
||||
const identity = new Identity("message")
|
||||
|
||||
const nullifier = identity.getNullifier()
|
||||
|
||||
expect(nullifier).toBe(
|
||||
BigInt("44673097405870585416457571638073245190425597599743560105244308998175651589997")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# generateCommitment", () => {
|
||||
it("Should generate an identity commitment", () => {
|
||||
const identity = new Identity("message")
|
||||
|
||||
const commitment = identity.generateCommitment()
|
||||
|
||||
expect(commitment).toBe(
|
||||
BigInt("1720349790382552497189398984241859233944354304766757200361065203741879866188")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# toString", () => {
|
||||
it("Should return a string", () => {
|
||||
const identity = new Identity("message")
|
||||
|
||||
const identityString = identity.toString()
|
||||
|
||||
expect(typeof identityString).toBe("string")
|
||||
})
|
||||
|
||||
it("Should return a valid identity string", () => {
|
||||
const identity = new Identity("message")
|
||||
|
||||
const [trapdoor, nullifier] = JSON.parse(identity.toString())
|
||||
|
||||
expect(BigNumber.from(`0x${trapdoor}`).toBigInt()).toBe(identity.getTrapdoor())
|
||||
expect(BigNumber.from(`0x${nullifier}`).toBigInt()).toBe(identity.getNullifier())
|
||||
})
|
||||
})
|
||||
})
|
||||
108
packages/identity/src/identity.ts
Normal file
108
packages/identity/src/identity.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { BigNumber } from "@ethersproject/bignumber"
|
||||
import poseidon from "poseidon-lite"
|
||||
import checkParameter from "./checkParameter"
|
||||
import { generateCommitment, genRandomNumber, isJsonArray, sha256 } from "./utils"
|
||||
|
||||
export default class Identity {
|
||||
private _trapdoor: bigint
|
||||
private _nullifier: bigint
|
||||
private _commitment: bigint
|
||||
|
||||
/**
|
||||
* Initializes the class attributes based on the strategy passed as parameter.
|
||||
* @param identityOrMessage Additional data needed to create identity for given strategy.
|
||||
*/
|
||||
constructor(identityOrMessage?: string) {
|
||||
if (identityOrMessage === undefined) {
|
||||
this._trapdoor = genRandomNumber()
|
||||
this._nullifier = genRandomNumber()
|
||||
this._commitment = generateCommitment(this._nullifier, this._trapdoor)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
checkParameter(identityOrMessage, "identityOrMessage", "string")
|
||||
|
||||
if (!isJsonArray(identityOrMessage)) {
|
||||
const messageHash = sha256(identityOrMessage).slice(2)
|
||||
|
||||
this._trapdoor = BigNumber.from(sha256(`${messageHash}identity_trapdoor`)).toBigInt()
|
||||
this._nullifier = BigNumber.from(sha256(`${messageHash}identity_nullifier`)).toBigInt()
|
||||
this._commitment = generateCommitment(this._nullifier, this._trapdoor)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const [trapdoor, nullifier] = JSON.parse(identityOrMessage)
|
||||
|
||||
this._trapdoor = BigNumber.from(`0x${trapdoor}`).toBigInt()
|
||||
this._nullifier = BigNumber.from(`0x${nullifier}`).toBigInt()
|
||||
this._commitment = generateCommitment(this._nullifier, this._trapdoor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identity trapdoor.
|
||||
* @returns The identity trapdoor.
|
||||
*/
|
||||
public get trapdoor(): bigint {
|
||||
return this._trapdoor
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identity trapdoor.
|
||||
* @returns The identity trapdoor.
|
||||
*/
|
||||
public getTrapdoor(): bigint {
|
||||
return this._trapdoor
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identity nullifier.
|
||||
* @returns The identity nullifier.
|
||||
*/
|
||||
public get nullifier(): bigint {
|
||||
return this._nullifier
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identity nullifier.
|
||||
* @returns The identity nullifier.
|
||||
*/
|
||||
public getNullifier(): bigint {
|
||||
return this._nullifier
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identity commitment.
|
||||
* @returns The identity commitment.
|
||||
*/
|
||||
public get commitment(): bigint {
|
||||
return this._commitment
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identity commitment.
|
||||
* @returns The identity commitment.
|
||||
*/
|
||||
public getCommitment(): bigint {
|
||||
return this._commitment
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since version 2.6.0
|
||||
* Generates the identity commitment from trapdoor and nullifier.
|
||||
* @returns identity commitment
|
||||
*/
|
||||
public generateCommitment(): bigint {
|
||||
return poseidon([poseidon([this._nullifier, this._trapdoor])])
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON string with trapdoor and nullifier. It can be used
|
||||
* to export the identity and reuse it later.
|
||||
* @returns The string representation of the identity.
|
||||
*/
|
||||
public toString(): string {
|
||||
return JSON.stringify([this._trapdoor.toString(16), this._nullifier.toString(16)])
|
||||
}
|
||||
}
|
||||
4
packages/identity/src/index.ts
Normal file
4
packages/identity/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import Identity from "./identity"
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export { Identity }
|
||||
48
packages/identity/src/utils.ts
Normal file
48
packages/identity/src/utils.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { BigNumber } from "@ethersproject/bignumber"
|
||||
import { randomBytes } from "@ethersproject/random"
|
||||
import { sha256 as _sha256 } from "@ethersproject/sha2"
|
||||
import { toUtf8Bytes } from "@ethersproject/strings"
|
||||
import poseidon from "poseidon-lite"
|
||||
|
||||
/**
|
||||
* Returns an hexadecimal sha256 hash of the message passed as parameter.
|
||||
* @param message The string to hash.
|
||||
* @returns The hexadecimal hash of the message.
|
||||
*/
|
||||
export function sha256(message: string): string {
|
||||
const hash = _sha256(toUtf8Bytes(message))
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random big number.
|
||||
* @param numberOfBytes The number of bytes of the number.
|
||||
* @returns The generated random number.
|
||||
*/
|
||||
export function genRandomNumber(numberOfBytes = 31): bigint {
|
||||
return BigNumber.from(randomBytes(numberOfBytes)).toBigInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the identity commitment from trapdoor and nullifier.
|
||||
* @param nullifier The identity nullifier.
|
||||
* @param trapdoor The identity trapdoor.
|
||||
* @returns identity commitment
|
||||
*/
|
||||
export function generateCommitment(nullifier: bigint, trapdoor: bigint): bigint {
|
||||
return poseidon([poseidon([nullifier, trapdoor])])
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is a JSON.
|
||||
* @param jsonString The JSON string.
|
||||
* @returns True or false.
|
||||
*/
|
||||
export function isJsonArray(jsonString: string) {
|
||||
try {
|
||||
return Array.isArray(JSON.parse(jsonString))
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
4
packages/identity/tsconfig.json
Normal file
4
packages/identity/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src", "rollup.config.ts"]
|
||||
}
|
||||
21
packages/proof/LICENSE
Normal file
21
packages/proof/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.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user